Chromium Code Reviews| 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 retryable. | |
|
nodir
2015/05/13 01:25:29
english: retriable
Vadim Sh.
2015/05/13 03:08:06
Changed to "temporary", retriable is not a word, I
nodir
2015/05/13 15:54:25
http://en.wiktionary.org/wiki/retriable
| |
| 57 » return true | |
| 58 } | |
| 59 | |
| 60 // makeRequest sends POST or GET REST JSON requests with retries. | |
| 61 func (r *remoteImpl) makeRequest(path, method string, request, response interfac e{}) error { | |
| 92 var body []byte | 62 var body []byte |
| 93 var err error | |
| 94 if request != nil { | 63 if request != nil { |
| 95 » » body, err = json.Marshal(request) | 64 » » b, err := json.Marshal(request) |
| 96 if err != nil { | 65 if err != nil { |
| 97 return err | 66 return err |
| 98 } | 67 } |
| 68 body = b | |
| 99 } | 69 } |
| 100 » url := fmt.Sprintf("%s/_ah/api/%s", r.serviceURL, path) | 70 |
| 101 » for attempt := 0; attempt < 10; attempt++ { | 71 » url := fmt.Sprintf("%s/_ah/api/%s", r.client.ServiceURL, path) |
| 72 » for attempt := 0; attempt < remoteMaxRetries; attempt++ { | |
| 102 if attempt != 0 { | 73 if attempt != 0 { |
| 103 » » » r.log.Warningf("cipd: retrying request to %s", url) | 74 » » » r.client.Log.Warningf("cipd: retrying request to %s", ur l) |
| 104 » » » clock.Sleep(2 * time.Second) | 75 » » » r.client.clock.sleep(2 * time.Second) |
| 105 } | 76 } |
| 77 | |
| 78 // Prepare request. | |
| 106 var bodyReader io.Reader | 79 var bodyReader io.Reader |
| 107 if body != nil { | 80 if body != nil { |
| 108 bodyReader = bytes.NewReader(body) | 81 bodyReader = bytes.NewReader(body) |
| 109 } | 82 } |
| 110 req, err := http.NewRequest(method, url, bodyReader) | 83 req, err := http.NewRequest(method, url, bodyReader) |
| 111 if err != nil { | 84 if err != nil { |
| 112 return err | 85 return err |
| 113 } | 86 } |
| 114 if body != nil { | 87 if body != nil { |
| 115 req.Header.Set("Content-Type", "application/json") | 88 req.Header.Set("Content-Type", "application/json") |
| 116 } | 89 } |
| 117 » » req.Header.Set("User-Agent", userAgent()) | 90 » » req.Header.Set("User-Agent", r.client.UserAgent) |
| 118 » » resp, err := r.client.Do(req) | 91 |
| 92 » » // Connect, read response. | |
| 93 » » r.client.Log.Debugf("cipd: %s %s", method, url) | |
| 94 » » resp, err := r.client.doAuthenticatedHTTPRequest(req) | |
| 119 if err != nil { | 95 if err != nil { |
| 96 if isTemporaryNetError(err) { | |
| 97 r.client.Log.Warningf("cipd: connectivity error (%s)", err) | |
| 98 continue | |
| 99 } | |
| 120 return err | 100 return err |
| 121 } | 101 } |
| 102 responseBody, err := ioutil.ReadAll(resp.Body) | |
| 103 resp.Body.Close() | |
| 104 if err != nil { | |
| 105 if isTemporaryNetError(err) { | |
| 106 r.client.Log.Warningf("cipd: temporary error whe n reading response (%s)", err) | |
| 107 continue | |
| 108 } | |
| 109 return err | |
| 110 } | |
| 111 r.client.Log.Debugf("cipd: http %d: %s", resp.StatusCode, body) | |
| 112 | |
| 122 // Success? | 113 // Success? |
| 123 if resp.StatusCode < 300 { | 114 if resp.StatusCode < 300 { |
| 124 » » » defer resp.Body.Close() | 115 » » » return json.Unmarshal(responseBody, response) |
| 125 » » » return json.NewDecoder(resp.Body).Decode(response) | |
| 126 } | 116 } |
| 117 | |
| 127 // Fatal error? | 118 // Fatal error? |
| 128 » » if resp.StatusCode >= 300 && resp.StatusCode < 500 { | 119 » » if resp.StatusCode >= 300 && resp.StatusCode < 500 && resp.Statu sCode != 408 { |
| 129 » » » defer resp.Body.Close() | 120 » » » if resp.StatusCode == 403 || resp.StatusCode == 401 { |
|
nodir
2015/05/13 01:25:29
move this if out of the enclosing if
Vadim Sh.
2015/05/13 03:08:06
Done.
| |
| 130 » » » body, _ := ioutil.ReadAll(resp.Body) | 121 » » » » return ErrAccessDenined |
| 122 » » » } | |
| 131 return fmt.Errorf("Unexpected reply (HTTP %d):\n%s", res p.StatusCode, string(body)) | 123 return fmt.Errorf("Unexpected reply (HTTP %d):\n%s", res p.StatusCode, string(body)) |
| 132 } | 124 } |
| 133 // Retry. | |
| 134 resp.Body.Close() | |
| 135 } | 125 } |
| 136 » return fmt.Errorf("Request to %s failed after 10 attempts", url) | 126 |
| 127 » return ErrBackendInaccessible | |
| 137 } | 128 } |
| 138 | 129 |
| 139 func (r *remoteService) initiateUpload(sha1 string) (s *uploadSession, err error ) { | 130 func (r *remoteImpl) initiateUpload(sha1 string) (s *UploadSession, err error) { |
| 140 var reply struct { | 131 var reply struct { |
| 141 Status string `json:"status"` | 132 Status string `json:"status"` |
| 142 UploadSessionID string `json:"upload_session_id"` | 133 UploadSessionID string `json:"upload_session_id"` |
| 143 UploadURL string `json:"upload_url"` | 134 UploadURL string `json:"upload_url"` |
| 144 ErrorMessage string `json:"error_message"` | 135 ErrorMessage string `json:"error_message"` |
| 145 } | 136 } |
| 146 err = r.makeRequest("cas/v1/upload/SHA1/"+sha1, "POST", nil, &reply) | 137 err = r.makeRequest("cas/v1/upload/SHA1/"+sha1, "POST", nil, &reply) |
| 147 if err != nil { | 138 if err != nil { |
| 148 return | 139 return |
| 149 } | 140 } |
| 150 switch reply.Status { | 141 switch reply.Status { |
| 151 case "ALREADY_UPLOADED": | 142 case "ALREADY_UPLOADED": |
| 152 return | 143 return |
| 153 case "SUCCESS": | 144 case "SUCCESS": |
| 154 » » s = &uploadSession{ | 145 » » s = &UploadSession{reply.UploadSessionID, reply.UploadURL} |
| 155 » » » ID: reply.UploadSessionID, | |
| 156 » » » URL: reply.UploadURL, | |
| 157 » » } | |
| 158 case "ERROR": | 146 case "ERROR": |
| 159 err = fmt.Errorf("Server replied with error: %s", reply.ErrorMes sage) | 147 err = fmt.Errorf("Server replied with error: %s", reply.ErrorMes sage) |
| 160 default: | 148 default: |
| 161 err = fmt.Errorf("Unexpected status: %s", reply.Status) | 149 err = fmt.Errorf("Unexpected status: %s", reply.Status) |
| 162 } | 150 } |
| 163 return | 151 return |
| 164 } | 152 } |
| 165 | 153 |
| 166 func (r *remoteService) finalizeUpload(sessionID string) (finished bool, err err or) { | 154 func (r *remoteImpl) finalizeUpload(sessionID string) (finished bool, err error) { |
| 167 var reply struct { | 155 var reply struct { |
| 168 Status string `json:"status"` | 156 Status string `json:"status"` |
| 169 ErrorMessage string `json:"error_message"` | 157 ErrorMessage string `json:"error_message"` |
| 170 } | 158 } |
| 171 err = r.makeRequest("cas/v1/finalize/"+sessionID, "POST", nil, &reply) | 159 err = r.makeRequest("cas/v1/finalize/"+sessionID, "POST", nil, &reply) |
| 172 if err != nil { | 160 if err != nil { |
| 173 return | 161 return |
| 174 } | 162 } |
| 175 switch reply.Status { | 163 switch reply.Status { |
| 176 case "MISSING": | 164 case "MISSING": |
| 177 » » err = errors.New("Upload session is unexpectedly missing") | 165 » » err = ErrUploadSessionDied |
| 178 case "UPLOADING", "VERIFYING": | 166 case "UPLOADING", "VERIFYING": |
| 179 finished = false | 167 finished = false |
| 180 case "PUBLISHED": | 168 case "PUBLISHED": |
| 181 finished = true | 169 finished = true |
| 182 case "ERROR": | 170 case "ERROR": |
| 183 err = errors.New(reply.ErrorMessage) | 171 err = errors.New(reply.ErrorMessage) |
| 184 default: | 172 default: |
| 185 err = fmt.Errorf("Unexpected upload session status: %s", reply.S tatus) | 173 err = fmt.Errorf("Unexpected upload session status: %s", reply.S tatus) |
| 186 } | 174 } |
| 187 return | 175 return |
| 188 } | 176 } |
| 189 | 177 |
| 190 func (r *remoteService) registerInstance(packageName, instanceID string) (*regis terInstanceResponse, error) { | 178 func (r *remoteImpl) registerInstance(pin common.Pin) (*registerInstanceResponse , error) { |
| 191 » endpoint, err := instanceEndpoint(packageName, instanceID) | 179 » endpoint, err := instanceEndpoint(pin) |
| 192 if err != nil { | 180 if err != nil { |
| 193 return nil, err | 181 return nil, err |
| 194 } | 182 } |
| 195 var reply struct { | 183 var reply struct { |
| 196 Status string `json:"status"` | 184 Status string `json:"status"` |
| 197 Instance packageInstanceMsg `json:"instance"` | 185 Instance packageInstanceMsg `json:"instance"` |
| 198 UploadSessionID string `json:"upload_session_id"` | 186 UploadSessionID string `json:"upload_session_id"` |
| 199 UploadURL string `json:"upload_url"` | 187 UploadURL string `json:"upload_url"` |
| 200 ErrorMessage string `json:"error_message"` | 188 ErrorMessage string `json:"error_message"` |
| 201 } | 189 } |
| 202 err = r.makeRequest(endpoint, "POST", nil, &reply) | 190 err = r.makeRequest(endpoint, "POST", nil, &reply) |
| 203 if err != nil { | 191 if err != nil { |
| 204 return nil, err | 192 return nil, err |
| 205 } | 193 } |
| 206 switch reply.Status { | 194 switch reply.Status { |
| 207 case "REGISTERED", "ALREADY_REGISTERED": | 195 case "REGISTERED", "ALREADY_REGISTERED": |
| 208 » » info, err := makePackageInstanceInfo(reply.Instance) | 196 » » ts, err := convertTimestamp(reply.Instance.RegisteredTs) |
| 209 if err != nil { | 197 if err != nil { |
| 210 return nil, err | 198 return nil, err |
| 211 } | 199 } |
| 212 return ®isterInstanceResponse{ | 200 return ®isterInstanceResponse{ |
| 213 » » » AlreadyRegistered: reply.Status == "ALREADY_REGISTERED", | 201 » » » alreadyRegistered: reply.Status == "ALREADY_REGISTERED", |
| 214 » » » Info: info, | 202 » » » registeredBy: reply.Instance.RegisteredBy, |
| 203 » » » registeredTs: ts, | |
| 215 }, nil | 204 }, nil |
| 216 case "UPLOAD_FIRST": | 205 case "UPLOAD_FIRST": |
| 217 if reply.UploadSessionID == "" { | 206 if reply.UploadSessionID == "" { |
| 218 » » » return nil, errors.New("Server didn't provide upload ses sion ID") | 207 » » » return nil, ErrNoUploadSessionID |
| 219 } | 208 } |
| 220 return ®isterInstanceResponse{ | 209 return ®isterInstanceResponse{ |
| 221 » » » UploadSession: &uploadSession{ | 210 » » » uploadSession: &UploadSession{reply.UploadSessionID, rep ly.UploadURL}, |
| 222 » » » » ID: reply.UploadSessionID, | |
| 223 » » » » URL: reply.UploadURL, | |
| 224 » » » }, | |
| 225 }, nil | 211 }, nil |
| 226 case "ERROR": | 212 case "ERROR": |
| 227 return nil, errors.New(reply.ErrorMessage) | 213 return nil, errors.New(reply.ErrorMessage) |
| 228 } | 214 } |
| 229 return nil, fmt.Errorf("Unexpected register package status: %s", reply.S tatus) | 215 return nil, fmt.Errorf("Unexpected register package status: %s", reply.S tatus) |
| 230 } | 216 } |
| 231 | 217 |
| 232 func (r *remoteService) fetchInstance(packageName, instanceID string) (*fetchIns tanceResponse, error) { | 218 func (r *remoteImpl) fetchInstance(pin common.Pin) (*fetchInstanceResponse, erro r) { |
| 233 » endpoint, err := instanceEndpoint(packageName, instanceID) | 219 » endpoint, err := instanceEndpoint(pin) |
| 234 if err != nil { | 220 if err != nil { |
| 235 return nil, err | 221 return nil, err |
| 236 } | 222 } |
| 237 var reply struct { | 223 var reply struct { |
| 238 Status string `json:"status"` | 224 Status string `json:"status"` |
| 239 Instance packageInstanceMsg `json:"instance"` | 225 Instance packageInstanceMsg `json:"instance"` |
| 240 FetchURL string `json:"fetch_url"` | 226 FetchURL string `json:"fetch_url"` |
| 241 ErrorMessage string `json:"error_message"` | 227 ErrorMessage string `json:"error_message"` |
| 242 } | 228 } |
| 243 err = r.makeRequest(endpoint, "GET", nil, &reply) | 229 err = r.makeRequest(endpoint, "GET", nil, &reply) |
| 244 if err != nil { | 230 if err != nil { |
| 245 return nil, err | 231 return nil, err |
| 246 } | 232 } |
| 247 switch reply.Status { | 233 switch reply.Status { |
| 248 case "SUCCESS": | 234 case "SUCCESS": |
| 249 » » info, err := makePackageInstanceInfo(reply.Instance) | 235 » » ts, err := convertTimestamp(reply.Instance.RegisteredTs) |
| 250 if err != nil { | 236 if err != nil { |
| 251 return nil, err | 237 return nil, err |
| 252 } | 238 } |
| 253 return &fetchInstanceResponse{ | 239 return &fetchInstanceResponse{ |
| 254 » » » FetchURL: reply.FetchURL, | 240 » » » fetchURL: reply.FetchURL, |
| 255 » » » Info: info, | 241 » » » registeredBy: reply.Instance.RegisteredBy, |
| 242 » » » registeredTs: ts, | |
| 256 }, nil | 243 }, nil |
| 257 case "PACKAGE_NOT_FOUND": | 244 case "PACKAGE_NOT_FOUND": |
| 258 » » return nil, fmt.Errorf("Package '%s' is not registered or you do not have permission to fetch it", packageName) | 245 » » 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": | 246 case "INSTANCE_NOT_FOUND": |
| 260 » » return nil, fmt.Errorf("Package '%s' doesn't have instance '%s'" , packageName, instanceID) | 247 » » return nil, fmt.Errorf("Package '%s' doesn't have instance '%s'" , pin.PackageName, pin.InstanceID) |
| 261 case "ERROR": | 248 case "ERROR": |
| 262 return nil, errors.New(reply.ErrorMessage) | 249 return nil, errors.New(reply.ErrorMessage) |
| 263 } | 250 } |
| 264 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) | 251 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) |
| 265 } | 252 } |
| 266 | 253 |
| 267 func (r *remoteService) fetchACL(packagePath string) ([]PackageACL, error) { | 254 func (r *remoteImpl) fetchACL(packagePath string) ([]PackageACL, error) { |
| 268 endpoint, err := aclEndpoint(packagePath) | 255 endpoint, err := aclEndpoint(packagePath) |
| 269 if err != nil { | 256 if err != nil { |
| 270 return nil, err | 257 return nil, err |
| 271 } | 258 } |
| 272 var reply struct { | 259 var reply struct { |
| 273 Status string `json:"status"` | 260 Status string `json:"status"` |
| 274 ErrorMessage string `json:"error_message"` | 261 ErrorMessage string `json:"error_message"` |
| 275 Acls struct { | 262 Acls struct { |
| 276 Acls []struct { | 263 Acls []struct { |
| 277 PackagePath string `json:"package_path"` | 264 PackagePath string `json:"package_path"` |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 302 ModifiedTs: ts, | 289 ModifiedTs: ts, |
| 303 }) | 290 }) |
| 304 } | 291 } |
| 305 return out, nil | 292 return out, nil |
| 306 case "ERROR": | 293 case "ERROR": |
| 307 return nil, errors.New(reply.ErrorMessage) | 294 return nil, errors.New(reply.ErrorMessage) |
| 308 } | 295 } |
| 309 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) | 296 return nil, fmt.Errorf("Unexpected reply status: %s", reply.Status) |
| 310 } | 297 } |
| 311 | 298 |
| 312 func (r *remoteService) modifyACL(packagePath string, changes []PackageACLChange ) error { | 299 func (r *remoteImpl) modifyACL(packagePath string, changes []PackageACLChange) e rror { |
| 313 endpoint, err := aclEndpoint(packagePath) | 300 endpoint, err := aclEndpoint(packagePath) |
| 314 if err != nil { | 301 if err != nil { |
| 315 return err | 302 return err |
| 316 } | 303 } |
| 317 var request struct { | 304 var request struct { |
| 318 Changes []roleChangeMsg `json:"changes"` | 305 Changes []roleChangeMsg `json:"changes"` |
| 319 } | 306 } |
| 320 for _, c := range changes { | 307 for _, c := range changes { |
| 321 action := "" | 308 action := "" |
| 322 if c.Action == GrantRole { | 309 if c.Action == GrantRole { |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 342 } | 329 } |
| 343 switch reply.Status { | 330 switch reply.Status { |
| 344 case "SUCCESS": | 331 case "SUCCESS": |
| 345 return nil | 332 return nil |
| 346 case "ERROR": | 333 case "ERROR": |
| 347 return errors.New(reply.ErrorMessage) | 334 return errors.New(reply.ErrorMessage) |
| 348 } | 335 } |
| 349 return fmt.Errorf("Unexpected reply status: %s", reply.Status) | 336 return fmt.Errorf("Unexpected reply status: %s", reply.Status) |
| 350 } | 337 } |
| 351 | 338 |
| 352 func (r *remoteService) attachTags(packageName, instanceID string, tags []string ) error { | 339 func (r *remoteImpl) attachTags(pin common.Pin, tags []string) error { |
| 353 // Tags will be passed in the request body, not via URL. | 340 // Tags will be passed in the request body, not via URL. |
| 354 » endpoint, err := tagsEndpoint(packageName, instanceID, nil) | 341 » endpoint, err := tagsEndpoint(pin, nil) |
| 355 if err != nil { | 342 if err != nil { |
| 356 return err | 343 return err |
| 357 } | 344 } |
| 358 | |
| 359 if len(tags) == 0 { | |
| 360 return errors.New("At least one tag must be provided") | |
| 361 } | |
| 362 for _, tag := range tags { | 345 for _, tag := range tags { |
| 363 » » err = ValidateInstanceTag(tag) | 346 » » err = common.ValidateInstanceTag(tag) |
| 364 if err != nil { | 347 if err != nil { |
| 365 return err | 348 return err |
| 366 } | 349 } |
| 367 } | 350 } |
| 351 | |
| 368 var request struct { | 352 var request struct { |
| 369 Tags []string `json:"tags"` | 353 Tags []string `json:"tags"` |
| 370 } | 354 } |
| 371 request.Tags = tags | 355 request.Tags = tags |
| 372 | 356 |
| 373 var reply struct { | 357 var reply struct { |
| 374 Status string `json:"status"` | 358 Status string `json:"status"` |
| 375 ErrorMessage string `json:"error_message"` | 359 ErrorMessage string `json:"error_message"` |
| 376 } | 360 } |
| 377 err = r.makeRequest(endpoint, "POST", &request, &reply) | 361 err = r.makeRequest(endpoint, "POST", &request, &reply) |
| 378 if err != nil { | 362 if err != nil { |
| 379 return err | 363 return err |
| 380 } | 364 } |
| 381 switch reply.Status { | 365 switch reply.Status { |
| 382 case "SUCCESS": | 366 case "SUCCESS": |
| 383 return nil | 367 return nil |
| 384 case "PROCESSING_NOT_FINISHED_YET": | 368 case "PROCESSING_NOT_FINISHED_YET": |
| 385 return &pendingProcessingError{reply.ErrorMessage} | 369 return &pendingProcessingError{reply.ErrorMessage} |
| 386 case "ERROR", "PROCESSING_FAILED": | 370 case "ERROR", "PROCESSING_FAILED": |
| 387 return errors.New(reply.ErrorMessage) | 371 return errors.New(reply.ErrorMessage) |
| 388 } | 372 } |
| 389 return fmt.Errorf("Unexpected status when attaching tags: %s", reply.Sta tus) | 373 return fmt.Errorf("Unexpected status when attaching tags: %s", reply.Sta tus) |
| 390 } | 374 } |
| 391 | 375 |
| 392 //////////////////////////////////////////////////////////////////////////////// | 376 //////////////////////////////////////////////////////////////////////////////// |
| 393 | 377 |
| 394 func instanceEndpoint(packageName, instanceID string) (string, error) { | 378 func instanceEndpoint(pin common.Pin) (string, error) { |
| 395 » err := ValidatePackageName(packageName) | 379 » err := common.ValidatePin(pin) |
| 396 » if err != nil { | |
| 397 » » return "", err | |
| 398 » } | |
| 399 » err = ValidateInstanceID(instanceID) | |
| 400 if err != nil { | 380 if err != nil { |
| 401 return "", err | 381 return "", err |
| 402 } | 382 } |
| 403 params := url.Values{} | 383 params := url.Values{} |
| 404 » params.Add("package_name", packageName) | 384 » params.Add("package_name", pin.PackageName) |
| 405 » params.Add("instance_id", instanceID) | 385 » params.Add("instance_id", pin.InstanceID) |
| 406 return "repo/v1/instance?" + params.Encode(), nil | 386 return "repo/v1/instance?" + params.Encode(), nil |
| 407 } | 387 } |
| 408 | 388 |
| 409 func aclEndpoint(packagePath string) (string, error) { | 389 func aclEndpoint(packagePath string) (string, error) { |
| 410 » err := ValidatePackageName(packagePath) | 390 » err := common.ValidatePackageName(packagePath) |
| 411 if err != nil { | 391 if err != nil { |
| 412 return "", err | 392 return "", err |
| 413 } | 393 } |
| 414 params := url.Values{} | 394 params := url.Values{} |
| 415 params.Add("package_path", packagePath) | 395 params.Add("package_path", packagePath) |
| 416 return "repo/v1/acl?" + params.Encode(), nil | 396 return "repo/v1/acl?" + params.Encode(), nil |
| 417 } | 397 } |
| 418 | 398 |
| 419 func tagsEndpoint(packageName, instanceID string, tags []string) (string, error) { | 399 func tagsEndpoint(pin common.Pin, tags []string) (string, error) { |
| 420 » err := ValidatePackageName(packageName) | 400 » err := common.ValidatePin(pin) |
| 421 » if err != nil { | |
| 422 » » return "", err | |
| 423 » } | |
| 424 » err = ValidateInstanceID(instanceID) | |
| 425 if err != nil { | 401 if err != nil { |
| 426 return "", err | 402 return "", err |
| 427 } | 403 } |
| 428 for _, tag := range tags { | 404 for _, tag := range tags { |
| 429 » » err = ValidateInstanceTag(tag) | 405 » » err = common.ValidateInstanceTag(tag) |
| 430 if err != nil { | 406 if err != nil { |
| 431 return "", err | 407 return "", err |
| 432 } | 408 } |
| 433 } | 409 } |
| 434 params := url.Values{} | 410 params := url.Values{} |
| 435 » params.Add("package_name", packageName) | 411 » params.Add("package_name", pin.PackageName) |
| 436 » params.Add("instance_id", instanceID) | 412 » params.Add("instance_id", pin.InstanceID) |
| 437 for _, tag := range tags { | 413 for _, tag := range tags { |
| 438 params.Add("tag", tag) | 414 params.Add("tag", tag) |
| 439 } | 415 } |
| 440 return "repo/v1/tags?" + params.Encode(), nil | 416 return "repo/v1/tags?" + params.Encode(), nil |
| 441 } | 417 } |
| 442 | 418 |
| 443 // convertTimestamp coverts string with int64 timestamp in microseconds since | 419 // convertTimestamp coverts string with int64 timestamp in microseconds since |
| 444 // to time.Time | 420 // to time.Time |
| 445 func convertTimestamp(ts string) (time.Time, error) { | 421 func convertTimestamp(ts string) (time.Time, error) { |
| 446 i, err := strconv.ParseInt(ts, 10, 64) | 422 i, err := strconv.ParseInt(ts, 10, 64) |
| 447 if err != nil { | 423 if err != nil { |
| 448 return time.Time{}, fmt.Errorf("Unexpected timestamp value '%s' in the server response", ts) | 424 return time.Time{}, fmt.Errorf("Unexpected timestamp value '%s' in the server response", ts) |
| 449 } | 425 } |
| 450 return time.Unix(0, i*1000), nil | 426 return time.Unix(0, i*1000), nil |
| 451 } | 427 } |
| 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 |