]>
Commit | Line | Data |
---|---|---|
1 | // Copyright 2014 Google LLC | |
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 | "context" | |
19 | "fmt" | |
20 | "net/http" | |
21 | "reflect" | |
22 | "time" | |
23 | ||
24 | "cloud.google.com/go/internal/optional" | |
25 | "cloud.google.com/go/internal/trace" | |
26 | "google.golang.org/api/googleapi" | |
27 | "google.golang.org/api/iterator" | |
28 | raw "google.golang.org/api/storage/v1" | |
29 | ) | |
30 | ||
31 | // BucketHandle provides operations on a Google Cloud Storage bucket. | |
32 | // Use Client.Bucket to get a handle. | |
33 | type BucketHandle struct { | |
34 | c *Client | |
35 | name string | |
36 | acl ACLHandle | |
37 | defaultObjectACL ACLHandle | |
38 | conds *BucketConditions | |
39 | userProject string // project for Requester Pays buckets | |
40 | } | |
41 | ||
42 | // Bucket returns a BucketHandle, which provides operations on the named bucket. | |
43 | // This call does not perform any network operations. | |
44 | // | |
45 | // The supplied name must contain only lowercase letters, numbers, dashes, | |
46 | // underscores, and dots. The full specification for valid bucket names can be | |
47 | // found at: | |
48 | // https://cloud.google.com/storage/docs/bucket-naming | |
49 | func (c *Client) Bucket(name string) *BucketHandle { | |
50 | return &BucketHandle{ | |
51 | c: c, | |
52 | name: name, | |
53 | acl: ACLHandle{ | |
54 | c: c, | |
55 | bucket: name, | |
56 | }, | |
57 | defaultObjectACL: ACLHandle{ | |
58 | c: c, | |
59 | bucket: name, | |
60 | isDefault: true, | |
61 | }, | |
62 | } | |
63 | } | |
64 | ||
65 | // Create creates the Bucket in the project. | |
66 | // If attrs is nil the API defaults will be used. | |
67 | func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) (err error) { | |
68 | ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create") | |
69 | defer func() { trace.EndSpan(ctx, err) }() | |
70 | ||
71 | var bkt *raw.Bucket | |
72 | if attrs != nil { | |
73 | bkt = attrs.toRawBucket() | |
74 | } else { | |
75 | bkt = &raw.Bucket{} | |
76 | } | |
77 | bkt.Name = b.name | |
78 | // If there is lifecycle information but no location, explicitly set | |
79 | // the location. This is a GCS quirk/bug. | |
80 | if bkt.Location == "" && bkt.Lifecycle != nil { | |
81 | bkt.Location = "US" | |
82 | } | |
83 | req := b.c.raw.Buckets.Insert(projectID, bkt) | |
84 | setClientHeader(req.Header()) | |
85 | if attrs != nil && attrs.PredefinedACL != "" { | |
86 | req.PredefinedAcl(attrs.PredefinedACL) | |
87 | } | |
88 | if attrs != nil && attrs.PredefinedDefaultObjectACL != "" { | |
89 | req.PredefinedDefaultObjectAcl(attrs.PredefinedDefaultObjectACL) | |
90 | } | |
91 | return runWithRetry(ctx, func() error { _, err := req.Context(ctx).Do(); return err }) | |
92 | } | |
93 | ||
94 | // Delete deletes the Bucket. | |
95 | func (b *BucketHandle) Delete(ctx context.Context) (err error) { | |
96 | ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Delete") | |
97 | defer func() { trace.EndSpan(ctx, err) }() | |
98 | ||
99 | req, err := b.newDeleteCall() | |
100 | if err != nil { | |
101 | return err | |
102 | } | |
103 | return runWithRetry(ctx, func() error { return req.Context(ctx).Do() }) | |
104 | } | |
105 | ||
106 | func (b *BucketHandle) newDeleteCall() (*raw.BucketsDeleteCall, error) { | |
107 | req := b.c.raw.Buckets.Delete(b.name) | |
108 | setClientHeader(req.Header()) | |
109 | if err := applyBucketConds("BucketHandle.Delete", b.conds, req); err != nil { | |
110 | return nil, err | |
111 | } | |
112 | if b.userProject != "" { | |
113 | req.UserProject(b.userProject) | |
114 | } | |
115 | return req, nil | |
116 | } | |
117 | ||
118 | // ACL returns an ACLHandle, which provides access to the bucket's access control list. | |
119 | // This controls who can list, create or overwrite the objects in a bucket. | |
120 | // This call does not perform any network operations. | |
121 | func (b *BucketHandle) ACL() *ACLHandle { | |
122 | return &b.acl | |
123 | } | |
124 | ||
125 | // DefaultObjectACL returns an ACLHandle, which provides access to the bucket's default object ACLs. | |
126 | // These ACLs are applied to newly created objects in this bucket that do not have a defined ACL. | |
127 | // This call does not perform any network operations. | |
128 | func (b *BucketHandle) DefaultObjectACL() *ACLHandle { | |
129 | return &b.defaultObjectACL | |
130 | } | |
131 | ||
132 | // Object returns an ObjectHandle, which provides operations on the named object. | |
133 | // This call does not perform any network operations. | |
134 | // | |
135 | // name must consist entirely of valid UTF-8-encoded runes. The full specification | |
136 | // for valid object names can be found at: | |
137 | // https://cloud.google.com/storage/docs/bucket-naming | |
138 | func (b *BucketHandle) Object(name string) *ObjectHandle { | |
139 | return &ObjectHandle{ | |
140 | c: b.c, | |
141 | bucket: b.name, | |
142 | object: name, | |
143 | acl: ACLHandle{ | |
144 | c: b.c, | |
145 | bucket: b.name, | |
146 | object: name, | |
147 | userProject: b.userProject, | |
148 | }, | |
149 | gen: -1, | |
150 | userProject: b.userProject, | |
151 | } | |
152 | } | |
153 | ||
154 | // Attrs returns the metadata for the bucket. | |
155 | func (b *BucketHandle) Attrs(ctx context.Context) (attrs *BucketAttrs, err error) { | |
156 | ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Attrs") | |
157 | defer func() { trace.EndSpan(ctx, err) }() | |
158 | ||
159 | req, err := b.newGetCall() | |
160 | if err != nil { | |
161 | return nil, err | |
162 | } | |
163 | var resp *raw.Bucket | |
164 | err = runWithRetry(ctx, func() error { | |
165 | resp, err = req.Context(ctx).Do() | |
166 | return err | |
167 | }) | |
168 | if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { | |
169 | return nil, ErrBucketNotExist | |
170 | } | |
171 | if err != nil { | |
172 | return nil, err | |
173 | } | |
174 | return newBucket(resp) | |
175 | } | |
176 | ||
177 | func (b *BucketHandle) newGetCall() (*raw.BucketsGetCall, error) { | |
178 | req := b.c.raw.Buckets.Get(b.name).Projection("full") | |
179 | setClientHeader(req.Header()) | |
180 | if err := applyBucketConds("BucketHandle.Attrs", b.conds, req); err != nil { | |
181 | return nil, err | |
182 | } | |
183 | if b.userProject != "" { | |
184 | req.UserProject(b.userProject) | |
185 | } | |
186 | return req, nil | |
187 | } | |
188 | ||
189 | // Update updates a bucket's attributes. | |
190 | func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (attrs *BucketAttrs, err error) { | |
191 | ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create") | |
192 | defer func() { trace.EndSpan(ctx, err) }() | |
193 | ||
194 | req, err := b.newPatchCall(&uattrs) | |
195 | if err != nil { | |
196 | return nil, err | |
197 | } | |
198 | if uattrs.PredefinedACL != "" { | |
199 | req.PredefinedAcl(uattrs.PredefinedACL) | |
200 | } | |
201 | if uattrs.PredefinedDefaultObjectACL != "" { | |
202 | req.PredefinedDefaultObjectAcl(uattrs.PredefinedDefaultObjectACL) | |
203 | } | |
204 | // TODO(jba): retry iff metagen is set? | |
205 | rb, err := req.Context(ctx).Do() | |
206 | if err != nil { | |
207 | return nil, err | |
208 | } | |
209 | return newBucket(rb) | |
210 | } | |
211 | ||
212 | func (b *BucketHandle) newPatchCall(uattrs *BucketAttrsToUpdate) (*raw.BucketsPatchCall, error) { | |
213 | rb := uattrs.toRawBucket() | |
214 | req := b.c.raw.Buckets.Patch(b.name, rb).Projection("full") | |
215 | setClientHeader(req.Header()) | |
216 | if err := applyBucketConds("BucketHandle.Update", b.conds, req); err != nil { | |
217 | return nil, err | |
218 | } | |
219 | if b.userProject != "" { | |
220 | req.UserProject(b.userProject) | |
221 | } | |
222 | return req, nil | |
223 | } | |
224 | ||
225 | // BucketAttrs represents the metadata for a Google Cloud Storage bucket. | |
226 | // Read-only fields are ignored by BucketHandle.Create. | |
227 | type BucketAttrs struct { | |
228 | // Name is the name of the bucket. | |
229 | // This field is read-only. | |
230 | Name string | |
231 | ||
232 | // ACL is the list of access control rules on the bucket. | |
233 | ACL []ACLRule | |
234 | ||
235 | // BucketPolicyOnly configures access checks to use only bucket-level IAM | |
236 | // policies. | |
237 | BucketPolicyOnly BucketPolicyOnly | |
238 | ||
239 | // DefaultObjectACL is the list of access controls to | |
240 | // apply to new objects when no object ACL is provided. | |
241 | DefaultObjectACL []ACLRule | |
242 | ||
243 | // DefaultEventBasedHold is the default value for event-based hold on | |
244 | // newly created objects in this bucket. It defaults to false. | |
245 | DefaultEventBasedHold bool | |
246 | ||
247 | // If not empty, applies a predefined set of access controls. It should be set | |
248 | // only when creating a bucket. | |
249 | // It is always empty for BucketAttrs returned from the service. | |
250 | // See https://cloud.google.com/storage/docs/json_api/v1/buckets/insert | |
251 | // for valid values. | |
252 | PredefinedACL string | |
253 | ||
254 | // If not empty, applies a predefined set of default object access controls. | |
255 | // It should be set only when creating a bucket. | |
256 | // It is always empty for BucketAttrs returned from the service. | |
257 | // See https://cloud.google.com/storage/docs/json_api/v1/buckets/insert | |
258 | // for valid values. | |
259 | PredefinedDefaultObjectACL string | |
260 | ||
261 | // Location is the location of the bucket. It defaults to "US". | |
262 | Location string | |
263 | ||
264 | // MetaGeneration is the metadata generation of the bucket. | |
265 | // This field is read-only. | |
266 | MetaGeneration int64 | |
267 | ||
268 | // StorageClass is the default storage class of the bucket. This defines | |
269 | // how objects in the bucket are stored and determines the SLA | |
270 | // and the cost of storage. Typical values are "MULTI_REGIONAL", | |
271 | // "REGIONAL", "NEARLINE", "COLDLINE", "STANDARD" and | |
272 | // "DURABLE_REDUCED_AVAILABILITY". Defaults to "STANDARD", which | |
273 | // is equivalent to "MULTI_REGIONAL" or "REGIONAL" depending on | |
274 | // the bucket's location settings. | |
275 | StorageClass string | |
276 | ||
277 | // Created is the creation time of the bucket. | |
278 | // This field is read-only. | |
279 | Created time.Time | |
280 | ||
281 | // VersioningEnabled reports whether this bucket has versioning enabled. | |
282 | VersioningEnabled bool | |
283 | ||
284 | // Labels are the bucket's labels. | |
285 | Labels map[string]string | |
286 | ||
287 | // RequesterPays reports whether the bucket is a Requester Pays bucket. | |
288 | // Clients performing operations on Requester Pays buckets must provide | |
289 | // a user project (see BucketHandle.UserProject), which will be billed | |
290 | // for the operations. | |
291 | RequesterPays bool | |
292 | ||
293 | // Lifecycle is the lifecycle configuration for objects in the bucket. | |
294 | Lifecycle Lifecycle | |
295 | ||
296 | // Retention policy enforces a minimum retention time for all objects | |
297 | // contained in the bucket. A RetentionPolicy of nil implies the bucket | |
298 | // has no minimum data retention. | |
299 | // | |
300 | // This feature is in private alpha release. It is not currently available to | |
301 | // most customers. It might be changed in backwards-incompatible ways and is not | |
302 | // subject to any SLA or deprecation policy. | |
303 | RetentionPolicy *RetentionPolicy | |
304 | ||
305 | // The bucket's Cross-Origin Resource Sharing (CORS) configuration. | |
306 | CORS []CORS | |
307 | ||
308 | // The encryption configuration used by default for newly inserted objects. | |
309 | Encryption *BucketEncryption | |
310 | ||
311 | // The logging configuration. | |
312 | Logging *BucketLogging | |
313 | ||
314 | // The website configuration. | |
315 | Website *BucketWebsite | |
316 | } | |
317 | ||
318 | // BucketPolicyOnly configures access checks to use only bucket-level IAM | |
319 | // policies. | |
320 | type BucketPolicyOnly struct { | |
321 | // Enabled specifies whether access checks use only bucket-level IAM | |
322 | // policies. Enabled may be disabled until the locked time. | |
323 | Enabled bool | |
324 | // LockedTime specifies the deadline for changing Enabled from true to | |
325 | // false. | |
326 | LockedTime time.Time | |
327 | } | |
328 | ||
329 | // Lifecycle is the lifecycle configuration for objects in the bucket. | |
330 | type Lifecycle struct { | |
331 | Rules []LifecycleRule | |
332 | } | |
333 | ||
334 | // RetentionPolicy enforces a minimum retention time for all objects | |
335 | // contained in the bucket. | |
336 | // | |
337 | // Any attempt to overwrite or delete objects younger than the retention | |
338 | // period will result in an error. An unlocked retention policy can be | |
339 | // modified or removed from the bucket via the Update method. A | |
340 | // locked retention policy cannot be removed or shortened in duration | |
341 | // for the lifetime of the bucket. | |
342 | // | |
343 | // This feature is in private alpha release. It is not currently available to | |
344 | // most customers. It might be changed in backwards-incompatible ways and is not | |
345 | // subject to any SLA or deprecation policy. | |
346 | type RetentionPolicy struct { | |
347 | // RetentionPeriod specifies the duration that objects need to be | |
348 | // retained. Retention duration must be greater than zero and less than | |
349 | // 100 years. Note that enforcement of retention periods less than a day | |
350 | // is not guaranteed. Such periods should only be used for testing | |
351 | // purposes. | |
352 | RetentionPeriod time.Duration | |
353 | ||
354 | // EffectiveTime is the time from which the policy was enforced and | |
355 | // effective. This field is read-only. | |
356 | EffectiveTime time.Time | |
357 | ||
358 | // IsLocked describes whether the bucket is locked. Once locked, an | |
359 | // object retention policy cannot be modified. | |
360 | // This field is read-only. | |
361 | IsLocked bool | |
362 | } | |
363 | ||
364 | const ( | |
365 | // RFC3339 date with only the date segment, used for CreatedBefore in LifecycleRule. | |
366 | rfc3339Date = "2006-01-02" | |
367 | ||
368 | // DeleteAction is a lifecycle action that deletes a live and/or archived | |
369 | // objects. Takes precedence over SetStorageClass actions. | |
370 | DeleteAction = "Delete" | |
371 | ||
372 | // SetStorageClassAction changes the storage class of live and/or archived | |
373 | // objects. | |
374 | SetStorageClassAction = "SetStorageClass" | |
375 | ) | |
376 | ||
377 | // LifecycleRule is a lifecycle configuration rule. | |
378 | // | |
379 | // When all the configured conditions are met by an object in the bucket, the | |
380 | // configured action will automatically be taken on that object. | |
381 | type LifecycleRule struct { | |
382 | // Action is the action to take when all of the associated conditions are | |
383 | // met. | |
384 | Action LifecycleAction | |
385 | ||
386 | // Condition is the set of conditions that must be met for the associated | |
387 | // action to be taken. | |
388 | Condition LifecycleCondition | |
389 | } | |
390 | ||
391 | // LifecycleAction is a lifecycle configuration action. | |
392 | type LifecycleAction struct { | |
393 | // Type is the type of action to take on matching objects. | |
394 | // | |
395 | // Acceptable values are "Delete" to delete matching objects and | |
396 | // "SetStorageClass" to set the storage class defined in StorageClass on | |
397 | // matching objects. | |
398 | Type string | |
399 | ||
400 | // StorageClass is the storage class to set on matching objects if the Action | |
401 | // is "SetStorageClass". | |
402 | StorageClass string | |
403 | } | |
404 | ||
405 | // Liveness specifies whether the object is live or not. | |
406 | type Liveness int | |
407 | ||
408 | const ( | |
409 | // LiveAndArchived includes both live and archived objects. | |
410 | LiveAndArchived Liveness = iota | |
411 | // Live specifies that the object is still live. | |
412 | Live | |
413 | // Archived specifies that the object is archived. | |
414 | Archived | |
415 | ) | |
416 | ||
417 | // LifecycleCondition is a set of conditions used to match objects and take an | |
418 | // action automatically. | |
419 | // | |
420 | // All configured conditions must be met for the associated action to be taken. | |
421 | type LifecycleCondition struct { | |
422 | // AgeInDays is the age of the object in days. | |
423 | AgeInDays int64 | |
424 | ||
425 | // CreatedBefore is the time the object was created. | |
426 | // | |
427 | // This condition is satisfied when an object is created before midnight of | |
428 | // the specified date in UTC. | |
429 | CreatedBefore time.Time | |
430 | ||
431 | // Liveness specifies the object's liveness. Relevant only for versioned objects | |
432 | Liveness Liveness | |
433 | ||
434 | // MatchesStorageClasses is the condition matching the object's storage | |
435 | // class. | |
436 | // | |
437 | // Values include "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE", | |
438 | // "STANDARD", and "DURABLE_REDUCED_AVAILABILITY". | |
439 | MatchesStorageClasses []string | |
440 | ||
441 | // NumNewerVersions is the condition matching objects with a number of newer versions. | |
442 | // | |
443 | // If the value is N, this condition is satisfied when there are at least N | |
444 | // versions (including the live version) newer than this version of the | |
445 | // object. | |
446 | NumNewerVersions int64 | |
447 | } | |
448 | ||
449 | // BucketLogging holds the bucket's logging configuration, which defines the | |
450 | // destination bucket and optional name prefix for the current bucket's | |
451 | // logs. | |
452 | type BucketLogging struct { | |
453 | // The destination bucket where the current bucket's logs | |
454 | // should be placed. | |
455 | LogBucket string | |
456 | ||
457 | // A prefix for log object names. | |
458 | LogObjectPrefix string | |
459 | } | |
460 | ||
461 | // BucketWebsite holds the bucket's website configuration, controlling how the | |
462 | // service behaves when accessing bucket contents as a web site. See | |
463 | // https://cloud.google.com/storage/docs/static-website for more information. | |
464 | type BucketWebsite struct { | |
465 | // If the requested object path is missing, the service will ensure the path has | |
466 | // a trailing '/', append this suffix, and attempt to retrieve the resulting | |
467 | // object. This allows the creation of index.html objects to represent directory | |
468 | // pages. | |
469 | MainPageSuffix string | |
470 | ||
471 | // If the requested object path is missing, and any mainPageSuffix object is | |
472 | // missing, if applicable, the service will return the named object from this | |
473 | // bucket as the content for a 404 Not Found result. | |
474 | NotFoundPage string | |
475 | } | |
476 | ||
477 | func newBucket(b *raw.Bucket) (*BucketAttrs, error) { | |
478 | if b == nil { | |
479 | return nil, nil | |
480 | } | |
481 | rp, err := toRetentionPolicy(b.RetentionPolicy) | |
482 | if err != nil { | |
483 | return nil, err | |
484 | } | |
485 | return &BucketAttrs{ | |
486 | Name: b.Name, | |
487 | Location: b.Location, | |
488 | MetaGeneration: b.Metageneration, | |
489 | DefaultEventBasedHold: b.DefaultEventBasedHold, | |
490 | StorageClass: b.StorageClass, | |
491 | Created: convertTime(b.TimeCreated), | |
492 | VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled, | |
493 | ACL: toBucketACLRules(b.Acl), | |
494 | DefaultObjectACL: toObjectACLRules(b.DefaultObjectAcl), | |
495 | Labels: b.Labels, | |
496 | RequesterPays: b.Billing != nil && b.Billing.RequesterPays, | |
497 | Lifecycle: toLifecycle(b.Lifecycle), | |
498 | RetentionPolicy: rp, | |
499 | CORS: toCORS(b.Cors), | |
500 | Encryption: toBucketEncryption(b.Encryption), | |
501 | Logging: toBucketLogging(b.Logging), | |
502 | Website: toBucketWebsite(b.Website), | |
503 | BucketPolicyOnly: toBucketPolicyOnly(b.IamConfiguration), | |
504 | }, nil | |
505 | } | |
506 | ||
507 | // toRawBucket copies the editable attribute from b to the raw library's Bucket type. | |
508 | func (b *BucketAttrs) toRawBucket() *raw.Bucket { | |
509 | // Copy label map. | |
510 | var labels map[string]string | |
511 | if len(b.Labels) > 0 { | |
512 | labels = make(map[string]string, len(b.Labels)) | |
513 | for k, v := range b.Labels { | |
514 | labels[k] = v | |
515 | } | |
516 | } | |
517 | // Ignore VersioningEnabled if it is false. This is OK because | |
518 | // we only call this method when creating a bucket, and by default | |
519 | // new buckets have versioning off. | |
520 | var v *raw.BucketVersioning | |
521 | if b.VersioningEnabled { | |
522 | v = &raw.BucketVersioning{Enabled: true} | |
523 | } | |
524 | var bb *raw.BucketBilling | |
525 | if b.RequesterPays { | |
526 | bb = &raw.BucketBilling{RequesterPays: true} | |
527 | } | |
528 | var bktIAM *raw.BucketIamConfiguration | |
529 | if b.BucketPolicyOnly.Enabled { | |
530 | bktIAM = &raw.BucketIamConfiguration{ | |
531 | BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{ | |
532 | Enabled: true, | |
533 | }, | |
534 | } | |
535 | } | |
536 | return &raw.Bucket{ | |
537 | Name: b.Name, | |
538 | Location: b.Location, | |
539 | StorageClass: b.StorageClass, | |
540 | Acl: toRawBucketACL(b.ACL), | |
541 | DefaultObjectAcl: toRawObjectACL(b.DefaultObjectACL), | |
542 | Versioning: v, | |
543 | Labels: labels, | |
544 | Billing: bb, | |
545 | Lifecycle: toRawLifecycle(b.Lifecycle), | |
546 | RetentionPolicy: b.RetentionPolicy.toRawRetentionPolicy(), | |
547 | Cors: toRawCORS(b.CORS), | |
548 | Encryption: b.Encryption.toRawBucketEncryption(), | |
549 | Logging: b.Logging.toRawBucketLogging(), | |
550 | Website: b.Website.toRawBucketWebsite(), | |
551 | IamConfiguration: bktIAM, | |
552 | } | |
553 | } | |
554 | ||
555 | // CORS is the bucket's Cross-Origin Resource Sharing (CORS) configuration. | |
556 | type CORS struct { | |
557 | // MaxAge is the value to return in the Access-Control-Max-Age | |
558 | // header used in preflight responses. | |
559 | MaxAge time.Duration | |
560 | ||
561 | // Methods is the list of HTTP methods on which to include CORS response | |
562 | // headers, (GET, OPTIONS, POST, etc) Note: "*" is permitted in the list | |
563 | // of methods, and means "any method". | |
564 | Methods []string | |
565 | ||
566 | // Origins is the list of Origins eligible to receive CORS response | |
567 | // headers. Note: "*" is permitted in the list of origins, and means | |
568 | // "any Origin". | |
569 | Origins []string | |
570 | ||
571 | // ResponseHeaders is the list of HTTP headers other than the simple | |
572 | // response headers to give permission for the user-agent to share | |
573 | // across domains. | |
574 | ResponseHeaders []string | |
575 | } | |
576 | ||
577 | // BucketEncryption is a bucket's encryption configuration. | |
578 | type BucketEncryption struct { | |
579 | // A Cloud KMS key name, in the form | |
580 | // projects/P/locations/L/keyRings/R/cryptoKeys/K, that will be used to encrypt | |
581 | // objects inserted into this bucket, if no encryption method is specified. | |
582 | // The key's location must be the same as the bucket's. | |
583 | DefaultKMSKeyName string | |
584 | } | |
585 | ||
586 | // BucketAttrsToUpdate define the attributes to update during an Update call. | |
587 | type BucketAttrsToUpdate struct { | |
588 | // If set, updates whether the bucket uses versioning. | |
589 | VersioningEnabled optional.Bool | |
590 | ||
591 | // If set, updates whether the bucket is a Requester Pays bucket. | |
592 | RequesterPays optional.Bool | |
593 | ||
594 | // DefaultEventBasedHold is the default value for event-based hold on | |
595 | // newly created objects in this bucket. | |
596 | DefaultEventBasedHold optional.Bool | |
597 | ||
598 | // BucketPolicyOnly configures access checks to use only bucket-level IAM | |
599 | // policies. | |
600 | BucketPolicyOnly *BucketPolicyOnly | |
601 | ||
602 | // If set, updates the retention policy of the bucket. Using | |
603 | // RetentionPolicy.RetentionPeriod = 0 will delete the existing policy. | |
604 | // | |
605 | // This feature is in private alpha release. It is not currently available to | |
606 | // most customers. It might be changed in backwards-incompatible ways and is not | |
607 | // subject to any SLA or deprecation policy. | |
608 | RetentionPolicy *RetentionPolicy | |
609 | ||
610 | // If set, replaces the CORS configuration with a new configuration. | |
611 | // An empty (rather than nil) slice causes all CORS policies to be removed. | |
612 | CORS []CORS | |
613 | ||
614 | // If set, replaces the encryption configuration of the bucket. Using | |
615 | // BucketEncryption.DefaultKMSKeyName = "" will delete the existing | |
616 | // configuration. | |
617 | Encryption *BucketEncryption | |
618 | ||
619 | // If set, replaces the lifecycle configuration of the bucket. | |
620 | Lifecycle *Lifecycle | |
621 | ||
622 | // If set, replaces the logging configuration of the bucket. | |
623 | Logging *BucketLogging | |
624 | ||
625 | // If set, replaces the website configuration of the bucket. | |
626 | Website *BucketWebsite | |
627 | ||
628 | // If not empty, applies a predefined set of access controls. | |
629 | // See https://cloud.google.com/storage/docs/json_api/v1/buckets/patch. | |
630 | PredefinedACL string | |
631 | ||
632 | // If not empty, applies a predefined set of default object access controls. | |
633 | // See https://cloud.google.com/storage/docs/json_api/v1/buckets/patch. | |
634 | PredefinedDefaultObjectACL string | |
635 | ||
636 | setLabels map[string]string | |
637 | deleteLabels map[string]bool | |
638 | } | |
639 | ||
640 | // SetLabel causes a label to be added or modified when ua is used | |
641 | // in a call to Bucket.Update. | |
642 | func (ua *BucketAttrsToUpdate) SetLabel(name, value string) { | |
643 | if ua.setLabels == nil { | |
644 | ua.setLabels = map[string]string{} | |
645 | } | |
646 | ua.setLabels[name] = value | |
647 | } | |
648 | ||
649 | // DeleteLabel causes a label to be deleted when ua is used in a | |
650 | // call to Bucket.Update. | |
651 | func (ua *BucketAttrsToUpdate) DeleteLabel(name string) { | |
652 | if ua.deleteLabels == nil { | |
653 | ua.deleteLabels = map[string]bool{} | |
654 | } | |
655 | ua.deleteLabels[name] = true | |
656 | } | |
657 | ||
658 | func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket { | |
659 | rb := &raw.Bucket{} | |
660 | if ua.CORS != nil { | |
661 | rb.Cors = toRawCORS(ua.CORS) | |
662 | rb.ForceSendFields = append(rb.ForceSendFields, "Cors") | |
663 | } | |
664 | if ua.DefaultEventBasedHold != nil { | |
665 | rb.DefaultEventBasedHold = optional.ToBool(ua.DefaultEventBasedHold) | |
666 | rb.ForceSendFields = append(rb.ForceSendFields, "DefaultEventBasedHold") | |
667 | } | |
668 | if ua.RetentionPolicy != nil { | |
669 | if ua.RetentionPolicy.RetentionPeriod == 0 { | |
670 | rb.NullFields = append(rb.NullFields, "RetentionPolicy") | |
671 | rb.RetentionPolicy = nil | |
672 | } else { | |
673 | rb.RetentionPolicy = ua.RetentionPolicy.toRawRetentionPolicy() | |
674 | } | |
675 | } | |
676 | if ua.VersioningEnabled != nil { | |
677 | rb.Versioning = &raw.BucketVersioning{ | |
678 | Enabled: optional.ToBool(ua.VersioningEnabled), | |
679 | ForceSendFields: []string{"Enabled"}, | |
680 | } | |
681 | } | |
682 | if ua.RequesterPays != nil { | |
683 | rb.Billing = &raw.BucketBilling{ | |
684 | RequesterPays: optional.ToBool(ua.RequesterPays), | |
685 | ForceSendFields: []string{"RequesterPays"}, | |
686 | } | |
687 | } | |
688 | if ua.BucketPolicyOnly != nil { | |
689 | rb.IamConfiguration = &raw.BucketIamConfiguration{ | |
690 | BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{ | |
691 | Enabled: ua.BucketPolicyOnly.Enabled, | |
692 | }, | |
693 | } | |
694 | } | |
695 | if ua.Encryption != nil { | |
696 | if ua.Encryption.DefaultKMSKeyName == "" { | |
697 | rb.NullFields = append(rb.NullFields, "Encryption") | |
698 | rb.Encryption = nil | |
699 | } else { | |
700 | rb.Encryption = ua.Encryption.toRawBucketEncryption() | |
701 | } | |
702 | } | |
703 | if ua.Lifecycle != nil { | |
704 | rb.Lifecycle = toRawLifecycle(*ua.Lifecycle) | |
705 | } | |
706 | if ua.Logging != nil { | |
707 | if *ua.Logging == (BucketLogging{}) { | |
708 | rb.NullFields = append(rb.NullFields, "Logging") | |
709 | rb.Logging = nil | |
710 | } else { | |
711 | rb.Logging = ua.Logging.toRawBucketLogging() | |
712 | } | |
713 | } | |
714 | if ua.Website != nil { | |
715 | if *ua.Website == (BucketWebsite{}) { | |
716 | rb.NullFields = append(rb.NullFields, "Website") | |
717 | rb.Website = nil | |
718 | } else { | |
719 | rb.Website = ua.Website.toRawBucketWebsite() | |
720 | } | |
721 | } | |
722 | if ua.PredefinedACL != "" { | |
723 | // Clear ACL or the call will fail. | |
724 | rb.Acl = nil | |
725 | rb.ForceSendFields = append(rb.ForceSendFields, "Acl") | |
726 | } | |
727 | if ua.PredefinedDefaultObjectACL != "" { | |
728 | // Clear ACLs or the call will fail. | |
729 | rb.DefaultObjectAcl = nil | |
730 | rb.ForceSendFields = append(rb.ForceSendFields, "DefaultObjectAcl") | |
731 | } | |
732 | if ua.setLabels != nil || ua.deleteLabels != nil { | |
733 | rb.Labels = map[string]string{} | |
734 | for k, v := range ua.setLabels { | |
735 | rb.Labels[k] = v | |
736 | } | |
737 | if len(rb.Labels) == 0 && len(ua.deleteLabels) > 0 { | |
738 | rb.ForceSendFields = append(rb.ForceSendFields, "Labels") | |
739 | } | |
740 | for l := range ua.deleteLabels { | |
741 | rb.NullFields = append(rb.NullFields, "Labels."+l) | |
742 | } | |
743 | } | |
744 | return rb | |
745 | } | |
746 | ||
747 | // If returns a new BucketHandle that applies a set of preconditions. | |
748 | // Preconditions already set on the BucketHandle are ignored. | |
749 | // Operations on the new handle will return an error if the preconditions are not | |
750 | // satisfied. The only valid preconditions for buckets are MetagenerationMatch | |
751 | // and MetagenerationNotMatch. | |
752 | func (b *BucketHandle) If(conds BucketConditions) *BucketHandle { | |
753 | b2 := *b | |
754 | b2.conds = &conds | |
755 | return &b2 | |
756 | } | |
757 | ||
758 | // BucketConditions constrain bucket methods to act on specific metagenerations. | |
759 | // | |
760 | // The zero value is an empty set of constraints. | |
761 | type BucketConditions struct { | |
762 | // MetagenerationMatch specifies that the bucket must have the given | |
763 | // metageneration for the operation to occur. | |
764 | // If MetagenerationMatch is zero, it has no effect. | |
765 | MetagenerationMatch int64 | |
766 | ||
767 | // MetagenerationNotMatch specifies that the bucket must not have the given | |
768 | // metageneration for the operation to occur. | |
769 | // If MetagenerationNotMatch is zero, it has no effect. | |
770 | MetagenerationNotMatch int64 | |
771 | } | |
772 | ||
773 | func (c *BucketConditions) validate(method string) error { | |
774 | if *c == (BucketConditions{}) { | |
775 | return fmt.Errorf("storage: %s: empty conditions", method) | |
776 | } | |
777 | if c.MetagenerationMatch != 0 && c.MetagenerationNotMatch != 0 { | |
778 | return fmt.Errorf("storage: %s: multiple conditions specified for metageneration", method) | |
779 | } | |
780 | return nil | |
781 | } | |
782 | ||
783 | // UserProject returns a new BucketHandle that passes the project ID as the user | |
784 | // project for all subsequent calls. Calls with a user project will be billed to that | |
785 | // project rather than to the bucket's owning project. | |
786 | // | |
787 | // A user project is required for all operations on Requester Pays buckets. | |
788 | func (b *BucketHandle) UserProject(projectID string) *BucketHandle { | |
789 | b2 := *b | |
790 | b2.userProject = projectID | |
791 | b2.acl.userProject = projectID | |
792 | b2.defaultObjectACL.userProject = projectID | |
793 | return &b2 | |
794 | } | |
795 | ||
796 | // LockRetentionPolicy locks a bucket's retention policy until a previously-configured | |
797 | // RetentionPeriod past the EffectiveTime. Note that if RetentionPeriod is set to less | |
798 | // than a day, the retention policy is treated as a development configuration and locking | |
799 | // will have no effect. The BucketHandle must have a metageneration condition that | |
800 | // matches the bucket's metageneration. See BucketHandle.If. | |
801 | // | |
802 | // This feature is in private alpha release. It is not currently available to | |
803 | // most customers. It might be changed in backwards-incompatible ways and is not | |
804 | // subject to any SLA or deprecation policy. | |
805 | func (b *BucketHandle) LockRetentionPolicy(ctx context.Context) error { | |
806 | var metageneration int64 | |
807 | if b.conds != nil { | |
808 | metageneration = b.conds.MetagenerationMatch | |
809 | } | |
810 | req := b.c.raw.Buckets.LockRetentionPolicy(b.name, metageneration) | |
811 | _, err := req.Context(ctx).Do() | |
812 | return err | |
813 | } | |
814 | ||
815 | // applyBucketConds modifies the provided call using the conditions in conds. | |
816 | // call is something that quacks like a *raw.WhateverCall. | |
817 | func applyBucketConds(method string, conds *BucketConditions, call interface{}) error { | |
818 | if conds == nil { | |
819 | return nil | |
820 | } | |
821 | if err := conds.validate(method); err != nil { | |
822 | return err | |
823 | } | |
824 | cval := reflect.ValueOf(call) | |
825 | switch { | |
826 | case conds.MetagenerationMatch != 0: | |
827 | if !setConditionField(cval, "IfMetagenerationMatch", conds.MetagenerationMatch) { | |
828 | return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", method) | |
829 | } | |
830 | case conds.MetagenerationNotMatch != 0: | |
831 | if !setConditionField(cval, "IfMetagenerationNotMatch", conds.MetagenerationNotMatch) { | |
832 | return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", method) | |
833 | } | |
834 | } | |
835 | return nil | |
836 | } | |
837 | ||
838 | func (rp *RetentionPolicy) toRawRetentionPolicy() *raw.BucketRetentionPolicy { | |
839 | if rp == nil { | |
840 | return nil | |
841 | } | |
842 | return &raw.BucketRetentionPolicy{ | |
843 | RetentionPeriod: int64(rp.RetentionPeriod / time.Second), | |
844 | } | |
845 | } | |
846 | ||
847 | func toRetentionPolicy(rp *raw.BucketRetentionPolicy) (*RetentionPolicy, error) { | |
848 | if rp == nil { | |
849 | return nil, nil | |
850 | } | |
851 | t, err := time.Parse(time.RFC3339, rp.EffectiveTime) | |
852 | if err != nil { | |
853 | return nil, err | |
854 | } | |
855 | return &RetentionPolicy{ | |
856 | RetentionPeriod: time.Duration(rp.RetentionPeriod) * time.Second, | |
857 | EffectiveTime: t, | |
858 | IsLocked: rp.IsLocked, | |
859 | }, nil | |
860 | } | |
861 | ||
862 | func toRawCORS(c []CORS) []*raw.BucketCors { | |
863 | var out []*raw.BucketCors | |
864 | for _, v := range c { | |
865 | out = append(out, &raw.BucketCors{ | |
866 | MaxAgeSeconds: int64(v.MaxAge / time.Second), | |
867 | Method: v.Methods, | |
868 | Origin: v.Origins, | |
869 | ResponseHeader: v.ResponseHeaders, | |
870 | }) | |
871 | } | |
872 | return out | |
873 | } | |
874 | ||
875 | func toCORS(rc []*raw.BucketCors) []CORS { | |
876 | var out []CORS | |
877 | for _, v := range rc { | |
878 | out = append(out, CORS{ | |
879 | MaxAge: time.Duration(v.MaxAgeSeconds) * time.Second, | |
880 | Methods: v.Method, | |
881 | Origins: v.Origin, | |
882 | ResponseHeaders: v.ResponseHeader, | |
883 | }) | |
884 | } | |
885 | return out | |
886 | } | |
887 | ||
888 | func toRawLifecycle(l Lifecycle) *raw.BucketLifecycle { | |
889 | var rl raw.BucketLifecycle | |
890 | if len(l.Rules) == 0 { | |
891 | return nil | |
892 | } | |
893 | for _, r := range l.Rules { | |
894 | rr := &raw.BucketLifecycleRule{ | |
895 | Action: &raw.BucketLifecycleRuleAction{ | |
896 | Type: r.Action.Type, | |
897 | StorageClass: r.Action.StorageClass, | |
898 | }, | |
899 | Condition: &raw.BucketLifecycleRuleCondition{ | |
900 | Age: r.Condition.AgeInDays, | |
901 | MatchesStorageClass: r.Condition.MatchesStorageClasses, | |
902 | NumNewerVersions: r.Condition.NumNewerVersions, | |
903 | }, | |
904 | } | |
905 | ||
906 | switch r.Condition.Liveness { | |
907 | case LiveAndArchived: | |
908 | rr.Condition.IsLive = nil | |
909 | case Live: | |
910 | rr.Condition.IsLive = googleapi.Bool(true) | |
911 | case Archived: | |
912 | rr.Condition.IsLive = googleapi.Bool(false) | |
913 | } | |
914 | ||
915 | if !r.Condition.CreatedBefore.IsZero() { | |
916 | rr.Condition.CreatedBefore = r.Condition.CreatedBefore.Format(rfc3339Date) | |
917 | } | |
918 | rl.Rule = append(rl.Rule, rr) | |
919 | } | |
920 | return &rl | |
921 | } | |
922 | ||
923 | func toLifecycle(rl *raw.BucketLifecycle) Lifecycle { | |
924 | var l Lifecycle | |
925 | if rl == nil { | |
926 | return l | |
927 | } | |
928 | for _, rr := range rl.Rule { | |
929 | r := LifecycleRule{ | |
930 | Action: LifecycleAction{ | |
931 | Type: rr.Action.Type, | |
932 | StorageClass: rr.Action.StorageClass, | |
933 | }, | |
934 | Condition: LifecycleCondition{ | |
935 | AgeInDays: rr.Condition.Age, | |
936 | MatchesStorageClasses: rr.Condition.MatchesStorageClass, | |
937 | NumNewerVersions: rr.Condition.NumNewerVersions, | |
938 | }, | |
939 | } | |
940 | ||
941 | switch { | |
942 | case rr.Condition.IsLive == nil: | |
943 | r.Condition.Liveness = LiveAndArchived | |
944 | case *rr.Condition.IsLive == true: | |
945 | r.Condition.Liveness = Live | |
946 | case *rr.Condition.IsLive == false: | |
947 | r.Condition.Liveness = Archived | |
948 | } | |
949 | ||
950 | if rr.Condition.CreatedBefore != "" { | |
951 | r.Condition.CreatedBefore, _ = time.Parse(rfc3339Date, rr.Condition.CreatedBefore) | |
952 | } | |
953 | l.Rules = append(l.Rules, r) | |
954 | } | |
955 | return l | |
956 | } | |
957 | ||
958 | func (e *BucketEncryption) toRawBucketEncryption() *raw.BucketEncryption { | |
959 | if e == nil { | |
960 | return nil | |
961 | } | |
962 | return &raw.BucketEncryption{ | |
963 | DefaultKmsKeyName: e.DefaultKMSKeyName, | |
964 | } | |
965 | } | |
966 | ||
967 | func toBucketEncryption(e *raw.BucketEncryption) *BucketEncryption { | |
968 | if e == nil { | |
969 | return nil | |
970 | } | |
971 | return &BucketEncryption{DefaultKMSKeyName: e.DefaultKmsKeyName} | |
972 | } | |
973 | ||
974 | func (b *BucketLogging) toRawBucketLogging() *raw.BucketLogging { | |
975 | if b == nil { | |
976 | return nil | |
977 | } | |
978 | return &raw.BucketLogging{ | |
979 | LogBucket: b.LogBucket, | |
980 | LogObjectPrefix: b.LogObjectPrefix, | |
981 | } | |
982 | } | |
983 | ||
984 | func toBucketLogging(b *raw.BucketLogging) *BucketLogging { | |
985 | if b == nil { | |
986 | return nil | |
987 | } | |
988 | return &BucketLogging{ | |
989 | LogBucket: b.LogBucket, | |
990 | LogObjectPrefix: b.LogObjectPrefix, | |
991 | } | |
992 | } | |
993 | ||
994 | func (w *BucketWebsite) toRawBucketWebsite() *raw.BucketWebsite { | |
995 | if w == nil { | |
996 | return nil | |
997 | } | |
998 | return &raw.BucketWebsite{ | |
999 | MainPageSuffix: w.MainPageSuffix, | |
1000 | NotFoundPage: w.NotFoundPage, | |
1001 | } | |
1002 | } | |
1003 | ||
1004 | func toBucketWebsite(w *raw.BucketWebsite) *BucketWebsite { | |
1005 | if w == nil { | |
1006 | return nil | |
1007 | } | |
1008 | return &BucketWebsite{ | |
1009 | MainPageSuffix: w.MainPageSuffix, | |
1010 | NotFoundPage: w.NotFoundPage, | |
1011 | } | |
1012 | } | |
1013 | ||
1014 | func toBucketPolicyOnly(b *raw.BucketIamConfiguration) BucketPolicyOnly { | |
1015 | if b == nil || b.BucketPolicyOnly == nil || !b.BucketPolicyOnly.Enabled { | |
1016 | return BucketPolicyOnly{} | |
1017 | } | |
1018 | lt, err := time.Parse(time.RFC3339, b.BucketPolicyOnly.LockedTime) | |
1019 | if err != nil { | |
1020 | return BucketPolicyOnly{ | |
1021 | Enabled: true, | |
1022 | } | |
1023 | } | |
1024 | return BucketPolicyOnly{ | |
1025 | Enabled: true, | |
1026 | LockedTime: lt, | |
1027 | } | |
1028 | } | |
1029 | ||
1030 | // Objects returns an iterator over the objects in the bucket that match the Query q. | |
1031 | // If q is nil, no filtering is done. | |
1032 | func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator { | |
1033 | it := &ObjectIterator{ | |
1034 | ctx: ctx, | |
1035 | bucket: b, | |
1036 | } | |
1037 | it.pageInfo, it.nextFunc = iterator.NewPageInfo( | |
1038 | it.fetch, | |
1039 | func() int { return len(it.items) }, | |
1040 | func() interface{} { b := it.items; it.items = nil; return b }) | |
1041 | if q != nil { | |
1042 | it.query = *q | |
1043 | } | |
1044 | return it | |
1045 | } | |
1046 | ||
1047 | // An ObjectIterator is an iterator over ObjectAttrs. | |
1048 | type ObjectIterator struct { | |
1049 | ctx context.Context | |
1050 | bucket *BucketHandle | |
1051 | query Query | |
1052 | pageInfo *iterator.PageInfo | |
1053 | nextFunc func() error | |
1054 | items []*ObjectAttrs | |
1055 | } | |
1056 | ||
1057 | // PageInfo supports pagination. See the google.golang.org/api/iterator package for details. | |
1058 | func (it *ObjectIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } | |
1059 | ||
1060 | // Next returns the next result. Its second return value is iterator.Done if | |
1061 | // there are no more results. Once Next returns iterator.Done, all subsequent | |
1062 | // calls will return iterator.Done. | |
1063 | // | |
1064 | // If Query.Delimiter is non-empty, some of the ObjectAttrs returned by Next will | |
1065 | // have a non-empty Prefix field, and a zero value for all other fields. These | |
1066 | // represent prefixes. | |
1067 | func (it *ObjectIterator) Next() (*ObjectAttrs, error) { | |
1068 | if err := it.nextFunc(); err != nil { | |
1069 | return nil, err | |
1070 | } | |
1071 | item := it.items[0] | |
1072 | it.items = it.items[1:] | |
1073 | return item, nil | |
1074 | } | |
1075 | ||
1076 | func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error) { | |
1077 | req := it.bucket.c.raw.Objects.List(it.bucket.name) | |
1078 | setClientHeader(req.Header()) | |
1079 | req.Projection("full") | |
1080 | req.Delimiter(it.query.Delimiter) | |
1081 | req.Prefix(it.query.Prefix) | |
1082 | req.Versions(it.query.Versions) | |
1083 | req.PageToken(pageToken) | |
1084 | if it.bucket.userProject != "" { | |
1085 | req.UserProject(it.bucket.userProject) | |
1086 | } | |
1087 | if pageSize > 0 { | |
1088 | req.MaxResults(int64(pageSize)) | |
1089 | } | |
1090 | var resp *raw.Objects | |
1091 | var err error | |
1092 | err = runWithRetry(it.ctx, func() error { | |
1093 | resp, err = req.Context(it.ctx).Do() | |
1094 | return err | |
1095 | }) | |
1096 | if err != nil { | |
1097 | if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { | |
1098 | err = ErrBucketNotExist | |
1099 | } | |
1100 | return "", err | |
1101 | } | |
1102 | for _, item := range resp.Items { | |
1103 | it.items = append(it.items, newObject(item)) | |
1104 | } | |
1105 | for _, prefix := range resp.Prefixes { | |
1106 | it.items = append(it.items, &ObjectAttrs{Prefix: prefix}) | |
1107 | } | |
1108 | return resp.NextPageToken, nil | |
1109 | } | |
1110 | ||
1111 | // Buckets returns an iterator over the buckets in the project. You may | |
1112 | // optionally set the iterator's Prefix field to restrict the list to buckets | |
1113 | // whose names begin with the prefix. By default, all buckets in the project | |
1114 | // are returned. | |
1115 | func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator { | |
1116 | it := &BucketIterator{ | |
1117 | ctx: ctx, | |
1118 | client: c, | |
1119 | projectID: projectID, | |
1120 | } | |
1121 | it.pageInfo, it.nextFunc = iterator.NewPageInfo( | |
1122 | it.fetch, | |
1123 | func() int { return len(it.buckets) }, | |
1124 | func() interface{} { b := it.buckets; it.buckets = nil; return b }) | |
1125 | return it | |
1126 | } | |
1127 | ||
1128 | // A BucketIterator is an iterator over BucketAttrs. | |
1129 | type BucketIterator struct { | |
1130 | // Prefix restricts the iterator to buckets whose names begin with it. | |
1131 | Prefix string | |
1132 | ||
1133 | ctx context.Context | |
1134 | client *Client | |
1135 | projectID string | |
1136 | buckets []*BucketAttrs | |
1137 | pageInfo *iterator.PageInfo | |
1138 | nextFunc func() error | |
1139 | } | |
1140 | ||
1141 | // Next returns the next result. Its second return value is iterator.Done if | |
1142 | // there are no more results. Once Next returns iterator.Done, all subsequent | |
1143 | // calls will return iterator.Done. | |
1144 | func (it *BucketIterator) Next() (*BucketAttrs, error) { | |
1145 | if err := it.nextFunc(); err != nil { | |
1146 | return nil, err | |
1147 | } | |
1148 | b := it.buckets[0] | |
1149 | it.buckets = it.buckets[1:] | |
1150 | return b, nil | |
1151 | } | |
1152 | ||
1153 | // PageInfo supports pagination. See the google.golang.org/api/iterator package for details. | |
1154 | func (it *BucketIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } | |
1155 | ||
1156 | func (it *BucketIterator) fetch(pageSize int, pageToken string) (token string, err error) { | |
1157 | req := it.client.raw.Buckets.List(it.projectID) | |
1158 | setClientHeader(req.Header()) | |
1159 | req.Projection("full") | |
1160 | req.Prefix(it.Prefix) | |
1161 | req.PageToken(pageToken) | |
1162 | if pageSize > 0 { | |
1163 | req.MaxResults(int64(pageSize)) | |
1164 | } | |
1165 | var resp *raw.Buckets | |
1166 | err = runWithRetry(it.ctx, func() error { | |
1167 | resp, err = req.Context(it.ctx).Do() | |
1168 | return err | |
1169 | }) | |
1170 | if err != nil { | |
1171 | return "", err | |
1172 | } | |
1173 | for _, item := range resp.Items { | |
1174 | b, err := newBucket(item) | |
1175 | if err != nil { | |
1176 | return "", err | |
1177 | } | |
1178 | it.buckets = append(it.buckets, b) | |
1179 | } | |
1180 | return resp.NextPageToken, nil | |
1181 | } |