| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package cipd | 5 package cipd |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "encoding/json" | 9 "encoding/json" |
| 10 "errors" | 10 "errors" |
| 11 "fmt" | 11 "fmt" |
| 12 "io" | 12 "io" |
| 13 "io/ioutil" | 13 "io/ioutil" |
| 14 "net/http" | 14 "net/http" |
| 15 "net/url" | 15 "net/url" |
| 16 "strconv" | 16 "strconv" |
| 17 "time" | 17 "time" |
| 18 | 18 |
| 19 » "infra/libs/logging" | 19 » "infra/tools/cipd/common" |
| 20 ) | 20 ) |
| 21 | 21 |
| 22 // remoteService is a wrapper around Cloud Endpoints APIs exposed by backend. | 22 // remoteMaxRetries is how many times to retry transient HTTP errors. |
| 23 // See //appengine/chrome_infra_packages. | 23 const remoteMaxRetries = 10 |
| 24 type remoteService struct { | |
| 25 » client *http.Client | |
| 26 » serviceURL string | |
| 27 » log logging.Logger | |
| 28 } | |
| 29 | 24 |
| 30 type uploadSession struct { | |
| 31 ID string | |
| 32 URL string | |
| 33 } | |
| 34 | |
| 35 // packageInstanceMsg corresponds to PackageInstance message on the backend. | |
| 36 type packageInstanceMsg struct { | 25 type packageInstanceMsg struct { |
| 37 PackageName string `json:"package_name"` | 26 PackageName string `json:"package_name"` |
| 38 InstanceID string `json:"instance_id"` | 27 InstanceID string `json:"instance_id"` |
| 39 RegisteredBy string `json:"registered_by"` | 28 RegisteredBy string `json:"registered_by"` |
| 40 RegisteredTs string `json:"registered_ts"` | 29 RegisteredTs string `json:"registered_ts"` |
| 41 } | 30 } |
| 42 | 31 |
| 43 // packageInstance is almost like packageInstanceMsg, but with timestamp as | |
| 44 // time.Time. See also makePackageInstanceInfo. | |
| 45 type packageInstanceInfo struct { | |
| 46 PackageName string | |
| 47 InstanceID string | |
| 48 RegisteredBy string | |
| 49 RegisteredTs time.Time | |
| 50 } | |
| 51 | |
| 52 type registerInstanceResponse struct { | |
| 53 UploadSession *uploadSession | |
| 54 AlreadyRegistered bool | |
| 55 Info packageInstanceInfo | |
| 56 } | |
| 57 | |
| 58 type fetchInstanceResponse struct { | |
| 59 FetchURL string | |
| 60 Info packageInstanceInfo | |
| 61 } | |
| 62 | |
| 63 // roleChangeMsg corresponds to RoleChange proto message on backend. | 32 // roleChangeMsg corresponds to RoleChange proto message on backend. |
| 64 type roleChangeMsg struct { | 33 type roleChangeMsg struct { |
| 65 Action string `json:"action"` | 34 Action string `json:"action"` |
| 66 Role string `json:"role"` | 35 Role string `json:"role"` |
| 67 Principal string `json:"principal"` | 36 Principal string `json:"principal"` |
| 68 } | 37 } |
| 69 | 38 |
| 70 // pendingProcessingError is returned by attachTags if package instance is not | 39 // pendingProcessingError is returned by attachTags if package instance is not |
| 71 // yet ready and the call should be retried later. | 40 // yet ready and the call should be retried later. |
| 72 type pendingProcessingError struct { | 41 type pendingProcessingError struct { |
| 73 message string | 42 message string |
| 74 } | 43 } |
| 75 | 44 |
| 76 func (e *pendingProcessingError) Error() string { | 45 func (e *pendingProcessingError) Error() string { |
| 77 return e.message | 46 return e.message |
| 78 } | 47 } |
| 79 | 48 |
| 80 // newRemoteService is mocked in tests. | 49 // remoteImpl implements remote on top of real HTTP calls. |
| 81 var newRemoteService = func(client *http.Client, url string, log logging.Logger)
*remoteService { | 50 type remoteImpl struct { |
| 82 » log.Infof("cipd: service URL is %s", url) | 51 » client *Client |
| 83 » return &remoteService{ | |
| 84 » » client: client, | |
| 85 » » serviceURL: url, | |
| 86 » » log: log, | |
| 87 » } | |
| 88 } | 52 } |
| 89 | 53 |
| 90 // makeRequest sends POST or GET request with retries. | 54 func isTemporaryNetError(err error) bool { |
| 91 func (r *remoteService) makeRequest(path, method string, request, response inter
face{}) error { | 55 » // TODO(vadimsh): Figure out how to recognize dial timeouts, read timeou
ts, |
| 56 » // etc. For now all errors that end up here are considered temporary. |
| 57 » return true |
| 58 } |
| 59 |
| 60 // isTemporaryHTTPError returns true for HTTP status codes that indicate |
| 61 // a temporary error that may go away if request is retried. |
| 62 func isTemporaryHTTPError(statusCode int) bool { |
| 63 » return statusCode >= 500 || statusCode == 408 || statusCode == 429 |
| 64 } |
| 65 |
| 66 // makeRequest sends POST or GET REST JSON requests with retries. |
| 67 func (r *remoteImpl) makeRequest(path, method string, request, response interfac
e{}) error { |
| 92 var body []byte | 68 var body []byte |
| 93 var err error | |
| 94 if request != nil { | 69 if request != nil { |
| 95 » » body, err = json.Marshal(request) | 70 » » b, err := json.Marshal(request) |
| 96 if err != nil { | 71 if err != nil { |
| 97 return err | 72 return err |
| 98 } | 73 } |
| 74 body = b |
| 99 } | 75 } |
| 100 » url := fmt.Sprintf("%s/_ah/api/%s", r.serviceURL, path) | 76 |
| 101 » for attempt := 0; attempt < 10; attempt++ { | 77 » url := fmt.Sprintf("%s/_ah/api/%s", r.client.ServiceURL, path) |
| 78 » for attempt := 0; attempt < remoteMaxRetries; attempt++ { |
| 102 if attempt != 0 { | 79 if attempt != 0 { |
| 103 » » » r.log.Warningf("cipd: retrying request to %s", url) | 80 » » » r.client.Log.Warningf("cipd: retrying request to %s", ur
l) |
| 104 » » » clock.Sleep(2 * time.Second) | 81 » » » r.client.clock.sleep(2 * time.Second) |
| 105 } | 82 } |
| 83 |
| 84 // Prepare request. |
| 106 var bodyReader io.Reader | 85 var bodyReader io.Reader |
| 107 if body != nil { | 86 if body != nil { |
| 108 bodyReader = bytes.NewReader(body) | 87 bodyReader = bytes.NewReader(body) |
| 109 } | 88 } |
| 110 req, err := http.NewRequest(method, url, bodyReader) | 89 req, err := http.NewRequest(method, url, bodyReader) |
| 111 if err != nil { | 90 if err != nil { |
| 112 return err | 91 return err |
| 113 } | 92 } |
| 114 if body != nil { | 93 if body != nil { |
| 115 req.Header.Set("Content-Type", "application/json") | 94 req.Header.Set("Content-Type", "application/json") |
| 116 } | 95 } |
| 117 » » req.Header.Set("User-Agent", userAgent()) | 96 » » req.Header.Set("User-Agent", r.client.UserAgent) |
| 118 » » resp, err := r.client.Do(req) | 97 |
| 98 » » // Connect, read response. |
| 99 » » r.client.Log.Debugf("cipd: %s %s", method, url) |
| 100 » » resp, err := r.client.doAuthenticatedHTTPRequest(req) |
| 119 if err != nil { | 101 if err != nil { |
| 102 if isTemporaryNetError(err) { |
| 103 r.client.Log.Warningf("cipd: connectivity error
(%s)", err) |
| 104 continue |
| 105 } |
| 120 return err | 106 return err |
| 121 } | 107 } |
| 108 responseBody, err := ioutil.ReadAll(resp.Body) |
| 109 resp.Body.Close() |
| 110 if err != nil { |
| 111 if isTemporaryNetError(err) { |
| 112 r.client.Log.Warningf("cipd: temporary error whe
n reading response (%s)", err) |
| 113 continue |
| 114 } |
| 115 return err |
| 116 } |
| 117 r.client.Log.Debugf("cipd: http %d: %s", resp.StatusCode, body) |
| 118 if isTemporaryHTTPError(resp.StatusCode) { |
| 119 continue |
| 120 } |
| 121 |
| 122 // Success? | 122 // Success? |
| 123 if resp.StatusCode < 300 { | 123 if resp.StatusCode < 300 { |
| 124 » » » defer resp.Body.Close() | 124 » » » return json.Unmarshal(responseBody, response) |
| 125 » » » return json.NewDecoder(resp.Body).Decode(response) | |
| 126 } | 125 } |
| 126 |
| 127 // Fatal error? | 127 // Fatal error? |
| 128 » » if resp.StatusCode >= 300 && resp.StatusCode < 500 { | 128 » » if resp.StatusCode == 403 || resp.StatusCode == 401 { |
| 129 » » » defer resp.Body.Close() | 129 » » » return ErrAccessDenined |
| 130 » » » body, _ := ioutil.ReadAll(resp.Body) | |
| 131 » » » return fmt.Errorf("Unexpected reply (HTTP %d):\n%s", res
p.StatusCode, string(body)) | |
| 132 } | 130 } |
| 133 » » // Retry. | 131 » » return fmt.Errorf("Unexpected reply (HTTP %d):\n%s", resp.Status
Code, string(body)) |
| 134 » » resp.Body.Close() | |
| 135 } | 132 } |
| 136 » return fmt.Errorf("Request to %s failed after 10 attempts", url) | 133 |
| 134 » return ErrBackendInaccessible |
| 137 } | 135 } |
| 138 | 136 |
| 139 func (r *remoteService) initiateUpload(sha1 string) (s *uploadSession, err error
) { | 137 func (r *remoteImpl) initiateUpload(sha1 string) (s *UploadSession, err error) { |
| 140 var reply struct { | 138 var reply struct { |
| 141 Status string `json:"status"` | 139 Status string `json:"status"` |
| 142 UploadSessionID string `json:"upload_session_id"` | 140 UploadSessionID string `json:"upload_session_id"` |
| 143 UploadURL string `json:"upload_url"` | 141 UploadURL string `json:"upload_url"` |
| 144 ErrorMessage string `json:"error_message"` | 142 ErrorMessage string `json:"error_message"` |
| 145 } | 143 } |
| 146 err = r.makeRequest("cas/v1/upload/SHA1/"+sha1, "POST", nil, &reply) | 144 err = r.makeRequest("cas/v1/upload/SHA1/"+sha1, "POST", nil, &reply) |
| 147 if err != nil { | 145 if err != nil { |
| 148 return | 146 return |
| 149 } | 147 } |
| 150 switch reply.Status { | 148 switch reply.Status { |
| 151 case "ALREADY_UPLOADED": | 149 case "ALREADY_UPLOADED": |
| 152 return | 150 return |
| 153 case "SUCCESS": | 151 case "SUCCESS": |
| 154 » » s = &uploadSession{ | 152 » » s = &UploadSession{reply.UploadSessionID, reply.UploadURL} |
| 155 » » » ID: reply.UploadSessionID, | |
| 156 » » » URL: reply.UploadURL, | |
| 157 » » } | |
| 158 case "ERROR": | 153 case "ERROR": |
| 159 err = fmt.Errorf("Server replied with error: %s", reply.ErrorMes
sage) | 154 err = fmt.Errorf("Server replied with error: %s", reply.ErrorMes
sage) |
| 160 default: | 155 default: |
| 161 err = fmt.Errorf("Unexpected status: %s", reply.Status) | 156 err = fmt.Errorf("Unexpected status: %s", reply.Status) |
| 162 } | 157 } |
| 163 return | 158 return |
| 164 } | 159 } |
| 165 | 160 |
| 166 func (r *remoteService) finalizeUpload(sessionID string) (finished bool, err err
or) { | 161 func (r *remoteImpl) finalizeUpload(sessionID string) (finished bool, err error)
{ |
| 167 var reply struct { | 162 var reply struct { |
| 168 Status string `json:"status"` | 163 Status string `json:"status"` |
| 169 ErrorMessage string `json:"error_message"` | 164 ErrorMessage string `json:"error_message"` |
| 170 } | 165 } |
| 171 err = r.makeRequest("cas/v1/finalize/"+sessionID, "POST", nil, &reply) | 166 err = r.makeRequest("cas/v1/finalize/"+sessionID, "POST", nil, &reply) |
| 172 if err != nil { | 167 if err != nil { |
| 173 return | 168 return |
| 174 } | 169 } |
| 175 switch reply.Status { | 170 switch reply.Status { |
| 176 case "MISSING": | 171 case "MISSING": |
| 177 » » err = errors.New("Upload session is unexpectedly missing") | 172 » » err = ErrUploadSessionDied |
| 178 case "UPLOADING", "VERIFYING": | 173 case "UPLOADING", "VERIFYING": |
| 179 finished = false | 174 finished = false |
| 180 case "PUBLISHED": | 175 case "PUBLISHED": |
| 181 finished = true | 176 finished = true |
| 182 case "ERROR": | 177 case "ERROR": |
| 183 err = errors.New(reply.ErrorMessage) | 178 err = errors.New(reply.ErrorMessage) |
| 184 default: | 179 default: |
| 185 err = fmt.Errorf("Unexpected upload session status: %s", reply.S
tatus) | 180 err = fmt.Errorf("Unexpected upload session status: %s", reply.S
tatus) |
| 186 } | 181 } |
| 187 return | 182 return |
| 188 } | 183 } |
| 189 | 184 |
| 190 func (r *remoteService) registerInstance(packageName, instanceID string) (*regis
terInstanceResponse, error) { | 185 func (r *remoteImpl) registerInstance(pin common.Pin) (*registerInstanceResponse
, error) { |
| 191 » endpoint, err := instanceEndpoint(packageName, instanceID) | 186 » endpoint, err := instanceEndpoint(pin) |
| 192 if err != nil { | 187 if err != nil { |
| 193 return nil, err | 188 return nil, err |
| 194 } | 189 } |
| 195 var reply struct { | 190 var reply struct { |
| 196 Status string `json:"status"` | 191 Status string `json:"status"` |
| 197 Instance packageInstanceMsg `json:"instance"` | 192 Instance packageInstanceMsg `json:"instance"` |
| 198 UploadSessionID string `json:"upload_session_id"` | 193 UploadSessionID string `json:"upload_session_id"` |
| 199 UploadURL string `json:"upload_url"` | 194 UploadURL string `json:"upload_url"` |
| 200 ErrorMessage string `json:"error_message"` | 195 ErrorMessage string `json:"error_message"` |
| 201 } | 196 } |
| 202 err = r.makeRequest(endpoint, "POST", nil, &reply) | 197 err = r.makeRequest(endpoint, "POST", nil, &reply) |
| 203 if err != nil { | 198 if err != nil { |
| 204 return nil, err | 199 return nil, err |
| 205 } | 200 } |
| 206 switch reply.Status { | 201 switch reply.Status { |
| 207 case "REGISTERED", "ALREADY_REGISTERED": | 202 case "REGISTERED", "ALREADY_REGISTERED": |
| 208 » » info, err := makePackageInstanceInfo(reply.Instance) | 203 » » ts, err := convertTimestamp(reply.Instance.RegisteredTs) |
| 209 if err != nil { | 204 if err != nil { |
| 210 return nil, err | 205 return nil, err |
| 211 } | 206 } |
| 212 return ®isterInstanceResponse{ | 207 return ®isterInstanceResponse{ |
| 213 » » » AlreadyRegistered: reply.Status == "ALREADY_REGISTERED", | 208 » » » alreadyRegistered: reply.Status == "ALREADY_REGISTERED", |
| 214 » » » Info: info, | 209 » » » registeredBy: reply.Instance.RegisteredBy, |
| 210 » » » registeredTs: ts, |
| 215 }, nil | 211 }, nil |
| 216 case "UPLOAD_FIRST": | 212 case "UPLOAD_FIRST": |
| 217 if reply.UploadSessionID == "" { | 213 if reply.UploadSessionID == "" { |
| 218 » » » return nil, errors.New("Server didn't provide upload ses
sion ID") | 214 » » » return nil, ErrNoUploadSessionID |
| 219 } | 215 } |
| 220 return ®isterInstanceResponse{ | 216 return ®isterInstanceResponse{ |
| 221 » » » UploadSession: &uploadSession{ | 217 » » » uploadSession: &UploadSession{reply.UploadSessionID, rep
ly.UploadURL}, |
| 222 » » » » ID: reply.UploadSessionID, | |
| 223 » » » » URL: reply.UploadURL, | |
| 224 » » » }, | |
| 225 }, nil | 218 }, nil |
| 226 case "ERROR": | 219 case "ERROR": |
| 227 return nil, errors.New(reply.ErrorMessage) | 220 return nil, errors.New(reply.ErrorMessage) |
| 228 } | 221 } |
| 229 return nil, fmt.Errorf("Unexpected register package status: %s", reply.S
tatus) | 222 return nil, fmt.Errorf("Unexpected register package status: %s", reply.S
tatus) |
| 230 } | 223 } |
| 231 | 224 |
| 232 func (r *remoteService) fetchInstance(packageName, instanceID string) (*fetchIns
tanceResponse, error) { | 225 func (r *remoteImpl) fetchInstance(pin common.Pin) (*fetchInstanceResponse, erro
r) { |
| 233 » endpoint, err := instanceEndpoint(packageName, instanceID) | 226 » endpoint, err := instanceEndpoint(pin) |
| 234 if err != nil { | 227 if err != nil { |
| 235 return nil, err | 228 return nil, err |
| 236 } | 229 } |
| 237 var reply struct { | 230 var reply struct { |
| 238 Status string `json:"status"` | 231 Status string `json:"status"` |
| 239 Instance packageInstanceMsg `json:"instance"` | 232 Instance packageInstanceMsg `json:"instance"` |
| 240 FetchURL string `json:"fetch_url"` | 233 FetchURL string `json:"fetch_url"` |
| 241 ErrorMessage string `json:"error_message"` | 234 ErrorMessage string `json:"error_message"` |
| 242 } | 235 } |
| 243 err = r.makeRequest(endpoint, "GET", nil, &reply) | 236 err = r.makeRequest(endpoint, "GET", nil, &reply) |
| 244 if err != nil { | 237 if err != nil { |
| 245 return nil, err | 238 return nil, err |
| 246 } | 239 } |
| 247 switch reply.Status { | 240 switch reply.Status { |
| 248 case "SUCCESS": | 241 case "SUCCESS": |
| 249 » » info, err := makePackageInstanceInfo(reply.Instance) | 242 » » ts, err := convertTimestamp(reply.Instance.RegisteredTs) |
| 250 if err != nil { | 243 if err != nil { |
| 251 return nil, err | 244 return nil, err |
| 252 } | 245 } |
| 253 return &fetchInstanceResponse{ | 246 return &fetchInstanceResponse{ |
| 254 » » » FetchURL: reply.FetchURL, | 247 » » » fetchURL: reply.FetchURL, |
| 255 » » » Info: info, | 248 » » » registeredBy: reply.Instance.RegisteredBy, |
| 249 » » » registeredTs: ts, |
| 256 }, nil | 250 }, nil |
| 257 case "PACKAGE_NOT_FOUND": | 251 case "PACKAGE_NOT_FOUND": |
| 258 » » return nil, fmt.Errorf("Package '%s' is not registered or you do
not have permission to fetch it", packageName) | 252 » » return nil, fmt.Errorf("Package '%s' is not registered or you do
not have permission to fetch it", pin.PackageName) |
| 259 case "INSTANCE_NOT_FOUND": | 253 case "INSTANCE_NOT_FOUND": |
| 260 » » return nil, fmt.Errorf("Package '%s' doesn't have instance '%s'"
, packageName, instanceID) | 254 » » return nil, fmt.Errorf("Package '%s' doesn't have instance '%s'"
, pin.PackageName, pin.InstanceID) |
| 261 case "ERROR": | 255 case "ERROR": |
| 262 return nil, errors.New(reply.ErrorMessage) | 256 return nil, errors.New(reply.ErrorMessage) |
| 263 } | 257 } |
| 264 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) | 258 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) |
| 265 } | 259 } |
| 266 | 260 |
| 267 func (r *remoteService) fetchACL(packagePath string) ([]PackageACL, error) { | 261 func (r *remoteImpl) fetchACL(packagePath string) ([]PackageACL, error) { |
| 268 endpoint, err := aclEndpoint(packagePath) | 262 endpoint, err := aclEndpoint(packagePath) |
| 269 if err != nil { | 263 if err != nil { |
| 270 return nil, err | 264 return nil, err |
| 271 } | 265 } |
| 272 var reply struct { | 266 var reply struct { |
| 273 Status string `json:"status"` | 267 Status string `json:"status"` |
| 274 ErrorMessage string `json:"error_message"` | 268 ErrorMessage string `json:"error_message"` |
| 275 Acls struct { | 269 Acls struct { |
| 276 Acls []struct { | 270 Acls []struct { |
| 277 PackagePath string `json:"package_path"` | 271 PackagePath string `json:"package_path"` |
| (...skipping 24 matching lines...) Expand all Loading... |
| 302 ModifiedTs: ts, | 296 ModifiedTs: ts, |
| 303 }) | 297 }) |
| 304 } | 298 } |
| 305 return out, nil | 299 return out, nil |
| 306 case "ERROR": | 300 case "ERROR": |
| 307 return nil, errors.New(reply.ErrorMessage) | 301 return nil, errors.New(reply.ErrorMessage) |
| 308 } | 302 } |
| 309 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) | 303 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) |
| 310 } | 304 } |
| 311 | 305 |
| 312 func (r *remoteService) modifyACL(packagePath string, changes []PackageACLChange
) error { | 306 func (r *remoteImpl) modifyACL(packagePath string, changes []PackageACLChange) e
rror { |
| 313 endpoint, err := aclEndpoint(packagePath) | 307 endpoint, err := aclEndpoint(packagePath) |
| 314 if err != nil { | 308 if err != nil { |
| 315 return err | 309 return err |
| 316 } | 310 } |
| 317 var request struct { | 311 var request struct { |
| 318 Changes []roleChangeMsg `json:"changes"` | 312 Changes []roleChangeMsg `json:"changes"` |
| 319 } | 313 } |
| 320 for _, c := range changes { | 314 for _, c := range changes { |
| 321 action := "" | 315 action := "" |
| 322 if c.Action == GrantRole { | 316 if c.Action == GrantRole { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 342 } | 336 } |
| 343 switch reply.Status { | 337 switch reply.Status { |
| 344 case "SUCCESS": | 338 case "SUCCESS": |
| 345 return nil | 339 return nil |
| 346 case "ERROR": | 340 case "ERROR": |
| 347 return errors.New(reply.ErrorMessage) | 341 return errors.New(reply.ErrorMessage) |
| 348 } | 342 } |
| 349 return fmt.Errorf("Unexpected reply status: %s", reply.Status) | 343 return fmt.Errorf("Unexpected reply status: %s", reply.Status) |
| 350 } | 344 } |
| 351 | 345 |
| 352 func (r *remoteService) attachTags(packageName, instanceID string, tags []string
) error { | 346 func (r *remoteImpl) attachTags(pin common.Pin, tags []string) error { |
| 353 // Tags will be passed in the request body, not via URL. | 347 // Tags will be passed in the request body, not via URL. |
| 354 » endpoint, err := tagsEndpoint(packageName, instanceID, nil) | 348 » endpoint, err := tagsEndpoint(pin, nil) |
| 355 if err != nil { | 349 if err != nil { |
| 356 return err | 350 return err |
| 357 } | 351 } |
| 358 | |
| 359 if len(tags) == 0 { | |
| 360 return errors.New("At least one tag must be provided") | |
| 361 } | |
| 362 for _, tag := range tags { | 352 for _, tag := range tags { |
| 363 » » err = ValidateInstanceTag(tag) | 353 » » err = common.ValidateInstanceTag(tag) |
| 364 if err != nil { | 354 if err != nil { |
| 365 return err | 355 return err |
| 366 } | 356 } |
| 367 } | 357 } |
| 358 |
| 368 var request struct { | 359 var request struct { |
| 369 Tags []string `json:"tags"` | 360 Tags []string `json:"tags"` |
| 370 } | 361 } |
| 371 request.Tags = tags | 362 request.Tags = tags |
| 372 | 363 |
| 373 var reply struct { | 364 var reply struct { |
| 374 Status string `json:"status"` | 365 Status string `json:"status"` |
| 375 ErrorMessage string `json:"error_message"` | 366 ErrorMessage string `json:"error_message"` |
| 376 } | 367 } |
| 377 err = r.makeRequest(endpoint, "POST", &request, &reply) | 368 err = r.makeRequest(endpoint, "POST", &request, &reply) |
| 378 if err != nil { | 369 if err != nil { |
| 379 return err | 370 return err |
| 380 } | 371 } |
| 381 switch reply.Status { | 372 switch reply.Status { |
| 382 case "SUCCESS": | 373 case "SUCCESS": |
| 383 return nil | 374 return nil |
| 384 case "PROCESSING_NOT_FINISHED_YET": | 375 case "PROCESSING_NOT_FINISHED_YET": |
| 385 return &pendingProcessingError{reply.ErrorMessage} | 376 return &pendingProcessingError{reply.ErrorMessage} |
| 386 case "ERROR", "PROCESSING_FAILED": | 377 case "ERROR", "PROCESSING_FAILED": |
| 387 return errors.New(reply.ErrorMessage) | 378 return errors.New(reply.ErrorMessage) |
| 388 } | 379 } |
| 389 return fmt.Errorf("Unexpected status when attaching tags: %s", reply.Sta
tus) | 380 return fmt.Errorf("Unexpected status when attaching tags: %s", reply.Sta
tus) |
| 390 } | 381 } |
| 391 | 382 |
| 392 //////////////////////////////////////////////////////////////////////////////// | 383 //////////////////////////////////////////////////////////////////////////////// |
| 393 | 384 |
| 394 func instanceEndpoint(packageName, instanceID string) (string, error) { | 385 func instanceEndpoint(pin common.Pin) (string, error) { |
| 395 » err := ValidatePackageName(packageName) | 386 » err := common.ValidatePin(pin) |
| 396 » if err != nil { | |
| 397 » » return "", err | |
| 398 » } | |
| 399 » err = ValidateInstanceID(instanceID) | |
| 400 if err != nil { | 387 if err != nil { |
| 401 return "", err | 388 return "", err |
| 402 } | 389 } |
| 403 params := url.Values{} | 390 params := url.Values{} |
| 404 » params.Add("package_name", packageName) | 391 » params.Add("package_name", pin.PackageName) |
| 405 » params.Add("instance_id", instanceID) | 392 » params.Add("instance_id", pin.InstanceID) |
| 406 return "repo/v1/instance?" + params.Encode(), nil | 393 return "repo/v1/instance?" + params.Encode(), nil |
| 407 } | 394 } |
| 408 | 395 |
| 409 func aclEndpoint(packagePath string) (string, error) { | 396 func aclEndpoint(packagePath string) (string, error) { |
| 410 » err := ValidatePackageName(packagePath) | 397 » err := common.ValidatePackageName(packagePath) |
| 411 if err != nil { | 398 if err != nil { |
| 412 return "", err | 399 return "", err |
| 413 } | 400 } |
| 414 params := url.Values{} | 401 params := url.Values{} |
| 415 params.Add("package_path", packagePath) | 402 params.Add("package_path", packagePath) |
| 416 return "repo/v1/acl?" + params.Encode(), nil | 403 return "repo/v1/acl?" + params.Encode(), nil |
| 417 } | 404 } |
| 418 | 405 |
| 419 func tagsEndpoint(packageName, instanceID string, tags []string) (string, error)
{ | 406 func tagsEndpoint(pin common.Pin, tags []string) (string, error) { |
| 420 » err := ValidatePackageName(packageName) | 407 » err := common.ValidatePin(pin) |
| 421 » if err != nil { | |
| 422 » » return "", err | |
| 423 » } | |
| 424 » err = ValidateInstanceID(instanceID) | |
| 425 if err != nil { | 408 if err != nil { |
| 426 return "", err | 409 return "", err |
| 427 } | 410 } |
| 428 for _, tag := range tags { | 411 for _, tag := range tags { |
| 429 » » err = ValidateInstanceTag(tag) | 412 » » err = common.ValidateInstanceTag(tag) |
| 430 if err != nil { | 413 if err != nil { |
| 431 return "", err | 414 return "", err |
| 432 } | 415 } |
| 433 } | 416 } |
| 434 params := url.Values{} | 417 params := url.Values{} |
| 435 » params.Add("package_name", packageName) | 418 » params.Add("package_name", pin.PackageName) |
| 436 » params.Add("instance_id", instanceID) | 419 » params.Add("instance_id", pin.InstanceID) |
| 437 for _, tag := range tags { | 420 for _, tag := range tags { |
| 438 params.Add("tag", tag) | 421 params.Add("tag", tag) |
| 439 } | 422 } |
| 440 return "repo/v1/tags?" + params.Encode(), nil | 423 return "repo/v1/tags?" + params.Encode(), nil |
| 441 } | 424 } |
| 442 | 425 |
| 443 // convertTimestamp coverts string with int64 timestamp in microseconds since | 426 // convertTimestamp coverts string with int64 timestamp in microseconds since |
| 444 // to time.Time | 427 // to time.Time |
| 445 func convertTimestamp(ts string) (time.Time, error) { | 428 func convertTimestamp(ts string) (time.Time, error) { |
| 446 i, err := strconv.ParseInt(ts, 10, 64) | 429 i, err := strconv.ParseInt(ts, 10, 64) |
| 447 if err != nil { | 430 if err != nil { |
| 448 return time.Time{}, fmt.Errorf("Unexpected timestamp value '%s'
in the server response", ts) | 431 return time.Time{}, fmt.Errorf("Unexpected timestamp value '%s'
in the server response", ts) |
| 449 } | 432 } |
| 450 return time.Unix(0, i*1000), nil | 433 return time.Unix(0, i*1000), nil |
| 451 } | 434 } |
| 452 | |
| 453 func makePackageInstanceInfo(msg packageInstanceMsg) (pi packageInstanceInfo, er
r error) { | |
| 454 ts, err := convertTimestamp(msg.RegisteredTs) | |
| 455 if err != nil { | |
| 456 return | |
| 457 } | |
| 458 pi = packageInstanceInfo{ | |
| 459 PackageName: msg.PackageName, | |
| 460 InstanceID: msg.InstanceID, | |
| 461 RegisteredBy: msg.RegisteredBy, | |
| 462 RegisteredTs: ts, | |
| 463 } | |
| 464 return | |
| 465 } | |
| OLD | NEW |