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

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

Issue 1129043003: cipd: Refactor client to make it more readable. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: be more careful with network error handling 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
« no previous file with comments | « go/src/infra/tools/cipd/builder_test.go ('k') | go/src/infra/tools/cipd/client_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /*
6 Package cipd implements client side of Chrome Infra Package Deployer.
7
8 TODO: write more.
9
10 Binary package file format (in free form representation):
11 <binary package> := <zipped data>
12 <zipped data> := DeterministicZip(<all input files> + <manifest json>)
13 <manifest json> := File{
14 name: ".cipdpkg/manifest.json",
15 data: JSON({
16 "FormatVersion": "1",
17 "PackageName": <name of the package>
18 }),
19 }
20 DeterministicZip = zip archive with deterministic ordering of files and stripp ed timestamps
21
22 Main package data (<zipped data> above) is deterministic, meaning its content
23 depends only on inputs used to built it (byte to byte): contents and names of
24 all files added to the package (plus 'executable' file mode bit) and a package
25 name (and all other data in the manifest).
26
27 Binary package data MUST NOT depend on a timestamp, hostname of machine that
28 built it, revision of the source code it was built from, etc. All that
29 information will be distributed as a separate metadata packet associated with
30 the package when it gets uploaded to the server.
31
32 TODO: expand more when there's server-side package data model (labels
33 and stuff).
34 */
35 package cipd
36
37 import (
38 "bufio"
39 "errors"
40 "fmt"
41 "io"
42 "io/ioutil"
43 "net/http"
44 "os"
45 "path/filepath"
46 "strings"
47 "time"
48
49 "infra/libs/build"
50 "infra/libs/logging"
51
52 "infra/tools/cipd/common"
53 "infra/tools/cipd/local"
54 )
55
56 // PackageACLChangeAction defines a flavor of PackageACLChange.
57 type PackageACLChangeAction string
58
59 const (
60 // GrantRole is used in PackageACLChange to request a role to be granted .
61 GrantRole PackageACLChangeAction = "GRANT"
62 // RevokeRole is used in PackageACLChange to request a role to be revoke d.
63 RevokeRole PackageACLChangeAction = "REVOKE"
64
65 // CASFinalizationTimeout is how long to wait for CAS service to finaliz e the upload.
66 CASFinalizationTimeout = 1 * time.Minute
67 // TagAttachTimeout is how long to wait for instance to be processed whe n attaching tags.
68 TagAttachTimeout = 1 * time.Minute
69
70 // UserAgent is HTTP user agent string for CIPD client.
71 UserAgent = "cipd 1.0"
72
73 // ProdServiceURL is URL of a backend to connect to if client is build w ith +release tag.
74 ProdServiceURL = "https://chrome-infra-packages.appspot.com"
75 // TestingServiceURL is URL of a backend to connect to if client is buil d without +release tag.
76 TestingServiceURL = "https://chrome-infra-packages-dev.appspot.com"
77 )
78
79 var (
80 // ErrFinalizationTimeout is returned if CAS service can not finalize up load fast enough.
81 ErrFinalizationTimeout = errors.New("Timeout while waiting for CAS servi ce to finalize the upload")
82 // ErrBadUpload is returned when a package file is uploaded, but servers asks us to upload it again.
83 ErrBadUpload = errors.New("Package file is uploaded, but servers asks us to upload it again")
84 // ErrBadUploadSession is returned by UploadToCAS if provided UploadSess ion is not valid.
85 ErrBadUploadSession = errors.New("UploadURL must be set if UploadSession ID is used")
86 // ErrUploadSessionDied is returned by UploadToCAS if upload session sud denly disappeared.
87 ErrUploadSessionDied = errors.New("Upload session is unexpectedly missin g")
88 // ErrNoUploadSessionID is returned by UploadToCAS if server didn't prov ide upload session ID.
89 ErrNoUploadSessionID = errors.New("Server didn't provide upload session ID")
90 // ErrAttachTagsTimeout is returned when service refuses to accept tags for a long time.
91 ErrAttachTagsTimeout = errors.New("Timeout while attaching tags")
92 // ErrDownloadError is returned by FetchInstance on download errors.
93 ErrDownloadError = errors.New("Failed to download the package file after multiple attempts")
94 // ErrUploadError is returned by RegisterInstance and UploadToCAS on upl oad errors.
95 ErrUploadError = errors.New("Failed to upload the package file after mul tiple attempts")
96 // ErrAccessDenined is returned by calls talking to backend on 401 or 40 3 HTTP errors.
97 ErrAccessDenined = errors.New("Access denied (not authenticated or not e nough permissions)")
98 // ErrBackendInaccessible is returned by calls talking to backed if it d oesn't response.
99 ErrBackendInaccessible = errors.New("Request to the backend failed after multiple attempts")
nodir 2015/05/12 19:09:20 In general, it is cool to have each and every erro
Vadim Sh. 2015/05/12 23:25:03 Some errors are not constants (e.g. errors that wr
100 )
101
102 // HTTPClientFactory lazily creates http.Client to use for making requests.
103 type HTTPClientFactory func() (*http.Client, error)
104
105 // Client provides high level CIPD client interface.
nodir 2015/05/12 19:09:20 nit: high-level
Vadim Sh. 2015/05/12 23:25:03 Done.
106 type Client struct {
107 // ServiceURL is root URL of the backend service, or "" to use default s ervice.
nodir 2015/05/12 19:09:20 nit: the default service
Vadim Sh. 2015/05/12 23:25:02 It wasn't correct. Removed.
108 ServiceURL string
109 // Log is a logger to use for logs, default is logging.DefaultLogger.
110 Log logging.Logger
111 // AuthenticatedClientFactory lazily creates http.Client to use for maki ng RPC requests.
112 AuthenticatedClientFactory HTTPClientFactory
113 // AnonymousClientFactory lazily creates http.Client to use for making r equests to storage.
114 AnonymousClientFactory HTTPClientFactory
115 // UserAgent is put into User-Agent HTTP header with each request.
116 UserAgent string
117
118 // clock provides current time and ability to sleep.
119 clock clock
120 // remote knows how to call backend REST API.
121 remote remote
122 // storage knows how upload and download raw binaries using signed URLs.
nodir 2015/05/12 19:09:20 nit: how to upload
Vadim Sh. 2015/05/12 23:25:03 Done.
123 storage storage
124
125 // authClient is lazily created http.Client to use to make authenticated requests.
nodir 2015/05/12 19:09:20 revisit this comment
Vadim Sh. 2015/05/12 23:25:03 What do you not like about this comment?
nodir 2015/05/12 23:35:54 This is better
126 authClient *http.Client
127 // anonClient is lazily created http.Client to use to make anonymous req uests.
nodir 2015/05/12 19:09:20 this too
128 anonClient *http.Client
129 }
130
131 // PackageACL is per package path per role access control list that is a part of
132 // larger overall ACL: ACL for package "a/b/c" is a union of PackageACLs for "a"
133 // "a/b" and "a/b/c".
134 type PackageACL struct {
135 // PackagePath is a package subpath this ACL is defined for.
136 PackagePath string
137 // Role is a role that listed users have, e.g. 'READER', 'WRITER', ...
138 Role string
nodir 2015/05/12 19:09:20 why not type Role? since you use a special type Ac
Vadim Sh. 2015/05/12 23:25:03 Action is conceptually an enum with only two valid
nodir 2015/05/12 23:35:53 It is not anything, it is READER, WRITER, OWNER, i
Vadim Sh. 2015/05/12 23:48:45 Yes, for now. With introduction of refs there will
nodir 2015/05/13 01:25:28 IMO a role should not identify the resource. I thi
Vadim Sh. 2015/05/13 03:08:06 I'll consider this when I'll be implementing ACL f
139 // Principals list users and groups granted the role.
140 Principals []string
141 // ModifiedBy specifies who modified the list the last time.
142 ModifiedBy string
143 // ModifiedTs is a timestamp when the list was modified the last time.
144 ModifiedTs time.Time
145 }
146
147 // PackageACLChange is a mutation to some package ACL.
148 type PackageACLChange struct {
149 // Action defines what action to perform: GrantRole or RevokeRole.
150 Action PackageACLChangeAction
151 // Role to grant or revoke to a user or group.
152 Role string
153 // Principal is a user or a group to grant or revoke a role for.
154 Principal string
155 }
156
157 // UploadSession describes open CAS upload session.
158 type UploadSession struct {
159 // ID identifies upload session in the backend.
160 ID string
161 // URL is where to upload the data to.
162 URL string
163 }
164
165 // NewClient initializes default CIPD client object. Its fields can be further
166 // tweaked after this call.
167 func NewClient() *Client {
168 c := &Client{
169 ServiceURL: ProdServiceURL,
170 Log: logging.DefaultLogger,
171 AuthenticatedClientFactory: func() (*http.Client, error) { retur n http.DefaultClient, nil },
172 AnonymousClientFactory: func() (*http.Client, error) { retur n http.DefaultClient, nil },
173 UserAgent: UserAgent,
174 }
nodir 2015/05/12 19:09:20 Curious why clock is not here?
Vadim Sh. 2015/05/12 23:25:03 Just forgot
175 if !build.ReleaseBuild {
176 c.ServiceURL = TestingServiceURL
177 c.UserAgent += " testing"
178 }
179 c.clock = &clockImpl{}
180 c.remote = &remoteImpl{c}
181 c.storage = &storageImpl{c, uploadChunkSize}
182 return c
183 }
184
185 // doAuthenticatedHTTPRequest is used by remote implementation to make HTTP call s.
186 func (client *Client) doAuthenticatedHTTPRequest(req *http.Request) (*http.Respo nse, error) {
187 if client.authClient == nil {
188 var err error
189 client.authClient, err = client.AuthenticatedClientFactory()
190 if err != nil {
191 return nil, err
192 }
193 }
194 return client.authClient.Do(req)
195 }
196
197 // doAnonymousHTTPRequest is used by storage implementation to make HTTP calls.
198 func (client *Client) doAnonymousHTTPRequest(req *http.Request) (*http.Response, error) {
199 if client.anonClient == nil {
200 var err error
201 client.anonClient, err = client.AnonymousClientFactory()
202 if err != nil {
203 return nil, err
204 }
205 }
206 return client.anonClient.Do(req)
207 }
nodir 2015/05/12 19:09:20 I wonder why here and in client factories, you hav
Vadim Sh. 2015/05/12 23:25:03 I think c.doAnonymousHTTPRequest(req) is more read
208
209 // FetchACL returns a list of PackageACL objects (parent paths first) that
210 // together define access control list for given package subpath.
nodir 2015/05/12 19:09:20 the access control list for the given package subp
Vadim Sh. 2015/05/12 23:25:02 Done.
211 func (client *Client) FetchACL(packagePath string) ([]PackageACL, error) {
212 return client.remote.fetchACL(packagePath)
213 }
214
215 // ModifyACL applies a set of PackageACLChanges to a package path.
216 func (client *Client) ModifyACL(packagePath string, changes []PackageACLChange) error {
217 return client.remote.modifyACL(packagePath, changes)
218 }
219
220 // UploadToCAS uploads package data blob to Content Addressed Store if it is not
221 // there already. The data is addressed by SHA1 hash (also known as package's
222 // InstanceID). It can be used as a standalone function (if 'session' is nil)
223 // or as a part of more high level upload process (in that case upload session
224 // can be opened elsewhere and its properties passed here via 'session'
225 // argument). Returns nil on successful upload.
226 func (client *Client) UploadToCAS(SHA1 string, data io.ReadSeeker, session *Uplo adSession) error {
nodir 2015/05/12 19:09:20 why SHA1 is capital letters?
Vadim Sh. 2015/05/12 23:25:03 Done.
227 // Open new upload session if existing is not provided.
nodir 2015/05/12 19:09:19 an existing
Vadim Sh. 2015/05/12 23:25:03 Done.
228 var err error
229 if session == nil {
230 client.Log.Infof("cipd: uploading %s: initiating", SHA1)
231 session, err = client.remote.initiateUpload(SHA1)
232 if err != nil {
233 client.Log.Warningf("cipd: can't upload %s - %s", SHA1, err)
234 return err
235 }
236 if session == nil {
237 client.Log.Infof("cipd: %s is already uploaded", SHA1)
238 return nil
239 }
240 } else {
241 if session.ID == "" || session.URL == "" {
242 return ErrBadUploadSession
243 }
244 }
245
246 // Upload the file to CAS storage.
247 err = client.storage.upload(session.URL, data)
248 if err != nil {
249 return err
250 }
251
252 // Finalize the upload, wait until server verifies and publishes the fil e.
253 started := client.clock.now()
254 delay := time.Second
255 for {
256 published, err := client.remote.finalizeUpload(session.ID)
257 if published {
258 client.Log.Infof("cipd: successfully uploaded %s", SHA1)
259 return nil
260 }
261 if err != nil {
nodir 2015/05/12 19:09:19 put err check before published
Vadim Sh. 2015/05/12 23:25:02 Done.
262 client.Log.Warningf("cipd: upload of %s failed: %s", SHA 1, err)
263 return err
264 }
265 if client.clock.now().Sub(started) > CASFinalizationTimeout {
266 client.Log.Warningf("cipd: upload of %s failed: timeout" , SHA1)
267 return ErrFinalizationTimeout
268 }
269 client.Log.Infof("cipd: uploading - verifying")
270 client.clock.sleep(delay)
271 if delay < 4*time.Second {
272 delay += 500 * time.Millisecond
273 }
274 }
275 }
276
277 // RegisterInstance makes the package instance available for clients by
278 // uploading it to the storage and registering it in the package repository.
279 // 'instance' is a package instance to register.
280 func (client *Client) RegisterInstance(instance local.PackageInstance) error {
281 // Attempt to register.
282 client.Log.Infof("cipd: registering %s", instance.Pin())
283 result, err := client.remote.registerInstance(instance.Pin())
284 if err != nil {
285 return err
286 }
287
288 // Asked to upload the package file to CAS first?
289 if result.uploadSession != nil {
290 err = client.UploadToCAS(instance.Pin().InstanceID, instance.Dat aReader(), result.uploadSession)
291 if err != nil {
292 return err
293 }
294 // Try again, now that file is uploaded.
295 client.Log.Infof("cipd: registering %s", instance.Pin())
296 result, err = client.remote.registerInstance(instance.Pin())
297 if err != nil {
298 return err
299 }
300 if result.uploadSession != nil {
301 return ErrBadUpload
302 }
303 }
304
305 if result.alreadyRegistered {
306 client.Log.Infof(
307 "cipd: instance %s is already registered by %s on %s",
308 instance.Pin(), result.registeredBy, result.registeredTs )
309 } else {
310 client.Log.Infof("cipd: instance %s was successfully registered" , instance.Pin())
311 }
312
313 return nil
314 }
315
316 // AttachTagsWhenReady attaches tags to an instance retrying on "not yet process ed" responses.
317 func (client *Client) AttachTagsWhenReady(pin common.Pin, tags []string) error {
318 err := common.ValidatePin(pin)
319 if err != nil {
320 return err
321 }
322 if len(tags) == 0 {
323 return nil
324 }
325 for _, tag := range tags {
326 client.Log.Infof("cipd: attaching tag %s", tag)
327 }
328 deadline := client.clock.now().Add(TagAttachTimeout)
329 for client.clock.now().Before(deadline) {
330 err = client.remote.attachTags(pin, tags)
331 if err == nil {
332 client.Log.Infof("cipd: all tags attached")
333 return nil
334 }
335 if _, ok := err.(*pendingProcessingError); ok {
336 client.Log.Warningf("cipd: package instance is not ready yet - %s", err)
337 client.clock.sleep(5 * time.Second)
338 } else {
339 client.Log.Errorf("cipd: failed to attach tags - %s", er r)
340 return err
341 }
342 }
343 client.Log.Errorf("cipd: failed to attach tags - deadline exceeded")
344 return ErrAttachTagsTimeout
345 }
346
347 // FetchInstance downloads package instance file from the repository.
348 func (client *Client) FetchInstance(pin common.Pin, output io.WriteSeeker) error {
349 err := common.ValidatePin(pin)
350 if err != nil {
351 return err
352 }
353 client.Log.Infof("cipd: resolving fetch URL for %s", pin)
354 fetchInfo, err := client.remote.fetchInstance(pin)
355 if err == nil {
356 err = client.storage.download(fetchInfo.fetchURL, output)
357 }
358 if err != nil {
359 client.Log.Errorf("cipd: failed to fetch %s (%s)", pin, err)
nodir 2015/05/12 19:09:20 normally use use " - " between summary and error d
Vadim Sh. 2015/05/12 23:25:03 Done.
360 return err
361 }
362 client.Log.Infof("cipd: successfully fetched %s", pin)
363 return nil
364 }
365
366 // FetchAndDeployInstance fetches the package instance and deploys it into
367 // a site root. It doesn't check whether the instance is already deployed.
368 func (client *Client) FetchAndDeployInstance(root string, pin common.Pin) error {
369 err := common.ValidatePin(pin)
370 if err != nil {
371 return err
372 }
373
374 // Use temp file for storing package file. Delete it when done.
375 var instance local.PackageInstance
376 tempPath := filepath.Join(root, local.SiteServiceDir, "tmp")
377 err = os.MkdirAll(tempPath, 0777)
378 if err != nil {
379 return err
380 }
381 f, err := ioutil.TempFile(tempPath, pin.InstanceID)
382 if err != nil {
383 return err
384 }
385 defer func() {
386 // Instance takes ownership of the file, no need to close it sep arately.
387 if instance == nil {
388 f.Close()
389 }
390 os.Remove(f.Name())
391 }()
392
393 // Fetch the package data to the provided storage.
394 err = client.FetchInstance(pin, f)
395 if err != nil {
396 return err
397 }
398
399 // Open the instance, verify the instance ID.
400 instance, err = local.OpenInstance(f, pin.InstanceID)
401 if err != nil {
402 return err
403 }
404 defer instance.Close()
405
406 // Deploy it. 'defer' will take care of removing the temp file if needed .
407 _, err = local.DeployInstance(root, instance)
408 return err
409 }
410
411 // ProcessEnsureFile parses text file that describes what should be installed
412 // by EnsurePackages function. It is a text file where each line has a form:
413 // <package name> <desired version>. Whitespaces are ignored. Lines that start
414 // with '#' are ignored. Version can be specified as instance ID, tag or ref.
415 // Will resolve tags and refs to concrete instance IDs.
416 func (client *Client) ProcessEnsureFile(r io.Reader) ([]common.Pin, error) {
417 lineNo := 0
418 makeError := func(msg string) error {
419 return fmt.Errorf("Failed to parse desired state (line %d): %s", lineNo, msg)
420 }
421
422 out := []common.Pin{}
423 scanner := bufio.NewScanner(r)
424 for scanner.Scan() {
425 lineNo++
426
427 // Split each line into words, ignore white space.
428 tokens := []string{}
429 for _, chunk := range strings.Split(scanner.Text(), " ") {
430 chunk = strings.TrimSpace(chunk)
431 if chunk != "" {
432 tokens = append(tokens, chunk)
433 }
434 }
435
436 // Skip empty lines or lines starting with '#'.
437 if len(tokens) == 0 || tokens[0][0] == '#' {
438 continue
439 }
440
441 // Each line has a format "<package name> <instance id>".
nodir 2015/05/12 19:09:19 version, not instance id
Vadim Sh. 2015/05/12 23:25:03 Done.
442 if len(tokens) != 2 {
443 return nil, makeError("expecting '<package name> <instan ce id>' line")
nodir 2015/05/12 19:09:19 here too
Vadim Sh. 2015/05/12 23:25:03 Done.
444 }
445 err := common.ValidatePackageName(tokens[0])
446 if err != nil {
447 return nil, makeError(err.Error())
448 }
449 err = common.ValidateInstanceID(tokens[1])
nodir 2015/05/12 19:09:20 validate version?
Vadim Sh. 2015/05/12 23:25:02 See TODO, it is not implemented yet.
450 if err != nil {
451 return nil, makeError(err.Error())
452 }
453
454 // Good enough.
455 out = append(out, common.Pin{PackageName: tokens[0], InstanceID: tokens[1]})
456 }
457
458 // TODO(vadimsh): Resolve tags to instance IDs.
459 return out, nil
460 }
461
462 // EnsurePackages is high level interface for installation, removal and updates
nodir 2015/05/12 19:09:19 nit: a high-level
Vadim Sh. 2015/05/12 23:25:03 Done.
463 // of packages inside some installation site root. Given a description of
464 // what packages (and versions) should be installed it will do all necessary
465 // actions to bring the state of the site root to desired one.
nodir 2015/05/12 19:09:19 the desired one
Vadim Sh. 2015/05/12 23:25:03 Done.
466 func (client *Client) EnsurePackages(root string, pins []common.Pin) error {
467 // Make sure a package is specified only once.
468 seen := make(map[string]bool, len(pins))
469 for _, p := range pins {
470 if seen[p.PackageName] {
471 return fmt.Errorf("Package %s is specified twice", p.Pac kageName)
472 }
473 seen[p.PackageName] = true
474 }
475
476 // Ensure site root is a directory (or missing).
477 root, err := filepath.Abs(filepath.Clean(root))
478 if err != nil {
479 return err
480 }
481 stat, err := os.Stat(root)
482 if err == nil && !stat.IsDir() {
483 return fmt.Errorf("Path %s is not a directory", root)
484 }
485 if err != nil && !os.IsNotExist(err) {
486 return err
487 }
488 rootExists := (err == nil)
489
490 // Enumerate existing packages (only if root already exists).
491 existing := []common.Pin{}
492 if rootExists {
493 existing, err = local.FindDeployed(root)
494 if err != nil {
495 client.Log.Errorf("Failed to enumerate installed package s: %s", err)
496 return err
497 }
498 }
499
500 // Figure out what needs to be updated and deleted, log it.
501 toDeploy, toDelete := buildActionPlan(pins, existing)
502 if len(toDeploy) == 0 && len(toDelete) == 0 {
503 client.Log.Infof("Everything is up-to-date.")
504 return nil
505 }
506 if len(toDeploy) != 0 {
507 client.Log.Infof("Packages to be installed:")
508 for _, pin := range toDeploy {
509 client.Log.Infof(" %s", pin)
510 }
511 }
512 if len(toDelete) != 0 {
513 client.Log.Infof("Packages to be removed:")
514 for _, pin := range toDelete {
515 client.Log.Infof(" %s", pin)
516 }
517 }
518
519 // Create the site root directory before installing anything there.
520 if len(toDeploy) != 0 && !rootExists {
521 err = os.MkdirAll(root, 0777)
522 if err != nil {
523 return err
524 }
525 }
526
527 // Remove all unneeded stuff.
528 errors := []error{}
529 for _, pin := range toDelete {
530 err = local.RemoveDeployed(root, pin.PackageName)
531 if err != nil {
532 client.Log.Errorf("Failed to remove %s - %s", pin.Packag eName, err)
533 errors = append(errors, err)
534 }
535 }
536
537 // Install all new stuff.
538 for _, pin := range toDeploy {
539 err = client.FetchAndDeployInstance(root, pin)
540 if err != nil {
541 client.Log.Errorf("Failed to install %s - %s", pin, err)
542 errors = append(errors, err)
nodir 2015/05/12 19:09:20 Consider prepending pin info to err message before
543 }
544 }
545
546 if len(errors) == 0 {
547 client.Log.Infof("All changes applied.")
548 return nil
549 }
550 return fmt.Errorf("Some actions failed: %v", errors)
nodir 2015/05/12 19:09:20 report errors on individual lines, otherwise it wi
Vadim Sh. 2015/05/12 23:25:02 Are multiline error objects allowed? Replaced this
nodir 2015/05/12 23:35:53 This is fine. My concern was that %v with an array
551 }
552
553 ////////////////////////////////////////////////////////////////////////////////
554 // Private structs and interfaces.
555
556 type clock interface {
557 now() time.Time
558 sleep(time.Duration)
559 }
560
561 type remote interface {
562 fetchACL(packagePath string) ([]PackageACL, error)
563 modifyACL(packagePath string, changes []PackageACLChange) error
564
565 initiateUpload(sha1 string) (*UploadSession, error)
566 finalizeUpload(sessionID string) (bool, error)
567 registerInstance(pin common.Pin) (*registerInstanceResponse, error)
568
569 attachTags(pin common.Pin, tags []string) error
570 fetchInstance(pin common.Pin) (*fetchInstanceResponse, error)
571 }
572
573 type storage interface {
574 upload(url string, data io.ReadSeeker) error
575 download(url string, output io.WriteSeeker) error
576 }
577
578 type registerInstanceResponse struct {
579 uploadSession *UploadSession
580 alreadyRegistered bool
581 registeredBy string
582 registeredTs time.Time
583 }
584
585 type fetchInstanceResponse struct {
586 fetchURL string
587 registeredBy string
588 registeredTs time.Time
589 }
590
591 // Private stuff.
592
593 type clockImpl struct{}
594
595 func (c *clockImpl) now() time.Time { return time.Now() }
596 func (c *clockImpl) sleep(d time.Duration) { time.Sleep(d) }
597
598 // buildActionPlan is used by EnsurePackages to figure out what to install or re move.
599 func buildActionPlan(desired []common.Pin, existing []common.Pin) (toDeploy []co mmon.Pin, toDelete []common.Pin) {
nodir 2015/05/12 19:09:19 toDeploy, toDelete []common.Pin
Vadim Sh. 2015/05/12 23:25:02 Done.
600 // Figure out what needs to be installed or updated.
601 for _, d := range desired {
602 alreadyGood := false
603 for _, e := range existing {
604 if e.PackageName == d.PackageName {
605 alreadyGood = e.InstanceID == d.InstanceID
nodir 2015/05/12 19:09:20 build a map package name -> instanceId for `existi
Vadim Sh. 2015/05/12 23:25:03 Done because it makes code shorter. As optimizatio
nodir 2015/05/12 23:35:53 I expected it to be much more than 1-2
606 break
607 }
608 }
609 if !alreadyGood {
610 toDeploy = append(toDeploy, d)
611 }
612 }
613
614 // Figure out what needs to be removed.
615 for _, e := range existing {
616 keep := false
617 for _, d := range desired {
618 if e.PackageName == d.PackageName {
619 keep = true
620 break
621 }
622 }
623 if !keep {
624 toDelete = append(toDelete, e)
625 }
626 }
627
628 return
629 }
OLDNEW
« no previous file with comments | « go/src/infra/tools/cipd/builder_test.go ('k') | go/src/infra/tools/cipd/client_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698