google-cloud-storage 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,783 @@
1
+ # Copyright 2014 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/storage/bucket/acl"
17
+ require "google/cloud/storage/bucket/list"
18
+ require "google/cloud/storage/bucket/cors"
19
+ require "google/cloud/storage/file"
20
+ require "pathname"
21
+
22
+ module Google
23
+ module Cloud
24
+ module Storage
25
+ ##
26
+ # # Bucket
27
+ #
28
+ # Represents a Storage bucket. Belongs to a Project and has many Files.
29
+ #
30
+ # @example
31
+ # require "google/cloud"
32
+ #
33
+ # gcloud = Google::Cloud.new
34
+ # storage = gcloud.storage
35
+ #
36
+ # bucket = storage.bucket "my-bucket"
37
+ # file = bucket.file "path/to/my-file.ext"
38
+ #
39
+ class Bucket
40
+ ##
41
+ # @private The Service object.
42
+ attr_accessor :service
43
+
44
+ ##
45
+ # @private The Google API Client object.
46
+ attr_accessor :gapi
47
+
48
+ ##
49
+ # @private Create an empty Bucket object.
50
+ def initialize
51
+ @service = nil
52
+ @gapi = Google::Apis::StorageV1::Bucket.new
53
+ end
54
+
55
+ ##
56
+ # The kind of item this is.
57
+ # For buckets, this is always `storage#bucket`.
58
+ def kind
59
+ @gapi.kind
60
+ end
61
+
62
+ ##
63
+ # The ID of the bucket.
64
+ def id
65
+ @gapi.id
66
+ end
67
+
68
+ ##
69
+ # The name of the bucket.
70
+ def name
71
+ @gapi.name
72
+ end
73
+
74
+ ##
75
+ # A URL that can be used to access the bucket using the REST API.
76
+ def api_url
77
+ @gapi.self_link
78
+ end
79
+
80
+ ##
81
+ # Creation time of the bucket.
82
+ def created_at
83
+ @gapi.time_created
84
+ end
85
+
86
+ ##
87
+ # Returns the current CORS configuration for a static website served
88
+ # from the bucket.
89
+ #
90
+ # The return value is a frozen (unmodifiable) array of hashes containing
91
+ # the attributes specified for the Bucket resource field
92
+ # [cors](https://cloud.google.com/storage/docs/json_api/v1/buckets#cors).
93
+ #
94
+ # This method also accepts a block for updating the bucket's CORS rules.
95
+ # See {Bucket::Cors} for details.
96
+ #
97
+ # @see https://cloud.google.com/storage/docs/cross-origin Cross-Origin
98
+ # Resource Sharing (CORS)
99
+ #
100
+ # @yield [cors] a block for setting CORS rules
101
+ # @yieldparam [Bucket::Cors] cors the object accepting CORS rules
102
+ #
103
+ # @example Retrieving the bucket's CORS rules.
104
+ # require "google/cloud"
105
+ #
106
+ # gcloud = Google::Cloud.new
107
+ # storage = gcloud.storage
108
+ #
109
+ # bucket = storage.bucket "my-todo-app"
110
+ # bucket.cors #=> [{"origin"=>["http://example.org"],
111
+ # # "method"=>["GET","POST","DELETE"],
112
+ # # "responseHeader"=>["X-My-Custom-Header"],
113
+ # # "maxAgeSeconds"=>3600}]
114
+ #
115
+ # @example Updating the bucket's CORS rules inside a block.
116
+ # require "google/cloud"
117
+ #
118
+ # gcloud = Google::Cloud.new
119
+ # storage = gcloud.storage
120
+ # bucket = storage.bucket "my-todo-app"
121
+ #
122
+ # bucket.update do |b|
123
+ # b.cors do |c|
124
+ # c.add_rule ["http://example.org", "https://example.org"],
125
+ # "*",
126
+ # response_headers: ["X-My-Custom-Header"],
127
+ # max_age: 3600
128
+ # end
129
+ # end
130
+ #
131
+ def cors
132
+ cors_builder = Bucket::Cors.from_gapi @gapi.cors_configurations
133
+ if block_given?
134
+ yield cors_builder
135
+ if cors_builder.changed?
136
+ @gapi.cors_configurations = cors_builder.to_gapi
137
+ patch_gapi! :cors_configurations
138
+ end
139
+ end
140
+ cors_builder.freeze # always return frozen objects
141
+ end
142
+
143
+ ##
144
+ # The location of the bucket.
145
+ # Object data for objects in the bucket resides in physical
146
+ # storage within this region. Defaults to US.
147
+ # See the developer's guide for the authoritative list.
148
+ #
149
+ # @see https://cloud.google.com/storage/docs/concepts-techniques
150
+ def location
151
+ @gapi.location
152
+ end
153
+
154
+ ##
155
+ # The destination bucket name for the bucket's logs.
156
+ #
157
+ # @see https://cloud.google.com/storage/docs/access-logs Access Logs
158
+ #
159
+ def logging_bucket
160
+ @gapi.logging.log_bucket if @gapi.logging
161
+ end
162
+
163
+ ##
164
+ # Updates the destination bucket for the bucket's logs.
165
+ #
166
+ # @see https://cloud.google.com/storage/docs/access-logs Access Logs
167
+ #
168
+ # @param [String] logging_bucket The bucket to hold the logging output
169
+ #
170
+ def logging_bucket= logging_bucket
171
+ @gapi.logging ||= Google::Apis::StorageV1::Bucket::Logging.new
172
+ @gapi.logging.log_bucket = logging_bucket
173
+ patch_gapi! :logging
174
+ end
175
+
176
+ ##
177
+ # The logging object prefix for the bucket's logs. For more information,
178
+ #
179
+ # @see https://cloud.google.com/storage/docs/access-logs Access Logs
180
+ #
181
+ def logging_prefix
182
+ @gapi.logging.log_object_prefix if @gapi.logging
183
+ end
184
+
185
+ ##
186
+ # Updates the logging object prefix. This prefix will be used to create
187
+ # log object names for the bucket. It can be at most 900 characters and
188
+ # must be a [valid object
189
+ # name](https://cloud.google.com/storage/docs/bucket-naming#objectnames).
190
+ # By default, the object prefix is the name of the bucket for which the
191
+ # logs are enabled.
192
+ #
193
+ # @see https://cloud.google.com/storage/docs/access-logs Access Logs
194
+ #
195
+ def logging_prefix= logging_prefix
196
+ @gapi.logging ||= Google::Apis::StorageV1::Bucket::Logging.new
197
+ @gapi.logging.log_object_prefix = logging_prefix
198
+ patch_gapi! :logging
199
+ end
200
+
201
+ ##
202
+ # The bucket's storage class. This defines how objects in the bucket are
203
+ # stored and determines the SLA and the cost of storage. Values include
204
+ # `STANDARD`, `NEARLINE`, and `DURABLE_REDUCED_AVAILABILITY`.
205
+ def storage_class
206
+ @gapi.storage_class
207
+ end
208
+
209
+ ##
210
+ # Whether [Object
211
+ # Versioning](https://cloud.google.com/storage/docs/object-versioning)
212
+ # is enabled for the bucket.
213
+ def versioning?
214
+ @gapi.versioning.enabled? unless @gapi.versioning.nil?
215
+ end
216
+
217
+ ##
218
+ # Updates whether [Object
219
+ # Versioning](https://cloud.google.com/storage/docs/object-versioning)
220
+ # is enabled for the bucket.
221
+ #
222
+ # @return [Boolean]
223
+ #
224
+ def versioning= new_versioning
225
+ @gapi.versioning ||= Google::Apis::StorageV1::Bucket::Versioning.new
226
+ @gapi.versioning.enabled = new_versioning
227
+ patch_gapi! :versioning
228
+ end
229
+
230
+ ##
231
+ # The index page returned from a static website served from the bucket
232
+ # when a site visitor requests the top level directory.
233
+ #
234
+ # @see https://cloud.google.com/storage/docs/website-configuration#step4
235
+ # How to Host a Static Website
236
+ #
237
+ def website_main
238
+ @gapi.website.main_page_suffix if @gapi.website
239
+ end
240
+
241
+ ##
242
+ # Updates the index page returned from a static website served from the
243
+ # bucket when a site visitor requests the top level directory.
244
+ #
245
+ # @see https://cloud.google.com/storage/docs/website-configuration#step4
246
+ # How to Host a Static Website
247
+ #
248
+ def website_main= website_main
249
+ @gapi.website ||= Google::Apis::StorageV1::Bucket::Website.new
250
+ @gapi.website.main_page_suffix = website_main
251
+ patch_gapi! :website
252
+ end
253
+
254
+ ##
255
+ # The page returned from a static website served from the bucket when a
256
+ # site visitor requests a resource that does not exist.
257
+ #
258
+ # @see https://cloud.google.com/storage/docs/website-configuration#step4
259
+ # How to Host a Static Website
260
+ #
261
+ def website_404
262
+ @gapi.website.not_found_page if @gapi.website
263
+ end
264
+
265
+ ##
266
+ # Updates the page returned from a static website served from the bucket
267
+ # when a site visitor requests a resource that does not exist.
268
+ #
269
+ # @see https://cloud.google.com/storage/docs/website-configuration#step4
270
+ # How to Host a Static Website
271
+ #
272
+ def website_404= website_404
273
+ @gapi.website ||= Google::Apis::StorageV1::Bucket::Website.new
274
+ @gapi.website.not_found_page = website_404
275
+ patch_gapi! :website
276
+ end
277
+
278
+ ##
279
+ # Updates the bucket with changes made in the given block in a single
280
+ # PATCH request. The following attributes may be set: {#cors},
281
+ # {#logging_bucket=}, {#logging_prefix=}, {#versioning=},
282
+ # {#website_main=}, and {#website_404=}. In addition, the #cors
283
+ # configuration accessible in the block is completely mutable and will
284
+ # be included in the request. (See {Bucket::Cors})
285
+ #
286
+ # @yield [bucket] a block yielding a delegate object for updating the
287
+ # file
288
+ #
289
+ # @example
290
+ # require "google/cloud"
291
+ #
292
+ # gcloud = Google::Cloud.new
293
+ # storage = gcloud.storage
294
+ #
295
+ # bucket = storage.bucket "my-bucket"
296
+ # bucket.update do |b|
297
+ # b.website_main = "index.html"
298
+ # b.website_404 = "not_found.html"
299
+ # b.cors[0]["method"] = ["GET","POST","DELETE"]
300
+ # b.cors[1]["responseHeader"] << "X-Another-Custom-Header"
301
+ # end
302
+ #
303
+ # @example New CORS rules can also be added in a nested block:
304
+ # require "google/cloud"
305
+ #
306
+ # gcloud = Google::Cloud.new
307
+ # storage = gcloud.storage
308
+ # bucket = storage.bucket "my-todo-app"
309
+ #
310
+ # bucket.update do |b|
311
+ # b.cors do |c|
312
+ # c.add_rule ["http://example.org", "https://example.org"],
313
+ # "*",
314
+ # response_headers: ["X-My-Custom-Header"],
315
+ # max_age: 300
316
+ # end
317
+ # end
318
+ #
319
+ def update
320
+ updater = Updater.new @gapi
321
+ yield updater
322
+ # Add check for mutable cors
323
+ updater.check_for_mutable_cors!
324
+ patch_gapi! updater.updates unless updater.updates.empty?
325
+ end
326
+
327
+ ##
328
+ # Permanently deletes the bucket.
329
+ # The bucket must be empty before it can be deleted.
330
+ #
331
+ # The API call to delete the bucket may be retried under certain
332
+ # conditions. See {Google::Cloud#storage} to control this behavior.
333
+ #
334
+ # @return [Boolean] Returns `true` if the bucket was deleted.
335
+ #
336
+ # @example
337
+ # require "google/cloud"
338
+ #
339
+ # gcloud = Google::Cloud.new
340
+ # storage = gcloud.storage
341
+ #
342
+ # bucket = storage.bucket "my-bucket"
343
+ # bucket.delete
344
+ #
345
+ def delete
346
+ ensure_service!
347
+ service.delete_bucket name
348
+ true
349
+ end
350
+
351
+ ##
352
+ # Retrieves a list of files matching the criteria.
353
+ #
354
+ # @param [String] prefix Filter results to files whose names begin with
355
+ # this prefix.
356
+ # @param [String] delimiter Returns results in a directory-like mode.
357
+ # `items` will contain only objects whose names, aside from the
358
+ # `prefix`, do not contain `delimiter`. Objects whose names, aside
359
+ # from the `prefix`, contain `delimiter` will have their name,
360
+ # truncated after the `delimiter`, returned in `prefixes`. Duplicate
361
+ # `prefixes` are omitted.
362
+ # @param [String] token A previously-returned page token representing
363
+ # part of the larger set of results to view.
364
+ # @param [Integer] max Maximum number of items plus prefixes to return.
365
+ # As duplicate prefixes are omitted, fewer total results may be
366
+ # returned than requested. The default value of this parameter is
367
+ # 1,000 items.
368
+ # @param [Boolean] versions If `true`, lists all versions of an object
369
+ # as distinct results. The default is `false`. For more information,
370
+ # see [Object Versioning
371
+ # ](https://cloud.google.com/storage/docs/object-versioning).
372
+ #
373
+ # @return [Array<Google::Cloud::Storage::File>] (See
374
+ # {Google::Cloud::Storage::File::List})
375
+ #
376
+ # @example
377
+ # require "google/cloud"
378
+ #
379
+ # gcloud = Google::Cloud.new
380
+ # storage = gcloud.storage
381
+ #
382
+ # bucket = storage.bucket "my-bucket"
383
+ # files = bucket.files
384
+ # files.each do |file|
385
+ # puts file.name
386
+ # end
387
+ #
388
+ # @example Retrieve all files: (See {File::List#all})
389
+ # require "google/cloud"
390
+ #
391
+ # gcloud = Google::Cloud.new
392
+ # storage = gcloud.storage
393
+ #
394
+ # bucket = storage.bucket "my-bucket"
395
+ # files = bucket.files
396
+ # files.all do |file|
397
+ # puts file.name
398
+ # end
399
+ #
400
+ def files prefix: nil, delimiter: nil, token: nil, max: nil,
401
+ versions: nil
402
+ ensure_service!
403
+ options = {
404
+ prefix: prefix,
405
+ delimiter: delimiter,
406
+ token: token,
407
+ max: max,
408
+ versions: versions
409
+ }
410
+ gapi = service.list_files name, options
411
+ File::List.from_gapi gapi, service, name, prefix, delimiter, max,
412
+ versions
413
+ end
414
+ alias_method :find_files, :files
415
+
416
+ ##
417
+ # Retrieves a file matching the path.
418
+ #
419
+ # If a [customer-supplied encryption
420
+ # key](https://cloud.google.com/storage/docs/encryption#customer-supplied)
421
+ # was used with {#create_file}, the `encryption_key` and
422
+ # `encryption_key_sha256` options must be provided or else the file's
423
+ # CRC32C checksum and MD5 hash will not be returned.
424
+ #
425
+ # @param [String] path Name (path) of the file.
426
+ # @param [Integer] generation When present, selects a specific revision
427
+ # of this object. Default is the latest version.
428
+ # @param [String] encryption_key Optional. The customer-supplied,
429
+ # AES-256 encryption key used to encrypt the file, if one was provided
430
+ # to {#create_file}. Must be provided if `encryption_key_sha256` is
431
+ # provided.
432
+ # @param [String] encryption_key_sha256 Optional. The SHA256 hash of the
433
+ # customer-supplied, AES-256 encryption key used to encrypt the file,
434
+ # if one was provided to {#create_file}. Must be provided if
435
+ # `encryption_key` is provided.
436
+ #
437
+ # @return [Google::Cloud::Storage::File, nil] Returns nil if file does
438
+ # not exist
439
+ #
440
+ # @example
441
+ # require "google/cloud"
442
+ #
443
+ # gcloud = Google::Cloud.new
444
+ # storage = gcloud.storage
445
+ #
446
+ # bucket = storage.bucket "my-bucket"
447
+ #
448
+ # file = bucket.file "path/to/my-file.ext"
449
+ # puts file.name
450
+ #
451
+ def file path, generation: nil, encryption_key: nil,
452
+ encryption_key_sha256: nil
453
+ ensure_service!
454
+ options = { generation: generation, key: encryption_key,
455
+ key_sha256: encryption_key_sha256 }
456
+ gapi = service.get_file name, path, options
457
+ File.from_gapi gapi, service
458
+ rescue Google::Cloud::NotFoundError
459
+ nil
460
+ end
461
+ alias_method :find_file, :file
462
+
463
+ ##
464
+ # Creates a new {File} object by providing a path to a local file to
465
+ # upload and the path to store it with in the bucket.
466
+ #
467
+ # #### Customer-supplied encryption keys
468
+ #
469
+ # By default, Google Cloud Storage manages server-side encryption keys
470
+ # on your behalf. However, a [customer-supplied encryption
471
+ # key](https://cloud.google.com/storage/docs/encryption#customer-supplied)
472
+ # can be provided with the `encryption_key` and `encryption_key_sha256`
473
+ # options. If given, the same key and SHA256 hash also must be provided
474
+ # to subsequently download or copy the file. If you use
475
+ # customer-supplied encryption keys, you must securely manage your keys
476
+ # and ensure that they are not lost. Also, please note that file
477
+ # metadata is not encrypted, with the exception of the CRC32C checksum
478
+ # and MD5 hash. The names of files and buckets are also not encrypted,
479
+ # and you can read or update the metadata of an encrypted file without
480
+ # providing the encryption key.
481
+ #
482
+ # @param [String] file Path of the file on the filesystem to upload.
483
+ # @param [String] path Path to store the file in Google Cloud Storage.
484
+ # @param [String] acl A predefined set of access controls to apply to
485
+ # this file.
486
+ #
487
+ # Acceptable values are:
488
+ #
489
+ # * `auth`, `auth_read`, `authenticated`, `authenticated_read`,
490
+ # `authenticatedRead` - File owner gets OWNER access, and
491
+ # allAuthenticatedUsers get READER access.
492
+ # * `owner_full`, `bucketOwnerFullControl` - File owner gets OWNER
493
+ # access, and project team owners get OWNER access.
494
+ # * `owner_read`, `bucketOwnerRead` - File owner gets OWNER access,
495
+ # and project team owners get READER access.
496
+ # * `private` - File owner gets OWNER access.
497
+ # * `project_private`, `projectPrivate` - File owner gets OWNER
498
+ # access, and project team members get access according to their
499
+ # roles.
500
+ # * `public`, `public_read`, `publicRead` - File owner gets OWNER
501
+ # access, and allUsers get READER access.
502
+ # @param [String] cache_control The
503
+ # [Cache-Control](https://tools.ietf.org/html/rfc7234#section-5.2)
504
+ # response header to be returned when the file is downloaded.
505
+ # @param [String] content_disposition The
506
+ # [Content-Disposition](https://tools.ietf.org/html/rfc6266)
507
+ # response header to be returned when the file is downloaded.
508
+ # @param [String] content_encoding The [Content-Encoding
509
+ # ](https://tools.ietf.org/html/rfc7231#section-3.1.2.2) response
510
+ # header to be returned when the file is downloaded.
511
+ # @param [String] content_language The
512
+ # [Content-Language](http://tools.ietf.org/html/bcp47) response
513
+ # header to be returned when the file is downloaded.
514
+ # @param [String] content_type The
515
+ # [Content-Type](https://tools.ietf.org/html/rfc2616#section-14.17)
516
+ # response header to be returned when the file is downloaded.
517
+ # @param [String] crc32c The CRC32c checksum of the file data, as
518
+ # described in [RFC 4960, Appendix
519
+ # B](http://tools.ietf.org/html/rfc4960#appendix-B).
520
+ # If provided, Cloud Storage will only create the file if the value
521
+ # matches the value calculated by the service. See
522
+ # [Validation](https://cloud.google.com/storage/docs/hashes-etags)
523
+ # for more information.
524
+ # @param [String] md5 The MD5 hash of the file data. If provided, Cloud
525
+ # Storage will only create the file if the value matches the value
526
+ # calculated by the service. See
527
+ # [Validation](https://cloud.google.com/storage/docs/hashes-etags) for
528
+ # more information.
529
+ # @param [Hash] metadata A hash of custom, user-provided web-safe keys
530
+ # and arbitrary string values that will returned with requests for the
531
+ # file as "x-goog-meta-" response headers.
532
+ # @param [String] encryption_key Optional. A customer-supplied, AES-256
533
+ # encryption key that will be used to encrypt the file. Must be
534
+ # provided if `encryption_key_sha256` is provided.
535
+ # @param [String] encryption_key_sha256 Optional. The SHA256 hash of the
536
+ # customer-supplied, AES-256 encryption key that will be used to
537
+ # encrypt the file. Must be provided if `encryption_key` is provided.
538
+ #
539
+ # @return [Google::Cloud::Storage::File]
540
+ #
541
+ # @example
542
+ # require "google/cloud"
543
+ #
544
+ # gcloud = Google::Cloud.new
545
+ # storage = gcloud.storage
546
+ #
547
+ # bucket = storage.bucket "my-bucket"
548
+ #
549
+ # bucket.create_file "path/to/local.file.ext"
550
+ #
551
+ # @example Specifying a destination path:
552
+ # require "google/cloud"
553
+ #
554
+ # gcloud = Google::Cloud.new
555
+ # storage = gcloud.storage
556
+ #
557
+ # bucket = storage.bucket "my-bucket"
558
+ #
559
+ # bucket.create_file "path/to/local.file.ext",
560
+ # "destination/path/file.ext"
561
+ #
562
+ # @example Providing a customer-supplied encryption key:
563
+ # require "google/cloud"
564
+ # require "digest/sha2"
565
+ #
566
+ # gcloud = Google::Cloud.new
567
+ # storage = gcloud.storage
568
+ # bucket = storage.bucket "my-bucket"
569
+ #
570
+ # # Key generation shown for example purposes only. Write your own.
571
+ # cipher = OpenSSL::Cipher.new "aes-256-cfb"
572
+ # cipher.encrypt
573
+ # key = cipher.random_key
574
+ # key_hash = Digest::SHA256.digest key
575
+ #
576
+ # bucket.create_file "path/to/local.file.ext",
577
+ # "destination/path/file.ext",
578
+ # encryption_key: key,
579
+ # encryption_key_sha256: key_hash
580
+ #
581
+ # # Store your key and hash securely for later use.
582
+ # file = bucket.file "destination/path/file.ext",
583
+ # encryption_key: key,
584
+ # encryption_key_sha256: key_hash
585
+ #
586
+ def create_file file, path = nil, acl: nil, cache_control: nil,
587
+ content_disposition: nil, content_encoding: nil,
588
+ content_language: nil, content_type: nil,
589
+ crc32c: nil, md5: nil, metadata: nil,
590
+ encryption_key: nil, encryption_key_sha256: nil
591
+ ensure_service!
592
+ options = { acl: File::Acl.predefined_rule_for(acl), md5: md5,
593
+ cache_control: cache_control, content_type: content_type,
594
+ content_disposition: content_disposition, crc32c: crc32c,
595
+ content_encoding: content_encoding,
596
+ content_language: content_language, metadata: metadata,
597
+ key: encryption_key, key_sha256: encryption_key_sha256 }
598
+ ensure_file_exists! file
599
+ # TODO: Handle file as an IO and path is missing more gracefully
600
+ path ||= Pathname(file).to_path
601
+ gapi = service.insert_file name, file, path, options
602
+ File.from_gapi gapi, service
603
+ end
604
+ alias_method :upload_file, :create_file
605
+ alias_method :new_file, :create_file
606
+
607
+ ##
608
+ # The Bucket::Acl instance used to control access to the bucket.
609
+ #
610
+ # A bucket has owners, writers, and readers. Permissions can be granted
611
+ # to an individual user's email address, a group's email address, as
612
+ # well as many predefined lists.
613
+ #
614
+ # @see https://cloud.google.com/storage/docs/access-control Access
615
+ # Control guide
616
+ #
617
+ # @example Grant access to a user by prepending `"user-"` to an email:
618
+ # require "google/cloud"
619
+ #
620
+ # gcloud = Google::Cloud.new
621
+ # storage = gcloud.storage
622
+ #
623
+ # bucket = storage.bucket "my-todo-app"
624
+ #
625
+ # email = "heidi@example.net"
626
+ # bucket.acl.add_reader "user-#{email}"
627
+ #
628
+ # @example Grant access to a group by prepending `"group-"` to an email:
629
+ # require "google/cloud"
630
+ #
631
+ # gcloud = Google::Cloud.new
632
+ # storage = gcloud.storage
633
+ #
634
+ # bucket = storage.bucket "my-todo-app"
635
+ #
636
+ # email = "authors@example.net"
637
+ # bucket.acl.add_reader "group-#{email}"
638
+ #
639
+ # @example Or, grant access via a predefined permissions list:
640
+ # require "google/cloud"
641
+ #
642
+ # gcloud = Google::Cloud.new
643
+ # storage = gcloud.storage
644
+ #
645
+ # bucket = storage.bucket "my-todo-app"
646
+ #
647
+ # bucket.acl.public!
648
+ #
649
+ def acl
650
+ @acl ||= Bucket::Acl.new self
651
+ end
652
+
653
+ ##
654
+ # The Bucket::DefaultAcl instance used to control access to the bucket's
655
+ # files.
656
+ #
657
+ # A bucket's files have owners, writers, and readers. Permissions can be
658
+ # granted to an individual user's email address, a group's email
659
+ # address, as well as many predefined lists.
660
+ #
661
+ # @see https://cloud.google.com/storage/docs/access-control Access
662
+ # Control guide
663
+ #
664
+ # @example Grant access to a user by prepending `"user-"` to an email:
665
+ # require "google/cloud"
666
+ #
667
+ # gcloud = Google::Cloud.new
668
+ # storage = gcloud.storage
669
+ #
670
+ # bucket = storage.bucket "my-todo-app"
671
+ #
672
+ # email = "heidi@example.net"
673
+ # bucket.default_acl.add_reader "user-#{email}"
674
+ #
675
+ # @example Grant access to a group by prepending `"group-"` to an email
676
+ # require "google/cloud"
677
+ #
678
+ # gcloud = Google::Cloud.new
679
+ # storage = gcloud.storage
680
+ #
681
+ # bucket = storage.bucket "my-todo-app"
682
+ #
683
+ # email = "authors@example.net"
684
+ # bucket.default_acl.add_reader "group-#{email}"
685
+ #
686
+ # @example Or, grant access via a predefined permissions list:
687
+ # require "google/cloud"
688
+ #
689
+ # gcloud = Google::Cloud.new
690
+ # storage = gcloud.storage
691
+ #
692
+ # bucket = storage.bucket "my-todo-app"
693
+ #
694
+ # bucket.default_acl.public!
695
+ #
696
+ def default_acl
697
+ @default_acl ||= Bucket::DefaultAcl.new self
698
+ end
699
+
700
+ ##
701
+ # Reloads the bucket with current data from the Storage service.
702
+ def reload!
703
+ ensure_service!
704
+ @gapi = service.get_bucket name
705
+ end
706
+ alias_method :refresh!, :reload!
707
+
708
+ ##
709
+ # @private New Bucket from a Google API Client object.
710
+ def self.from_gapi gapi, conn
711
+ new.tap do |f|
712
+ f.gapi = gapi
713
+ f.service = conn
714
+ end
715
+ end
716
+
717
+ protected
718
+
719
+ ##
720
+ # Raise an error unless an active service is available.
721
+ def ensure_service!
722
+ fail "Must have active connection" unless service
723
+ end
724
+
725
+ def patch_gapi! *attributes
726
+ attributes.flatten!
727
+ return if attributes.empty?
728
+ ensure_service!
729
+ patch_args = Hash[attributes.map do |attr|
730
+ [attr, @gapi.send(attr)]
731
+ end]
732
+ patch_gapi = Google::Apis::StorageV1::Bucket.new patch_args
733
+ @gapi = service.patch_bucket name, patch_gapi
734
+ end
735
+
736
+ ##
737
+ # Raise an error if the file is not found.
738
+ def ensure_file_exists! file
739
+ return if ::File.file? file
740
+ fail ArgumentError, "cannot find file #{file}"
741
+ end
742
+
743
+ ##
744
+ # Yielded to a block to accumulate changes for a patch request.
745
+ class Updater < Bucket
746
+ attr_reader :updates
747
+ ##
748
+ # Create an Updater object.
749
+ def initialize gapi
750
+ @updates = []
751
+ @gapi = gapi
752
+ @cors_builder = nil
753
+ end
754
+
755
+ def cors
756
+ # Same as Bucket#cors, but not frozen
757
+ @cors_builder ||= Bucket::Cors.from_gapi @gapi.cors_configurations
758
+ yield @cors_builder if block_given?
759
+ @cors_builder
760
+ end
761
+
762
+ ##
763
+ # @private Make sure any cors changes are saved
764
+ def check_for_mutable_cors!
765
+ return if @cors_builder.nil?
766
+ return unless @cors_builder.changed?
767
+ @gapi.cors_configurations = @cors_builder.to_gapi
768
+ patch_gapi! :cors_configurations
769
+ end
770
+
771
+ protected
772
+
773
+ ##
774
+ # Queue up all the updates instead of making them.
775
+ def patch_gapi! attribute
776
+ @updates << attribute
777
+ @updates.uniq!
778
+ end
779
+ end
780
+ end
781
+ end
782
+ end
783
+ end