1 // Copyright 2014 Google Inc. LiveAndArchived Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package storage 16 17 import ( 18 "fmt" 19 "net/http" 20 "reflect" 21 "time" 22 23 "cloud.google.com/go/internal/optional" 24 "golang.org/x/net/context" 25 "google.golang.org/api/googleapi" 26 "google.golang.org/api/iterator" 27 raw "google.golang.org/api/storage/v1" 28 ) 29 30 // BucketHandle provides operations on a Google Cloud Storage bucket. 31 // Use Client.Bucket to get a handle. 32 type BucketHandle struct { 33 c *Client 34 name string 35 acl ACLHandle 36 defaultObjectACL ACLHandle 37 conds *BucketConditions 38 userProject string // project for requester-pays buckets 39 } 40 41 // Bucket returns a BucketHandle, which provides operations on the named bucket. 42 // This call does not perform any network operations. 43 // 44 // The supplied name must contain only lowercase letters, numbers, dashes, 45 // underscores, and dots. The full specification for valid bucket names can be 46 // found at: 47 // https://cloud.google.com/storage/docs/bucket-naming 48 func (c *Client) Bucket(name string) *BucketHandle { 49 return &BucketHandle{ 50 c: c, 51 name: name, 52 acl: ACLHandle{ 53 c: c, 54 bucket: name, 55 }, 56 defaultObjectACL: ACLHandle{ 57 c: c, 58 bucket: name, 59 isDefault: true, 60 }, 61 } 62 } 63 64 // Create creates the Bucket in the project. 65 // If attrs is nil the API defaults will be used. 66 func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) error { 67 var bkt *raw.Bucket 68 if attrs != nil { 69 bkt = attrs.toRawBucket() 70 } else { 71 bkt = &raw.Bucket{} 72 } 73 bkt.Name = b.name 74 // If there is lifecycle information but no location, explicitly set 75 // the location. This is a GCS quirk/bug. 76 if bkt.Location == "" && bkt.Lifecycle != nil { 77 bkt.Location = "US" 78 } 79 req := b.c.raw.Buckets.Insert(projectID, bkt) 80 setClientHeader(req.Header()) 81 return runWithRetry(ctx, func() error { _, err := req.Context(ctx).Do(); return err }) 82 } 83 84 // Delete deletes the Bucket. 85 func (b *BucketHandle) Delete(ctx context.Context) error { 86 req, err := b.newDeleteCall() 87 if err != nil { 88 return err 89 } 90 return runWithRetry(ctx, func() error { return req.Context(ctx).Do() }) 91 } 92 93 func (b *BucketHandle) newDeleteCall() (*raw.BucketsDeleteCall, error) { 94 req := b.c.raw.Buckets.Delete(b.name) 95 setClientHeader(req.Header()) 96 if err := applyBucketConds("BucketHandle.Delete", b.conds, req); err != nil { 97 return nil, err 98 } 99 if b.userProject != "" { 100 req.UserProject(b.userProject) 101 } 102 return req, nil 103 } 104 105 // ACL returns an ACLHandle, which provides access to the bucket's access control list. 106 // This controls who can list, create or overwrite the objects in a bucket. 107 // This call does not perform any network operations. 108 func (b *BucketHandle) ACL() *ACLHandle { 109 return &b.acl 110 } 111 112 // DefaultObjectACL returns an ACLHandle, which provides access to the bucket's default object ACLs. 113 // These ACLs are applied to newly created objects in this bucket that do not have a defined ACL. 114 // This call does not perform any network operations. 115 func (b *BucketHandle) DefaultObjectACL() *ACLHandle { 116 return &b.defaultObjectACL 117 } 118 119 // Object returns an ObjectHandle, which provides operations on the named object. 120 // This call does not perform any network operations. 121 // 122 // name must consist entirely of valid UTF-8-encoded runes. The full specification 123 // for valid object names can be found at: 124 // https://cloud.google.com/storage/docs/bucket-naming 125 func (b *BucketHandle) Object(name string) *ObjectHandle { 126 return &ObjectHandle{ 127 c: b.c, 128 bucket: b.name, 129 object: name, 130 acl: ACLHandle{ 131 c: b.c, 132 bucket: b.name, 133 object: name, 134 userProject: b.userProject, 135 }, 136 gen: -1, 137 userProject: b.userProject, 138 } 139 } 140 141 // Attrs returns the metadata for the bucket. 142 func (b *BucketHandle) Attrs(ctx context.Context) (*BucketAttrs, error) { 143 req, err := b.newGetCall() 144 if err != nil { 145 return nil, err 146 } 147 var resp *raw.Bucket 148 err = runWithRetry(ctx, func() error { 149 resp, err = req.Context(ctx).Do() 150 return err 151 }) 152 if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { 153 return nil, ErrBucketNotExist 154 } 155 if err != nil { 156 return nil, err 157 } 158 return newBucket(resp), nil 159 } 160 161 func (b *BucketHandle) newGetCall() (*raw.BucketsGetCall, error) { 162 req := b.c.raw.Buckets.Get(b.name).Projection("full") 163 setClientHeader(req.Header()) 164 if err := applyBucketConds("BucketHandle.Attrs", b.conds, req); err != nil { 165 return nil, err 166 } 167 if b.userProject != "" { 168 req.UserProject(b.userProject) 169 } 170 return req, nil 171 } 172 173 func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (*BucketAttrs, error) { 174 req, err := b.newPatchCall(&uattrs) 175 if err != nil { 176 return nil, err 177 } 178 // TODO(jba): retry iff metagen is set? 179 rb, err := req.Context(ctx).Do() 180 if err != nil { 181 return nil, err 182 } 183 return newBucket(rb), nil 184 } 185 186 func (b *BucketHandle) newPatchCall(uattrs *BucketAttrsToUpdate) (*raw.BucketsPatchCall, error) { 187 rb := uattrs.toRawBucket() 188 req := b.c.raw.Buckets.Patch(b.name, rb).Projection("full") 189 setClientHeader(req.Header()) 190 if err := applyBucketConds("BucketHandle.Update", b.conds, req); err != nil { 191 return nil, err 192 } 193 if b.userProject != "" { 194 req.UserProject(b.userProject) 195 } 196 return req, nil 197 } 198 199 // BucketAttrs represents the metadata for a Google Cloud Storage bucket. 200 type BucketAttrs struct { 201 // Name is the name of the bucket. 202 Name string 203 204 // ACL is the list of access control rules on the bucket. 205 ACL []ACLRule 206 207 // DefaultObjectACL is the list of access controls to 208 // apply to new objects when no object ACL is provided. 209 DefaultObjectACL []ACLRule 210 211 // Location is the location of the bucket. It defaults to "US". 212 Location string 213 214 // MetaGeneration is the metadata generation of the bucket. 215 MetaGeneration int64 216 217 // StorageClass is the default storage class of the bucket. This defines 218 // how objects in the bucket are stored and determines the SLA 219 // and the cost of storage. Typical values are "MULTI_REGIONAL", 220 // "REGIONAL", "NEARLINE", "COLDLINE", "STANDARD" and 221 // "DURABLE_REDUCED_AVAILABILITY". Defaults to "STANDARD", which 222 // is equivalent to "MULTI_REGIONAL" or "REGIONAL" depending on 223 // the bucket's location settings. 224 StorageClass string 225 226 // Created is the creation time of the bucket. 227 Created time.Time 228 229 // VersioningEnabled reports whether this bucket has versioning enabled. 230 // This field is read-only. 231 VersioningEnabled bool 232 233 // Labels are the bucket's labels. 234 Labels map[string]string 235 236 // RequesterPays reports whether the bucket is a Requester Pays bucket. 237 RequesterPays bool 238 // Lifecycle is the lifecycle configuration for objects in the bucket. 239 Lifecycle Lifecycle 240 } 241 242 // Lifecycle is the lifecycle configuration for objects in the bucket. 243 type Lifecycle struct { 244 Rules []LifecycleRule 245 } 246 247 const ( 248 // RFC3339 date with only the date segment, used for CreatedBefore in LifecycleRule. 249 rfc3339Date = "2006-01-02" 250 251 // DeleteAction is a lifecycle action that deletes a live and/or archived 252 // objects. Takes precendence over SetStorageClass actions. 253 DeleteAction = "Delete" 254 255 // SetStorageClassAction changes the storage class of live and/or archived 256 // objects. 257 SetStorageClassAction = "SetStorageClass" 258 ) 259 260 // LifecycleRule is a lifecycle configuration rule. 261 // 262 // When all the configured conditions are met by an object in the bucket, the 263 // configured action will automatically be taken on that object. 264 type LifecycleRule struct { 265 // Action is the action to take when all of the associated conditions are 266 // met. 267 Action LifecycleAction 268 269 // Condition is the set of conditions that must be met for the associated 270 // action to be taken. 271 Condition LifecycleCondition 272 } 273 274 // LifecycleAction is a lifecycle configuration action. 275 type LifecycleAction struct { 276 // Type is the type of action to take on matching objects. 277 // 278 // Acceptable values are "Delete" to delete matching objects and 279 // "SetStorageClass" to set the storage class defined in StorageClass on 280 // matching objects. 281 Type string 282 283 // StorageClass is the storage class to set on matching objects if the Action 284 // is "SetStorageClass". 285 StorageClass string 286 } 287 288 // Liveness specifies whether the object is live or not. 289 type Liveness int 290 291 const ( 292 // LiveAndArchived includes both live and archived objects. 293 LiveAndArchived Liveness = iota 294 // Live specifies that the object is still live. 295 Live 296 // Archived specifies that the object is archived. 297 Archived 298 ) 299 300 // LifecycleCondition is a set of conditions used to match objects and take an 301 // action automatically. 302 // 303 // All configured conditions must be met for the associated action to be taken. 304 type LifecycleCondition struct { 305 // AgeInDays is the age of the object in days. 306 AgeInDays int64 307 308 // CreatedBefore is the time the object was created. 309 // 310 // This condition is satisfied when an object is created before midnight of 311 // the specified date in UTC. 312 CreatedBefore time.Time 313 314 // Liveness specifies the object's liveness. Relevant only for versioned objects 315 Liveness Liveness 316 317 // MatchesStorageClasses is the condition matching the object's storage 318 // class. 319 // 320 // Values include "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE", 321 // "STANDARD", and "DURABLE_REDUCED_AVAILABILITY". 322 MatchesStorageClasses []string 323 324 // NumNewerVersions is the condition matching objects with a number of newer versions. 325 // 326 // If the value is N, this condition is satisfied when there are at least N 327 // versions (including the live version) newer than this version of the 328 // object. 329 NumNewerVersions int64 330 } 331 332 func newBucket(b *raw.Bucket) *BucketAttrs { 333 if b == nil { 334 return nil 335 } 336 bucket := &BucketAttrs{ 337 Name: b.Name, 338 Location: b.Location, 339 MetaGeneration: b.Metageneration, 340 StorageClass: b.StorageClass, 341 Created: convertTime(b.TimeCreated), 342 VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled, 343 Labels: b.Labels, 344 RequesterPays: b.Billing != nil && b.Billing.RequesterPays, 345 Lifecycle: toLifecycle(b.Lifecycle), 346 } 347 acl := make([]ACLRule, len(b.Acl)) 348 for i, rule := range b.Acl { 349 acl[i] = ACLRule{ 350 Entity: ACLEntity(rule.Entity), 351 Role: ACLRole(rule.Role), 352 } 353 } 354 bucket.ACL = acl 355 objACL := make([]ACLRule, len(b.DefaultObjectAcl)) 356 for i, rule := range b.DefaultObjectAcl { 357 objACL[i] = ACLRule{ 358 Entity: ACLEntity(rule.Entity), 359 Role: ACLRole(rule.Role), 360 } 361 } 362 bucket.DefaultObjectACL = objACL 363 return bucket 364 } 365 366 // toRawBucket copies the editable attribute from b to the raw library's Bucket type. 367 func (b *BucketAttrs) toRawBucket() *raw.Bucket { 368 var acl []*raw.BucketAccessControl 369 if len(b.ACL) > 0 { 370 acl = make([]*raw.BucketAccessControl, len(b.ACL)) 371 for i, rule := range b.ACL { 372 acl[i] = &raw.BucketAccessControl{ 373 Entity: string(rule.Entity), 374 Role: string(rule.Role), 375 } 376 } 377 } 378 dACL := toRawObjectACL(b.DefaultObjectACL) 379 // Copy label map. 380 var labels map[string]string 381 if len(b.Labels) > 0 { 382 labels = make(map[string]string, len(b.Labels)) 383 for k, v := range b.Labels { 384 labels[k] = v 385 } 386 } 387 // Ignore VersioningEnabled if it is false. This is OK because 388 // we only call this method when creating a bucket, and by default 389 // new buckets have versioning off. 390 var v *raw.BucketVersioning 391 if b.VersioningEnabled { 392 v = &raw.BucketVersioning{Enabled: true} 393 } 394 var bb *raw.BucketBilling 395 if b.RequesterPays { 396 bb = &raw.BucketBilling{RequesterPays: true} 397 } 398 return &raw.Bucket{ 399 Name: b.Name, 400 DefaultObjectAcl: dACL, 401 Location: b.Location, 402 StorageClass: b.StorageClass, 403 Acl: acl, 404 Versioning: v, 405 Labels: labels, 406 Billing: bb, 407 Lifecycle: toRawLifecycle(b.Lifecycle), 408 } 409 } 410 411 type BucketAttrsToUpdate struct { 412 // VersioningEnabled, if set, updates whether the bucket uses versioning. 413 VersioningEnabled optional.Bool 414 415 // RequesterPays, if set, updates whether the bucket is a Requester Pays bucket. 416 RequesterPays optional.Bool 417 418 setLabels map[string]string 419 deleteLabels map[string]bool 420 } 421 422 // SetLabel causes a label to be added or modified when ua is used 423 // in a call to Bucket.Update. 424 func (ua *BucketAttrsToUpdate) SetLabel(name, value string) { 425 if ua.setLabels == nil { 426 ua.setLabels = map[string]string{} 427 } 428 ua.setLabels[name] = value 429 } 430 431 // DeleteLabel causes a label to be deleted when ua is used in a 432 // call to Bucket.Update. 433 func (ua *BucketAttrsToUpdate) DeleteLabel(name string) { 434 if ua.deleteLabels == nil { 435 ua.deleteLabels = map[string]bool{} 436 } 437 ua.deleteLabels[name] = true 438 } 439 440 func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket { 441 rb := &raw.Bucket{} 442 if ua.VersioningEnabled != nil { 443 rb.Versioning = &raw.BucketVersioning{ 444 Enabled: optional.ToBool(ua.VersioningEnabled), 445 ForceSendFields: []string{"Enabled"}, 446 } 447 } 448 if ua.RequesterPays != nil { 449 rb.Billing = &raw.BucketBilling{ 450 RequesterPays: optional.ToBool(ua.RequesterPays), 451 ForceSendFields: []string{"RequesterPays"}, 452 } 453 } 454 if ua.setLabels != nil || ua.deleteLabels != nil { 455 rb.Labels = map[string]string{} 456 for k, v := range ua.setLabels { 457 rb.Labels[k] = v 458 } 459 if len(rb.Labels) == 0 && len(ua.deleteLabels) > 0 { 460 rb.ForceSendFields = append(rb.ForceSendFields, "Labels") 461 } 462 for l := range ua.deleteLabels { 463 rb.NullFields = append(rb.NullFields, "Labels."+l) 464 } 465 } 466 return rb 467 } 468 469 // If returns a new BucketHandle that applies a set of preconditions. 470 // Preconditions already set on the BucketHandle are ignored. 471 // Operations on the new handle will only occur if the preconditions are 472 // satisfied. The only valid preconditions for buckets are MetagenerationMatch 473 // and MetagenerationNotMatch. 474 func (b *BucketHandle) If(conds BucketConditions) *BucketHandle { 475 b2 := *b 476 b2.conds = &conds 477 return &b2 478 } 479 480 // BucketConditions constrain bucket methods to act on specific metagenerations. 481 // 482 // The zero value is an empty set of constraints. 483 type BucketConditions struct { 484 // MetagenerationMatch specifies that the bucket must have the given 485 // metageneration for the operation to occur. 486 // If MetagenerationMatch is zero, it has no effect. 487 MetagenerationMatch int64 488 489 // MetagenerationNotMatch specifies that the bucket must not have the given 490 // metageneration for the operation to occur. 491 // If MetagenerationNotMatch is zero, it has no effect. 492 MetagenerationNotMatch int64 493 } 494 495 func (c *BucketConditions) validate(method string) error { 496 if *c == (BucketConditions{}) { 497 return fmt.Errorf("storage: %s: empty conditions", method) 498 } 499 if c.MetagenerationMatch != 0 && c.MetagenerationNotMatch != 0 { 500 return fmt.Errorf("storage: %s: multiple conditions specified for metageneration", method) 501 } 502 return nil 503 } 504 505 // UserProject returns a new BucketHandle that passes the project ID as the user 506 // project for all subsequent calls. A user project is required for all operations 507 // on requester-pays buckets. 508 func (b *BucketHandle) UserProject(projectID string) *BucketHandle { 509 b2 := *b 510 b2.userProject = projectID 511 b2.acl.userProject = projectID 512 b2.defaultObjectACL.userProject = projectID 513 return &b2 514 } 515 516 // applyBucketConds modifies the provided call using the conditions in conds. 517 // call is something that quacks like a *raw.WhateverCall. 518 func applyBucketConds(method string, conds *BucketConditions, call interface{}) error { 519 if conds == nil { 520 return nil 521 } 522 if err := conds.validate(method); err != nil { 523 return err 524 } 525 cval := reflect.ValueOf(call) 526 switch { 527 case conds.MetagenerationMatch != 0: 528 if !setConditionField(cval, "IfMetagenerationMatch", conds.MetagenerationMatch) { 529 return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", method) 530 } 531 case conds.MetagenerationNotMatch != 0: 532 if !setConditionField(cval, "IfMetagenerationNotMatch", conds.MetagenerationNotMatch) { 533 return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", method) 534 } 535 } 536 return nil 537 } 538 539 func toRawLifecycle(l Lifecycle) *raw.BucketLifecycle { 540 var rl raw.BucketLifecycle 541 if len(l.Rules) == 0 { 542 return nil 543 } 544 for _, r := range l.Rules { 545 rr := &raw.BucketLifecycleRule{ 546 Action: &raw.BucketLifecycleRuleAction{ 547 Type: r.Action.Type, 548 StorageClass: r.Action.StorageClass, 549 }, 550 Condition: &raw.BucketLifecycleRuleCondition{ 551 Age: r.Condition.AgeInDays, 552 MatchesStorageClass: r.Condition.MatchesStorageClasses, 553 NumNewerVersions: r.Condition.NumNewerVersions, 554 }, 555 } 556 557 switch r.Condition.Liveness { 558 case LiveAndArchived: 559 rr.Condition.IsLive = nil 560 case Live: 561 rr.Condition.IsLive = googleapi.Bool(true) 562 case Archived: 563 rr.Condition.IsLive = googleapi.Bool(false) 564 } 565 566 if !r.Condition.CreatedBefore.IsZero() { 567 rr.Condition.CreatedBefore = r.Condition.CreatedBefore.Format(rfc3339Date) 568 } 569 rl.Rule = append(rl.Rule, rr) 570 } 571 return &rl 572 } 573 574 func toLifecycle(rl *raw.BucketLifecycle) Lifecycle { 575 var l Lifecycle 576 if rl == nil { 577 return l 578 } 579 for _, rr := range rl.Rule { 580 r := LifecycleRule{ 581 Action: LifecycleAction{ 582 Type: rr.Action.Type, 583 StorageClass: rr.Action.StorageClass, 584 }, 585 Condition: LifecycleCondition{ 586 AgeInDays: rr.Condition.Age, 587 MatchesStorageClasses: rr.Condition.MatchesStorageClass, 588 NumNewerVersions: rr.Condition.NumNewerVersions, 589 }, 590 } 591 592 switch { 593 case rr.Condition.IsLive == nil: 594 r.Condition.Liveness = LiveAndArchived 595 case *rr.Condition.IsLive == true: 596 r.Condition.Liveness = Live 597 case *rr.Condition.IsLive == false: 598 r.Condition.Liveness = Archived 599 } 600 601 if rr.Condition.CreatedBefore != "" { 602 r.Condition.CreatedBefore, _ = time.Parse(rfc3339Date, rr.Condition.CreatedBefore) 603 } 604 } 605 return l 606 } 607 608 // Objects returns an iterator over the objects in the bucket that match the Query q. 609 // If q is nil, no filtering is done. 610 func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator { 611 it := &ObjectIterator{ 612 ctx: ctx, 613 bucket: b, 614 } 615 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 616 it.fetch, 617 func() int { return len(it.items) }, 618 func() interface{} { b := it.items; it.items = nil; return b }) 619 if q != nil { 620 it.query = *q 621 } 622 return it 623 } 624 625 // An ObjectIterator is an iterator over ObjectAttrs. 626 type ObjectIterator struct { 627 ctx context.Context 628 bucket *BucketHandle 629 query Query 630 pageInfo *iterator.PageInfo 631 nextFunc func() error 632 items []*ObjectAttrs 633 } 634 635 // PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 636 func (it *ObjectIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 637 638 // Next returns the next result. Its second return value is iterator.Done if 639 // there are no more results. Once Next returns iterator.Done, all subsequent 640 // calls will return iterator.Done. 641 // 642 // If Query.Delimiter is non-empty, some of the ObjectAttrs returned by Next will 643 // have a non-empty Prefix field, and a zero value for all other fields. These 644 // represent prefixes. 645 func (it *ObjectIterator) Next() (*ObjectAttrs, error) { 646 if err := it.nextFunc(); err != nil { 647 return nil, err 648 } 649 item := it.items[0] 650 it.items = it.items[1:] 651 return item, nil 652 } 653 654 func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error) { 655 req := it.bucket.c.raw.Objects.List(it.bucket.name) 656 setClientHeader(req.Header()) 657 req.Projection("full") 658 req.Delimiter(it.query.Delimiter) 659 req.Prefix(it.query.Prefix) 660 req.Versions(it.query.Versions) 661 req.PageToken(pageToken) 662 if it.bucket.userProject != "" { 663 req.UserProject(it.bucket.userProject) 664 } 665 if pageSize > 0 { 666 req.MaxResults(int64(pageSize)) 667 } 668 var resp *raw.Objects 669 var err error 670 err = runWithRetry(it.ctx, func() error { 671 resp, err = req.Context(it.ctx).Do() 672 return err 673 }) 674 if err != nil { 675 if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { 676 err = ErrBucketNotExist 677 } 678 return "", err 679 } 680 for _, item := range resp.Items { 681 it.items = append(it.items, newObject(item)) 682 } 683 for _, prefix := range resp.Prefixes { 684 it.items = append(it.items, &ObjectAttrs{Prefix: prefix}) 685 } 686 return resp.NextPageToken, nil 687 } 688 689 // TODO(jbd): Add storage.buckets.update. 690 691 // Buckets returns an iterator over the buckets in the project. You may 692 // optionally set the iterator's Prefix field to restrict the list to buckets 693 // whose names begin with the prefix. By default, all buckets in the project 694 // are returned. 695 func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator { 696 it := &BucketIterator{ 697 ctx: ctx, 698 client: c, 699 projectID: projectID, 700 } 701 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 702 it.fetch, 703 func() int { return len(it.buckets) }, 704 func() interface{} { b := it.buckets; it.buckets = nil; return b }) 705 return it 706 } 707 708 // A BucketIterator is an iterator over BucketAttrs. 709 type BucketIterator struct { 710 // Prefix restricts the iterator to buckets whose names begin with it. 711 Prefix string 712 713 ctx context.Context 714 client *Client 715 projectID string 716 buckets []*BucketAttrs 717 pageInfo *iterator.PageInfo 718 nextFunc func() error 719 } 720 721 // Next returns the next result. Its second return value is iterator.Done if 722 // there are no more results. Once Next returns iterator.Done, all subsequent 723 // calls will return iterator.Done. 724 func (it *BucketIterator) Next() (*BucketAttrs, error) { 725 if err := it.nextFunc(); err != nil { 726 return nil, err 727 } 728 b := it.buckets[0] 729 it.buckets = it.buckets[1:] 730 return b, nil 731 } 732 733 // PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 734 func (it *BucketIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 735 736 func (it *BucketIterator) fetch(pageSize int, pageToken string) (string, error) { 737 req := it.client.raw.Buckets.List(it.projectID) 738 setClientHeader(req.Header()) 739 req.Projection("full") 740 req.Prefix(it.Prefix) 741 req.PageToken(pageToken) 742 if pageSize > 0 { 743 req.MaxResults(int64(pageSize)) 744 } 745 var resp *raw.Buckets 746 var err error 747 err = runWithRetry(it.ctx, func() error { 748 resp, err = req.Context(it.ctx).Do() 749 return err 750 }) 751 if err != nil { 752 return "", err 753 } 754 for _, item := range resp.Items { 755 it.buckets = append(it.buckets, newBucket(item)) 756 } 757 return resp.NextPageToken, nil 758 } 759