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