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 |