Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(379)

Side by Side Diff: go/src/infra/tools/cipd/remote.go

Issue 1129043003: cipd: Refactor client to make it more readable. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 &registerInstanceResponse{ 200 return &registerInstanceResponse{
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 &registerInstanceResponse{ 209 return &registerInstanceResponse{
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698