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 15package storage 16 17import ( 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. 32type 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 48func (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. 66func (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. 85func (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 93func (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. 108func (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. 115func (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 125func (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. 142func (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 161func (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 173func (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 186func (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// Read-only fields are ignored by BucketHandle.Create. 201type BucketAttrs struct { 202 // Name is the name of the bucket. 203 // This field is read-only. 204 Name string 205 206 // ACL is the list of access control rules on the bucket. 207 ACL []ACLRule 208 209 // DefaultObjectACL is the list of access controls to 210 // apply to new objects when no object ACL is provided. 211 DefaultObjectACL []ACLRule 212 213 // Location is the location of the bucket. It defaults to "US". 214 Location string 215 216 // MetaGeneration is the metadata generation of the bucket. 217 // This field is read-only. 218 MetaGeneration int64 219 220 // StorageClass is the default storage class of the bucket. This defines 221 // how objects in the bucket are stored and determines the SLA 222 // and the cost of storage. Typical values are "MULTI_REGIONAL", 223 // "REGIONAL", "NEARLINE", "COLDLINE", "STANDARD" and 224 // "DURABLE_REDUCED_AVAILABILITY". Defaults to "STANDARD", which 225 // is equivalent to "MULTI_REGIONAL" or "REGIONAL" depending on 226 // the bucket's location settings. 227 StorageClass string 228 229 // Created is the creation time of the bucket. 230 // This field is read-only. 231 Created time.Time 232 233 // VersioningEnabled reports whether this bucket has versioning enabled. 234 VersioningEnabled bool 235 236 // Labels are the bucket's labels. 237 Labels map[string]string 238 239 // RequesterPays reports whether the bucket is a Requester Pays bucket. 240 // Clients performing operations on Requester Pays buckets must provide 241 // a user project (see BucketHandle.UserProject), which will be billed 242 // for the operations. 243 RequesterPays bool 244 // Lifecycle is the lifecycle configuration for objects in the bucket. 245 Lifecycle Lifecycle 246} 247 248// Lifecycle is the lifecycle configuration for objects in the bucket. 249type Lifecycle struct { 250 Rules []LifecycleRule 251} 252 253const ( 254 // RFC3339 date with only the date segment, used for CreatedBefore in LifecycleRule. 255 rfc3339Date = "2006-01-02" 256 257 // DeleteAction is a lifecycle action that deletes a live and/or archived 258 // objects. Takes precendence over SetStorageClass actions. 259 DeleteAction = "Delete" 260 261 // SetStorageClassAction changes the storage class of live and/or archived 262 // objects. 263 SetStorageClassAction = "SetStorageClass" 264) 265 266// LifecycleRule is a lifecycle configuration rule. 267// 268// When all the configured conditions are met by an object in the bucket, the 269// configured action will automatically be taken on that object. 270type LifecycleRule struct { 271 // Action is the action to take when all of the associated conditions are 272 // met. 273 Action LifecycleAction 274 275 // Condition is the set of conditions that must be met for the associated 276 // action to be taken. 277 Condition LifecycleCondition 278} 279 280// LifecycleAction is a lifecycle configuration action. 281type LifecycleAction struct { 282 // Type is the type of action to take on matching objects. 283 // 284 // Acceptable values are "Delete" to delete matching objects and 285 // "SetStorageClass" to set the storage class defined in StorageClass on 286 // matching objects. 287 Type string 288 289 // StorageClass is the storage class to set on matching objects if the Action 290 // is "SetStorageClass". 291 StorageClass string 292} 293 294// Liveness specifies whether the object is live or not. 295type Liveness int 296 297const ( 298 // LiveAndArchived includes both live and archived objects. 299 LiveAndArchived Liveness = iota 300 // Live specifies that the object is still live. 301 Live 302 // Archived specifies that the object is archived. 303 Archived 304) 305 306// LifecycleCondition is a set of conditions used to match objects and take an 307// action automatically. 308// 309// All configured conditions must be met for the associated action to be taken. 310type LifecycleCondition struct { 311 // AgeInDays is the age of the object in days. 312 AgeInDays int64 313 314 // CreatedBefore is the time the object was created. 315 // 316 // This condition is satisfied when an object is created before midnight of 317 // the specified date in UTC. 318 CreatedBefore time.Time 319 320 // Liveness specifies the object's liveness. Relevant only for versioned objects 321 Liveness Liveness 322 323 // MatchesStorageClasses is the condition matching the object's storage 324 // class. 325 // 326 // Values include "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE", 327 // "STANDARD", and "DURABLE_REDUCED_AVAILABILITY". 328 MatchesStorageClasses []string 329 330 // NumNewerVersions is the condition matching objects with a number of newer versions. 331 // 332 // If the value is N, this condition is satisfied when there are at least N 333 // versions (including the live version) newer than this version of the 334 // object. 335 NumNewerVersions int64 336} 337 338func newBucket(b *raw.Bucket) *BucketAttrs { 339 if b == nil { 340 return nil 341 } 342 bucket := &BucketAttrs{ 343 Name: b.Name, 344 Location: b.Location, 345 MetaGeneration: b.Metageneration, 346 StorageClass: b.StorageClass, 347 Created: convertTime(b.TimeCreated), 348 VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled, 349 Labels: b.Labels, 350 RequesterPays: b.Billing != nil && b.Billing.RequesterPays, 351 Lifecycle: toLifecycle(b.Lifecycle), 352 } 353 acl := make([]ACLRule, len(b.Acl)) 354 for i, rule := range b.Acl { 355 acl[i] = ACLRule{ 356 Entity: ACLEntity(rule.Entity), 357 Role: ACLRole(rule.Role), 358 } 359 } 360 bucket.ACL = acl 361 objACL := make([]ACLRule, len(b.DefaultObjectAcl)) 362 for i, rule := range b.DefaultObjectAcl { 363 objACL[i] = ACLRule{ 364 Entity: ACLEntity(rule.Entity), 365 Role: ACLRole(rule.Role), 366 } 367 } 368 bucket.DefaultObjectACL = objACL 369 return bucket 370} 371 372// toRawBucket copies the editable attribute from b to the raw library's Bucket type. 373func (b *BucketAttrs) toRawBucket() *raw.Bucket { 374 var acl []*raw.BucketAccessControl 375 if len(b.ACL) > 0 { 376 acl = make([]*raw.BucketAccessControl, len(b.ACL)) 377 for i, rule := range b.ACL { 378 acl[i] = &raw.BucketAccessControl{ 379 Entity: string(rule.Entity), 380 Role: string(rule.Role), 381 } 382 } 383 } 384 dACL := toRawObjectACL(b.DefaultObjectACL) 385 // Copy label map. 386 var labels map[string]string 387 if len(b.Labels) > 0 { 388 labels = make(map[string]string, len(b.Labels)) 389 for k, v := range b.Labels { 390 labels[k] = v 391 } 392 } 393 // Ignore VersioningEnabled if it is false. This is OK because 394 // we only call this method when creating a bucket, and by default 395 // new buckets have versioning off. 396 var v *raw.BucketVersioning 397 if b.VersioningEnabled { 398 v = &raw.BucketVersioning{Enabled: true} 399 } 400 var bb *raw.BucketBilling 401 if b.RequesterPays { 402 bb = &raw.BucketBilling{RequesterPays: true} 403 } 404 return &raw.Bucket{ 405 Name: b.Name, 406 DefaultObjectAcl: dACL, 407 Location: b.Location, 408 StorageClass: b.StorageClass, 409 Acl: acl, 410 Versioning: v, 411 Labels: labels, 412 Billing: bb, 413 Lifecycle: toRawLifecycle(b.Lifecycle), 414 } 415} 416 417type BucketAttrsToUpdate struct { 418 // VersioningEnabled, if set, updates whether the bucket uses versioning. 419 VersioningEnabled optional.Bool 420 421 // RequesterPays, if set, updates whether the bucket is a Requester Pays bucket. 422 RequesterPays optional.Bool 423 424 setLabels map[string]string 425 deleteLabels map[string]bool 426} 427 428// SetLabel causes a label to be added or modified when ua is used 429// in a call to Bucket.Update. 430func (ua *BucketAttrsToUpdate) SetLabel(name, value string) { 431 if ua.setLabels == nil { 432 ua.setLabels = map[string]string{} 433 } 434 ua.setLabels[name] = value 435} 436 437// DeleteLabel causes a label to be deleted when ua is used in a 438// call to Bucket.Update. 439func (ua *BucketAttrsToUpdate) DeleteLabel(name string) { 440 if ua.deleteLabels == nil { 441 ua.deleteLabels = map[string]bool{} 442 } 443 ua.deleteLabels[name] = true 444} 445 446func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket { 447 rb := &raw.Bucket{} 448 if ua.VersioningEnabled != nil { 449 rb.Versioning = &raw.BucketVersioning{ 450 Enabled: optional.ToBool(ua.VersioningEnabled), 451 ForceSendFields: []string{"Enabled"}, 452 } 453 } 454 if ua.RequesterPays != nil { 455 rb.Billing = &raw.BucketBilling{ 456 RequesterPays: optional.ToBool(ua.RequesterPays), 457 ForceSendFields: []string{"RequesterPays"}, 458 } 459 } 460 if ua.setLabels != nil || ua.deleteLabels != nil { 461 rb.Labels = map[string]string{} 462 for k, v := range ua.setLabels { 463 rb.Labels[k] = v 464 } 465 if len(rb.Labels) == 0 && len(ua.deleteLabels) > 0 { 466 rb.ForceSendFields = append(rb.ForceSendFields, "Labels") 467 } 468 for l := range ua.deleteLabels { 469 rb.NullFields = append(rb.NullFields, "Labels."+l) 470 } 471 } 472 return rb 473} 474 475// If returns a new BucketHandle that applies a set of preconditions. 476// Preconditions already set on the BucketHandle are ignored. 477// Operations on the new handle will only occur if the preconditions are 478// satisfied. The only valid preconditions for buckets are MetagenerationMatch 479// and MetagenerationNotMatch. 480func (b *BucketHandle) If(conds BucketConditions) *BucketHandle { 481 b2 := *b 482 b2.conds = &conds 483 return &b2 484} 485 486// BucketConditions constrain bucket methods to act on specific metagenerations. 487// 488// The zero value is an empty set of constraints. 489type BucketConditions struct { 490 // MetagenerationMatch specifies that the bucket must have the given 491 // metageneration for the operation to occur. 492 // If MetagenerationMatch is zero, it has no effect. 493 MetagenerationMatch int64 494 495 // MetagenerationNotMatch specifies that the bucket must not have the given 496 // metageneration for the operation to occur. 497 // If MetagenerationNotMatch is zero, it has no effect. 498 MetagenerationNotMatch int64 499} 500 501func (c *BucketConditions) validate(method string) error { 502 if *c == (BucketConditions{}) { 503 return fmt.Errorf("storage: %s: empty conditions", method) 504 } 505 if c.MetagenerationMatch != 0 && c.MetagenerationNotMatch != 0 { 506 return fmt.Errorf("storage: %s: multiple conditions specified for metageneration", method) 507 } 508 return nil 509} 510 511// UserProject returns a new BucketHandle that passes the project ID as the user 512// project for all subsequent calls. Calls with a user project will be billed to that 513// project rather than to the bucket's owning project. 514// 515// A user project is required for all operations on Requester Pays buckets. 516func (b *BucketHandle) UserProject(projectID string) *BucketHandle { 517 b2 := *b 518 b2.userProject = projectID 519 b2.acl.userProject = projectID 520 b2.defaultObjectACL.userProject = projectID 521 return &b2 522} 523 524// applyBucketConds modifies the provided call using the conditions in conds. 525// call is something that quacks like a *raw.WhateverCall. 526func applyBucketConds(method string, conds *BucketConditions, call interface{}) error { 527 if conds == nil { 528 return nil 529 } 530 if err := conds.validate(method); err != nil { 531 return err 532 } 533 cval := reflect.ValueOf(call) 534 switch { 535 case conds.MetagenerationMatch != 0: 536 if !setConditionField(cval, "IfMetagenerationMatch", conds.MetagenerationMatch) { 537 return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", method) 538 } 539 case conds.MetagenerationNotMatch != 0: 540 if !setConditionField(cval, "IfMetagenerationNotMatch", conds.MetagenerationNotMatch) { 541 return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", method) 542 } 543 } 544 return nil 545} 546 547func toRawLifecycle(l Lifecycle) *raw.BucketLifecycle { 548 var rl raw.BucketLifecycle 549 if len(l.Rules) == 0 { 550 return nil 551 } 552 for _, r := range l.Rules { 553 rr := &raw.BucketLifecycleRule{ 554 Action: &raw.BucketLifecycleRuleAction{ 555 Type: r.Action.Type, 556 StorageClass: r.Action.StorageClass, 557 }, 558 Condition: &raw.BucketLifecycleRuleCondition{ 559 Age: r.Condition.AgeInDays, 560 MatchesStorageClass: r.Condition.MatchesStorageClasses, 561 NumNewerVersions: r.Condition.NumNewerVersions, 562 }, 563 } 564 565 switch r.Condition.Liveness { 566 case LiveAndArchived: 567 rr.Condition.IsLive = nil 568 case Live: 569 rr.Condition.IsLive = googleapi.Bool(true) 570 case Archived: 571 rr.Condition.IsLive = googleapi.Bool(false) 572 } 573 574 if !r.Condition.CreatedBefore.IsZero() { 575 rr.Condition.CreatedBefore = r.Condition.CreatedBefore.Format(rfc3339Date) 576 } 577 rl.Rule = append(rl.Rule, rr) 578 } 579 return &rl 580} 581 582func toLifecycle(rl *raw.BucketLifecycle) Lifecycle { 583 var l Lifecycle 584 if rl == nil { 585 return l 586 } 587 for _, rr := range rl.Rule { 588 r := LifecycleRule{ 589 Action: LifecycleAction{ 590 Type: rr.Action.Type, 591 StorageClass: rr.Action.StorageClass, 592 }, 593 Condition: LifecycleCondition{ 594 AgeInDays: rr.Condition.Age, 595 MatchesStorageClasses: rr.Condition.MatchesStorageClass, 596 NumNewerVersions: rr.Condition.NumNewerVersions, 597 }, 598 } 599 600 switch { 601 case rr.Condition.IsLive == nil: 602 r.Condition.Liveness = LiveAndArchived 603 case *rr.Condition.IsLive == true: 604 r.Condition.Liveness = Live 605 case *rr.Condition.IsLive == false: 606 r.Condition.Liveness = Archived 607 } 608 609 if rr.Condition.CreatedBefore != "" { 610 r.Condition.CreatedBefore, _ = time.Parse(rfc3339Date, rr.Condition.CreatedBefore) 611 } 612 l.Rules = append(l.Rules, r) 613 } 614 return l 615} 616 617// Objects returns an iterator over the objects in the bucket that match the Query q. 618// If q is nil, no filtering is done. 619func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator { 620 it := &ObjectIterator{ 621 ctx: ctx, 622 bucket: b, 623 } 624 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 625 it.fetch, 626 func() int { return len(it.items) }, 627 func() interface{} { b := it.items; it.items = nil; return b }) 628 if q != nil { 629 it.query = *q 630 } 631 return it 632} 633 634// An ObjectIterator is an iterator over ObjectAttrs. 635type ObjectIterator struct { 636 ctx context.Context 637 bucket *BucketHandle 638 query Query 639 pageInfo *iterator.PageInfo 640 nextFunc func() error 641 items []*ObjectAttrs 642} 643 644// PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 645func (it *ObjectIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 646 647// Next returns the next result. Its second return value is iterator.Done if 648// there are no more results. Once Next returns iterator.Done, all subsequent 649// calls will return iterator.Done. 650// 651// If Query.Delimiter is non-empty, some of the ObjectAttrs returned by Next will 652// have a non-empty Prefix field, and a zero value for all other fields. These 653// represent prefixes. 654func (it *ObjectIterator) Next() (*ObjectAttrs, error) { 655 if err := it.nextFunc(); err != nil { 656 return nil, err 657 } 658 item := it.items[0] 659 it.items = it.items[1:] 660 return item, nil 661} 662 663func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error) { 664 req := it.bucket.c.raw.Objects.List(it.bucket.name) 665 setClientHeader(req.Header()) 666 req.Projection("full") 667 req.Delimiter(it.query.Delimiter) 668 req.Prefix(it.query.Prefix) 669 req.Versions(it.query.Versions) 670 req.PageToken(pageToken) 671 if it.bucket.userProject != "" { 672 req.UserProject(it.bucket.userProject) 673 } 674 if pageSize > 0 { 675 req.MaxResults(int64(pageSize)) 676 } 677 var resp *raw.Objects 678 var err error 679 err = runWithRetry(it.ctx, func() error { 680 resp, err = req.Context(it.ctx).Do() 681 return err 682 }) 683 if err != nil { 684 if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { 685 err = ErrBucketNotExist 686 } 687 return "", err 688 } 689 for _, item := range resp.Items { 690 it.items = append(it.items, newObject(item)) 691 } 692 for _, prefix := range resp.Prefixes { 693 it.items = append(it.items, &ObjectAttrs{Prefix: prefix}) 694 } 695 return resp.NextPageToken, nil 696} 697 698// TODO(jbd): Add storage.buckets.update. 699 700// Buckets returns an iterator over the buckets in the project. You may 701// optionally set the iterator's Prefix field to restrict the list to buckets 702// whose names begin with the prefix. By default, all buckets in the project 703// are returned. 704func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator { 705 it := &BucketIterator{ 706 ctx: ctx, 707 client: c, 708 projectID: projectID, 709 } 710 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 711 it.fetch, 712 func() int { return len(it.buckets) }, 713 func() interface{} { b := it.buckets; it.buckets = nil; return b }) 714 return it 715} 716 717// A BucketIterator is an iterator over BucketAttrs. 718type BucketIterator struct { 719 // Prefix restricts the iterator to buckets whose names begin with it. 720 Prefix string 721 722 ctx context.Context 723 client *Client 724 projectID string 725 buckets []*BucketAttrs 726 pageInfo *iterator.PageInfo 727 nextFunc func() error 728} 729 730// Next returns the next result. Its second return value is iterator.Done if 731// there are no more results. Once Next returns iterator.Done, all subsequent 732// calls will return iterator.Done. 733func (it *BucketIterator) Next() (*BucketAttrs, error) { 734 if err := it.nextFunc(); err != nil { 735 return nil, err 736 } 737 b := it.buckets[0] 738 it.buckets = it.buckets[1:] 739 return b, nil 740} 741 742// PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 743func (it *BucketIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 744 745func (it *BucketIterator) fetch(pageSize int, pageToken string) (string, error) { 746 req := it.client.raw.Buckets.List(it.projectID) 747 setClientHeader(req.Header()) 748 req.Projection("full") 749 req.Prefix(it.Prefix) 750 req.PageToken(pageToken) 751 if pageSize > 0 { 752 req.MaxResults(int64(pageSize)) 753 } 754 var resp *raw.Buckets 755 var err error 756 err = runWithRetry(it.ctx, func() error { 757 resp, err = req.Context(it.ctx).Do() 758 return err 759 }) 760 if err != nil { 761 return "", err 762 } 763 for _, item := range resp.Items { 764 it.buckets = append(it.buckets, newBucket(item)) 765 } 766 return resp.NextPageToken, nil 767} 768