google-cloud-storage 1.18.1 → 1.44.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHENTICATION.md +17 -30
  3. data/CHANGELOG.md +312 -0
  4. data/CONTRIBUTING.md +4 -5
  5. data/LOGGING.md +1 -1
  6. data/OVERVIEW.md +37 -5
  7. data/TROUBLESHOOTING.md +2 -8
  8. data/lib/google/cloud/storage/bucket/acl.rb +40 -40
  9. data/lib/google/cloud/storage/bucket/cors.rb +4 -1
  10. data/lib/google/cloud/storage/bucket/lifecycle.rb +259 -44
  11. data/lib/google/cloud/storage/bucket/list.rb +3 -3
  12. data/lib/google/cloud/storage/bucket.rb +1096 -172
  13. data/lib/google/cloud/storage/convert.rb +4 -3
  14. data/lib/google/cloud/storage/credentials.rb +16 -14
  15. data/lib/google/cloud/storage/errors.rb +7 -2
  16. data/lib/google/cloud/storage/file/acl.rb +181 -20
  17. data/lib/google/cloud/storage/file/list.rb +10 -8
  18. data/lib/google/cloud/storage/file/signer_v2.rb +36 -18
  19. data/lib/google/cloud/storage/file/signer_v4.rb +249 -61
  20. data/lib/google/cloud/storage/file/verifier.rb +2 -2
  21. data/lib/google/cloud/storage/file.rb +450 -84
  22. data/lib/google/cloud/storage/hmac_key/list.rb +182 -0
  23. data/lib/google/cloud/storage/hmac_key.rb +316 -0
  24. data/lib/google/cloud/storage/policy/binding.rb +246 -0
  25. data/lib/google/cloud/storage/policy/bindings.rb +196 -0
  26. data/lib/google/cloud/storage/policy/condition.rb +138 -0
  27. data/lib/google/cloud/storage/policy.rb +277 -24
  28. data/lib/google/cloud/storage/post_object.rb +20 -2
  29. data/lib/google/cloud/storage/project.rb +249 -50
  30. data/lib/google/cloud/storage/service.rb +479 -288
  31. data/lib/google/cloud/storage/version.rb +1 -1
  32. data/lib/google/cloud/storage.rb +86 -16
  33. data/lib/google-cloud-storage.rb +54 -7
  34. metadata +74 -27
@@ -191,7 +191,7 @@ module Google
191
191
  # @return [Integer]
192
192
  #
193
193
  def size
194
- @gapi.size.to_i if @gapi.size
194
+ @gapi.size&.to_i
195
195
  end
196
196
 
197
197
  ##
@@ -260,6 +260,9 @@ module Google
260
260
  # directive for the file data. If omitted, and the file is accessible
261
261
  # to all anonymous users, the default will be `public, max-age=3600`.
262
262
  #
263
+ # To pass generation and/or metageneration preconditions, call this
264
+ # method within a block passed to {#update}.
265
+ #
263
266
  # @param [String] cache_control The Cache-Control directive.
264
267
  #
265
268
  def cache_control= cache_control
@@ -281,6 +284,9 @@ module Google
281
284
  # Updates the [Content-Disposition](https://tools.ietf.org/html/rfc6266)
282
285
  # of the file data.
283
286
  #
287
+ # To pass generation and/or metageneration preconditions, call this
288
+ # method within a block passed to {#update}.
289
+ #
284
290
  # @param [String] content_disposition The Content-Disposition of the
285
291
  # file.
286
292
  #
@@ -305,6 +311,9 @@ module Google
305
311
  # ](https://tools.ietf.org/html/rfc7231#section-3.1.2.2) of the file
306
312
  # data.
307
313
  #
314
+ # To pass generation and/or metageneration preconditions, call this
315
+ # method within a block passed to {#update}.
316
+ #
308
317
  # @param [String] content_encoding The Content-Encoding of the file.
309
318
  #
310
319
  def content_encoding= content_encoding
@@ -326,6 +335,9 @@ module Google
326
335
  # Updates the [Content-Language](http://tools.ietf.org/html/bcp47) of
327
336
  # the file data.
328
337
  #
338
+ # To pass generation and/or metageneration preconditions, call this
339
+ # method within a block passed to {#update}.
340
+ #
329
341
  # @param [String] content_language The Content-Language of the file.
330
342
  #
331
343
  def content_language= content_language
@@ -348,6 +360,9 @@ module Google
348
360
  # [Content-Type](https://tools.ietf.org/html/rfc2616#section-14.17) of
349
361
  # the file data.
350
362
  #
363
+ # To pass generation and/or metageneration preconditions, call this
364
+ # method within a block passed to {#update}.
365
+ #
351
366
  # @param [String] content_type The Content-Type of the file.
352
367
  #
353
368
  def content_type= content_type
@@ -355,6 +370,32 @@ module Google
355
370
  update_gapi! :content_type
356
371
  end
357
372
 
373
+ ##
374
+ # A custom time specified by the user for the file, or `nil`.
375
+ #
376
+ # @return [DateTime, nil]
377
+ #
378
+ def custom_time
379
+ @gapi.custom_time
380
+ end
381
+
382
+ ##
383
+ # Updates the custom time specified by the user for the file. Once set,
384
+ # custom_time can't be unset, and it can only be changed to a time in the
385
+ # future. If custom_time must be unset, you must either perform a rewrite
386
+ # operation, or upload the data again and create a new file.
387
+ #
388
+ # To pass generation and/or metageneration preconditions, call this
389
+ # method within a block passed to {#update}.
390
+ #
391
+ # @param [DateTime] custom_time A custom time specified by the user
392
+ # for the file.
393
+ #
394
+ def custom_time= custom_time
395
+ @gapi.custom_time = custom_time
396
+ update_gapi! :custom_time
397
+ end
398
+
358
399
  ##
359
400
  # A hash of custom, user-provided web-safe keys and arbitrary string
360
401
  # values that will returned with requests for the file as "x-goog-meta-"
@@ -373,6 +414,9 @@ module Google
373
414
  # string values that will returned with requests for the file as
374
415
  # "x-goog-meta-" response headers.
375
416
  #
417
+ # To pass generation and/or metageneration preconditions, call this
418
+ # method within a block passed to {#update}.
419
+ #
376
420
  # @param [Hash(String => String)] metadata The user-provided metadata,
377
421
  # in key/value pairs.
378
422
  #
@@ -389,7 +433,8 @@ module Google
389
433
  # You can use this SHA256 hash to uniquely identify the AES-256
390
434
  # encryption key required to decrypt this file.
391
435
  #
392
- # @return [String]
436
+ # @return [String, nil] The encoded SHA256 hash, or `nil` if there is
437
+ # no customer-supplied encryption key for this file.
393
438
  #
394
439
  def encryption_key_sha256
395
440
  return nil unless @gapi.customer_encryption
@@ -428,10 +473,10 @@ module Google
428
473
  # Rewrites the file with a new storage class, which determines the SLA
429
474
  # and the cost of storage. Accepted values include:
430
475
  #
431
- # * `:multi_regional`
432
- # * `:regional`
476
+ # * `:standard`
433
477
  # * `:nearline`
434
478
  # * `:coldline`
479
+ # * `:archive`
435
480
  #
436
481
  # as well as the equivalent strings returned by {File#storage_class} or
437
482
  # {Bucket#storage_class}. For more information, see [Storage
@@ -441,6 +486,9 @@ module Google
441
486
  # The default value is the default storage class for the bucket. See
442
487
  # {Bucket#storage_class}.
443
488
  #
489
+ # To pass generation and/or metageneration preconditions, call this
490
+ # method within a block passed to {#update}.
491
+ #
444
492
  # @param [Symbol, String] storage_class Storage class of the file.
445
493
  #
446
494
  def storage_class= storage_class
@@ -481,6 +529,9 @@ module Google
481
529
  # removed, the file's `retention_expires_at` date is not changed. The
482
530
  # default value is `false`.
483
531
  #
532
+ # To pass generation and/or metageneration preconditions, call this
533
+ # method within a block passed to {#update}.
534
+ #
484
535
  # See {#retention_expires_at}.
485
536
  #
486
537
  # @example
@@ -509,6 +560,9 @@ module Google
509
560
  #
510
561
  # See {#retention_expires_at}.
511
562
  #
563
+ # To pass generation and/or metageneration preconditions, call this
564
+ # method within a block passed to {#update}.
565
+ #
512
566
  # @example
513
567
  # require "google/cloud/storage"
514
568
  #
@@ -619,6 +673,9 @@ module Google
619
673
  # holds released prior to the effective date of the new policy may
620
674
  # have already been deleted by the user.
621
675
  #
676
+ # To pass generation and/or metageneration preconditions, call this
677
+ # method within a block passed to {#update}.
678
+ #
622
679
  # @example
623
680
  # require "google/cloud/storage"
624
681
  #
@@ -657,6 +714,9 @@ module Google
657
714
  # {Bucket#default_event_based_hold?} and
658
715
  # {Bucket#default_event_based_hold=}.
659
716
  #
717
+ # To pass generation and/or metageneration preconditions, call this
718
+ # method within a block passed to {#update}.
719
+ #
660
720
  # @example
661
721
  # require "google/cloud/storage"
662
722
  #
@@ -746,8 +806,25 @@ module Google
746
806
  # Updates the file with changes made in the given block in a single
747
807
  # PATCH request. The following attributes may be set: {#cache_control=},
748
808
  # {#content_disposition=}, {#content_encoding=}, {#content_language=},
749
- # {#content_type=}, and {#metadata=}. The {#metadata} hash accessible in
750
- # the block is completely mutable and will be included in the request.
809
+ # {#content_type=}, {#custom_time=} and {#metadata=}. The {#metadata} hash
810
+ # accessible in the block is completely mutable and will be included in the
811
+ # request.
812
+ #
813
+ # @param [Integer] generation Select a specific revision of the file to
814
+ # update. The default is the latest version.
815
+ # @param [Integer] if_generation_match Makes the operation conditional
816
+ # on whether the file's current generation matches the given value.
817
+ # Setting to 0 makes the operation succeed only if there are no live
818
+ # versions of the file.
819
+ # @param [Integer] if_generation_not_match Makes the operation conditional
820
+ # on whether the file's current generation does not match the given
821
+ # value. If no live file exists, the precondition fails. Setting to 0
822
+ # makes the operation succeed only if there is a live version of the file.
823
+ # @param [Integer] if_metageneration_match Makes the operation conditional
824
+ # on whether the file's current metageneration matches the given value.
825
+ # @param [Integer] if_metageneration_not_match Makes the operation
826
+ # conditional on whether the file's current metageneration does not
827
+ # match the given value.
751
828
  #
752
829
  # @yield [file] a block yielding a delegate object for updating the file
753
830
  #
@@ -766,19 +843,43 @@ module Google
766
843
  # f.content_encoding = "deflate"
767
844
  # f.content_language = "de"
768
845
  # f.content_type = "application/json"
846
+ # f.custom_time = DateTime.new 2025, 12, 31
769
847
  # f.metadata["player"] = "Bob"
770
848
  # f.metadata["score"] = "10"
771
849
  # end
772
850
  #
773
- def update
851
+ # @example With a `if_generation_match` precondition:
852
+ # require "google/cloud/storage"
853
+ #
854
+ # storage = Google::Cloud::Storage.new
855
+ #
856
+ # bucket = storage.bucket "my-bucket"
857
+ #
858
+ # file = bucket.file "path/to/my-file.ext"
859
+ #
860
+ # file.update if_generation_match: 1602263125261858 do |f|
861
+ # f.cache_control = "private, max-age=0, no-cache"
862
+ # end
863
+ #
864
+ def update generation: nil,
865
+ if_generation_match: nil,
866
+ if_generation_not_match: nil,
867
+ if_metageneration_match: nil,
868
+ if_metageneration_not_match: nil
774
869
  updater = Updater.new gapi
775
870
  yield updater
776
871
  updater.check_for_changed_metadata!
777
- update_gapi! updater.updates unless updater.updates.empty?
872
+ return if updater.updates.empty?
873
+ update_gapi! updater.updates,
874
+ generation: generation,
875
+ if_generation_match: if_generation_match,
876
+ if_generation_not_match: if_generation_not_match,
877
+ if_metageneration_match: if_metageneration_match,
878
+ if_metageneration_not_match: if_metageneration_not_match
778
879
  end
779
880
 
780
881
  ##
781
- # Download the file's contents to a local file or an File-like object.
882
+ # Downloads the file's contents to a local file or an File-like object.
782
883
  #
783
884
  # By default, the download is verified by calculating the MD5 digest.
784
885
  #
@@ -938,8 +1039,8 @@ module Google
938
1039
  end
939
1040
  file, resp =
940
1041
  service.download_file bucket, name, path,
941
- key: encryption_key, range: range,
942
- user_project: user_project
1042
+ generation: generation, key: encryption_key,
1043
+ range: range, user_project: user_project
943
1044
  # FIX: downloading with encryption key will return nil
944
1045
  file ||= ::File.new path
945
1046
  verify = :none if range
@@ -952,7 +1053,15 @@ module Google
952
1053
  end
953
1054
 
954
1055
  ##
955
- # Copy the file to a new location.
1056
+ # Copies the file to a new location. Metadata excluding ACL from the source
1057
+ # object will be copied to the destination object unless a block is provided.
1058
+ #
1059
+ # If an optional block for updating is provided, only the updates made in
1060
+ # this block will appear in the destination object, and other metadata
1061
+ # fields in the destination object will not be copied. To copy the other
1062
+ # source file metadata fields while updating destination fields in a
1063
+ # block, use the `force_copy_metadata: true` flag, and the client library
1064
+ # will copy metadata from source metadata into the copy request.
956
1065
  #
957
1066
  # If a [customer-supplied encryption
958
1067
  # key](https://cloud.google.com/storage/docs/encryption#customer-supplied)
@@ -987,6 +1096,19 @@ module Google
987
1096
  # @param [String] encryption_key Optional. The customer-supplied,
988
1097
  # AES-256 encryption key used to encrypt the file, if one was provided
989
1098
  # to {Bucket#create_file}.
1099
+ # @param [Boolean] force_copy_metadata Optional. If `true` and if updates
1100
+ # are made in a block, the following fields will be copied from the
1101
+ # source file to the destination file (except when changed by updates):
1102
+ #
1103
+ # * `cache_control`
1104
+ # * `content_disposition`
1105
+ # * `content_encoding`
1106
+ # * `content_language`
1107
+ # * `content_type`
1108
+ # * `metadata`
1109
+ #
1110
+ # If `nil` or `false`, only the updates made in the yielded block will
1111
+ # be applied to the destination object. The default is `nil`.
990
1112
  # @yield [file] a block yielding a delegate object for updating
991
1113
  #
992
1114
  # @return [Google::Cloud::Storage::File]
@@ -1036,12 +1158,13 @@ module Google
1036
1158
  # f.metadata["copied_from"] = "#{file.bucket}/#{file.name}"
1037
1159
  # end
1038
1160
  #
1039
- def copy dest_bucket_or_path, dest_path = nil,
1040
- acl: nil, generation: nil, encryption_key: nil
1161
+ def copy dest_bucket_or_path, dest_path = nil, acl: nil, generation: nil, encryption_key: nil,
1162
+ force_copy_metadata: nil
1041
1163
  rewrite dest_bucket_or_path, dest_path,
1042
1164
  acl: acl, generation: generation,
1043
1165
  encryption_key: encryption_key,
1044
- new_encryption_key: encryption_key do |updater|
1166
+ new_encryption_key: encryption_key,
1167
+ force_copy_metadata: force_copy_metadata do |updater|
1045
1168
  yield updater if block_given?
1046
1169
  end
1047
1170
  end
@@ -1049,7 +1172,15 @@ module Google
1049
1172
  ##
1050
1173
  # [Rewrites](https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite)
1051
1174
  # the file to a new location. Or the same location can be provided to
1052
- # rewrite the file in place.
1175
+ # rewrite the file in place. Metadata from the source object will
1176
+ # be copied to the destination object unless a block is provided.
1177
+ #
1178
+ # If an optional block for updating is provided, only the updates made in
1179
+ # this block will appear in the destination object, and other metadata
1180
+ # fields in the destination object will not be copied. To copy the other
1181
+ # source file metadata fields while updating destination fields in a
1182
+ # block, use the `force_copy_metadata: true` flag, and the client library
1183
+ # will copy metadata from source metadata into the copy request.
1053
1184
  #
1054
1185
  # If a [customer-supplied encryption
1055
1186
  # key](https://cloud.google.com/storage/docs/encryption#customer-supplied)
@@ -1082,6 +1213,27 @@ module Google
1082
1213
  # access, and allUsers get READER access.
1083
1214
  # @param [Integer] generation Select a specific revision of the file to
1084
1215
  # rewrite. The default is the latest version.
1216
+ # @param [Integer] if_generation_match Makes the operation conditional
1217
+ # on whether the destination file's current generation matches the given value.
1218
+ # Setting to 0 makes the operation succeed only if there are no live
1219
+ # versions of the file.
1220
+ # @param [Integer] if_generation_not_match Makes the operation conditional
1221
+ # on whether the destination file's current generation does not match the given
1222
+ # value. If no live file exists, the precondition fails. Setting to 0
1223
+ # makes the operation succeed only if there is a live version of the file.
1224
+ # @param [Integer] if_metageneration_match Makes the operation conditional
1225
+ # on whether the destination file's current metageneration matches the given value.
1226
+ # @param [Integer] if_metageneration_not_match Makes the operation
1227
+ # conditional on whether the destination file's current metageneration does not
1228
+ # match the given value.
1229
+ # @param [Integer] if_source_generation_match Makes the operation conditional on
1230
+ # whether the source object's current generation matches the given value.
1231
+ # @param [Integer] if_source_generation_not_match Makes the operation conditional
1232
+ # on whether the source object's current generation does not match the given value.
1233
+ # @param [Integer] if_source_metageneration_match Makes the operation conditional
1234
+ # on whether the source object's current metageneration matches the given value.
1235
+ # @param [Integer] if_source_metageneration_not_match Makes the operation conditional
1236
+ # on whether the source object's current metageneration does not match the given value.
1085
1237
  # @param [String] encryption_key Optional. The customer-supplied,
1086
1238
  # AES-256 encryption key used to decrypt the file, if the existing
1087
1239
  # file is encrypted.
@@ -1097,6 +1249,19 @@ module Google
1097
1249
  # the same location as the bucket.The Service Account associated with
1098
1250
  # your project requires access to this encryption key. Do not provide
1099
1251
  # if `new_encryption_key` is used.
1252
+ # @param [Boolean] force_copy_metadata Optional. If `true` and if updates
1253
+ # are made in a block, the following fields will be copied from the
1254
+ # source file to the destination file (except when changed by updates):
1255
+ #
1256
+ # * `cache_control`
1257
+ # * `content_disposition`
1258
+ # * `content_encoding`
1259
+ # * `content_language`
1260
+ # * `content_type`
1261
+ # * `metadata`
1262
+ #
1263
+ # If `nil` or `false`, only the updates made in the yielded block will
1264
+ # be applied to the destination object. The default is `nil`.
1100
1265
  # @yield [file] a block yielding a delegate object for updating
1101
1266
  #
1102
1267
  # @return [Google::Cloud::Storage::File]
@@ -1161,7 +1326,7 @@ module Google
1161
1326
  # cipher.encrypt
1162
1327
  # new_key = cipher.random_key
1163
1328
  #
1164
- # file = bucket.file "path/to/my-file.ext"
1329
+ # file = bucket.file "path/to/my-file.ext", encryption_key: old_key
1165
1330
  # file.rewrite "new-destination-bucket",
1166
1331
  # "path/to/destination/file.ext",
1167
1332
  # encryption_key: old_key,
@@ -1182,7 +1347,7 @@ module Google
1182
1347
  # # Old customer-supplied key was stored securely for later use.
1183
1348
  # old_key = "y\x03\"\x0E\xB6\xD3\x9B\x0E\xAB*\x19\xFAv\xDEY\xBEI..."
1184
1349
  #
1185
- # file = bucket.file "path/to/my-file.ext"
1350
+ # file = bucket.file "path/to/my-file.ext", encryption_key: old_key
1186
1351
  # file.rewrite "new-destination-bucket",
1187
1352
  # "path/to/destination/file.ext",
1188
1353
  # encryption_key: old_key,
@@ -1190,27 +1355,51 @@ module Google
1190
1355
  # f.metadata["rewritten_from"] = "#{file.bucket}/#{file.name}"
1191
1356
  # end
1192
1357
  #
1193
- def rewrite dest_bucket_or_path, dest_path = nil,
1194
- acl: nil, generation: nil,
1195
- encryption_key: nil, new_encryption_key: nil,
1196
- new_kms_key: nil
1358
+ def rewrite dest_bucket_or_path,
1359
+ dest_path = nil,
1360
+ acl: nil,
1361
+ generation: nil,
1362
+ if_generation_match: nil,
1363
+ if_generation_not_match: nil,
1364
+ if_metageneration_match: nil,
1365
+ if_metageneration_not_match: nil,
1366
+ if_source_generation_match: nil,
1367
+ if_source_generation_not_match: nil,
1368
+ if_source_metageneration_match: nil,
1369
+ if_source_metageneration_not_match: nil,
1370
+ encryption_key: nil,
1371
+ new_encryption_key: nil,
1372
+ new_kms_key: nil,
1373
+ force_copy_metadata: nil
1197
1374
  ensure_service!
1198
- dest_bucket, dest_path = fix_rewrite_args dest_bucket_or_path,
1199
- dest_path
1375
+ dest_bucket, dest_path = fix_rewrite_args dest_bucket_or_path, dest_path
1200
1376
 
1201
1377
  update_gapi = nil
1202
1378
  if block_given?
1203
- updater = Updater.new gapi
1379
+ updater = Updater.new gapi.dup
1204
1380
  yield updater
1205
1381
  updater.check_for_changed_metadata!
1206
1382
  if updater.updates.any?
1207
- update_gapi = gapi_from_attrs updater.updates
1383
+ attributes = force_copy_metadata ? (Updater::COPY_ATTRS + updater.updates).uniq : updater.updates
1384
+ update_gapi = self.class.gapi_from_attrs updater.gapi, attributes
1208
1385
  end
1209
1386
  end
1210
1387
 
1211
- new_gapi = rewrite_gapi bucket, name, update_gapi,
1212
- new_bucket: dest_bucket, new_name: dest_path,
1213
- acl: acl, generation: generation,
1388
+ new_gapi = rewrite_gapi bucket,
1389
+ name,
1390
+ update_gapi,
1391
+ new_bucket: dest_bucket,
1392
+ new_name: dest_path,
1393
+ acl: acl,
1394
+ generation: generation,
1395
+ if_generation_match: if_generation_match,
1396
+ if_generation_not_match: if_generation_not_match,
1397
+ if_metageneration_match: if_metageneration_match,
1398
+ if_metageneration_not_match: if_metageneration_not_match,
1399
+ if_source_generation_match: if_source_generation_match,
1400
+ if_source_generation_not_match: if_source_generation_not_match,
1401
+ if_source_metageneration_match: if_source_metageneration_match,
1402
+ if_source_metageneration_not_match: if_source_metageneration_not_match,
1214
1403
  encryption_key: encryption_key,
1215
1404
  new_encryption_key: new_encryption_key,
1216
1405
  new_kms_key: new_kms_key,
@@ -1307,6 +1496,20 @@ module Google
1307
1496
  # {#generation}. The default behavior is to delete the latest version
1308
1497
  # of the file (regardless of the version to which the file is set,
1309
1498
  # which is the version returned by {#generation}.)
1499
+ # @param [Integer] if_generation_match Makes the operation conditional
1500
+ # on whether the file's current generation matches the given value.
1501
+ # Setting to 0 makes the operation succeed only if there are no live
1502
+ # versions of the file.
1503
+ # @param [Integer] if_generation_not_match Makes the operation conditional
1504
+ # on whether the file's current generation does not match the given
1505
+ # value. If no live file exists, the precondition fails. Setting to 0
1506
+ # makes the operation succeed only if there is a live version of the file.
1507
+ # @param [Integer] if_metageneration_match Makes the operation conditional
1508
+ # on whether the file's current metageneration matches the given value.
1509
+ # @param [Integer] if_metageneration_not_match Makes the operation
1510
+ # conditional on whether the file's current metageneration does not
1511
+ # match the given value.
1512
+ #
1310
1513
  # @return [Boolean] Returns `true` if the file was deleted.
1311
1514
  #
1312
1515
  # @example
@@ -1339,11 +1542,21 @@ module Google
1339
1542
  # file = bucket.file "path/to/my-file.ext"
1340
1543
  # file.delete generation: 123456
1341
1544
  #
1342
- def delete generation: nil
1545
+ def delete generation: nil,
1546
+ if_generation_match: nil,
1547
+ if_generation_not_match: nil,
1548
+ if_metageneration_match: nil,
1549
+ if_metageneration_not_match: nil
1343
1550
  generation = self.generation if generation == true
1344
1551
  ensure_service!
1345
- service.delete_file bucket, name, generation: generation,
1346
- user_project: user_project
1552
+ service.delete_file bucket,
1553
+ name,
1554
+ generation: generation,
1555
+ if_generation_match: if_generation_match,
1556
+ if_generation_not_match: if_generation_not_match,
1557
+ if_metageneration_match: if_metageneration_match,
1558
+ if_metageneration_not_match: if_metageneration_not_match,
1559
+ user_project: user_project
1347
1560
  true
1348
1561
  end
1349
1562
 
@@ -1400,7 +1613,7 @@ module Google
1400
1613
  # A {SignedUrlUnavailable} is raised if the service account credentials
1401
1614
  # are missing. Service account credentials are acquired by following the
1402
1615
  # steps in [Service Account Authentication](
1403
- # https://cloud.google.com/storage/docs/authentication#service_accounts).
1616
+ # https://cloud.google.com/iam/docs/service-accounts).
1404
1617
  #
1405
1618
  # @see https://cloud.google.com/storage/docs/access-control/signed-urls
1406
1619
  # Signed URLs guide
@@ -1425,10 +1638,22 @@ module Google
1425
1638
  # use the signed URL.
1426
1639
  # @param [String] issuer Service Account's Client Email.
1427
1640
  # @param [String] client_email Service Account's Client Email.
1428
- # @param [OpenSSL::PKey::RSA, String] signing_key Service Account's
1429
- # Private Key.
1430
- # @param [OpenSSL::PKey::RSA, String] private_key Service Account's
1431
- # Private Key.
1641
+ # @param [OpenSSL::PKey::RSA, String, Proc] signing_key Service Account's
1642
+ # Private Key or a Proc that accepts a single String parameter and returns a
1643
+ # RSA SHA256 signature using a valid Google Service Account Private Key.
1644
+ # @param [OpenSSL::PKey::RSA, String, Proc] private_key Service Account's
1645
+ # Private Key or a Proc that accepts a single String parameter and returns a
1646
+ # RSA SHA256 signature using a valid Google Service Account Private Key.
1647
+ # @param [OpenSSL::PKey::RSA, String, Proc] signer Service Account's
1648
+ # Private Key or a Proc that accepts a single String parameter and returns a
1649
+ # RSA SHA256 signature using a valid Google Service Account Private Key.
1650
+ #
1651
+ # When using this method in environments such as GAE Flexible Environment,
1652
+ # GKE, or Cloud Functions where the private key is unavailable, it may be
1653
+ # necessary to provide a Proc (or lambda) via the signer parameter. This
1654
+ # Proc should return a signature created using a RPC call to the
1655
+ # [Service Account Credentials signBlob](https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob)
1656
+ # method as shown in the example below.
1432
1657
  # @param [Hash] query Query string parameters to include in the signed
1433
1658
  # URL. The given parameters are not verified by the signature.
1434
1659
  #
@@ -1437,11 +1662,29 @@ module Google
1437
1662
  # using the URL, but only when the file resource is missing the
1438
1663
  # corresponding values. (These values can be permanently set using
1439
1664
  # {#content_disposition=} and {#content_type=}.)
1665
+ # @param [String] scheme The URL scheme. The default value is `HTTPS`.
1666
+ # @param [Boolean] virtual_hosted_style Whether to use a virtual hosted-style
1667
+ # hostname, which adds the bucket into the host portion of the URI rather
1668
+ # than the path, e.g. `https://mybucket.storage.googleapis.com/...`.
1669
+ # For V4 signing, this also sets the `host` header in the canonicalized
1670
+ # extension headers to the virtual hosted-style host, unless that header is
1671
+ # supplied via the `headers` param. The default value of `false` uses the
1672
+ # form of `https://storage.googleapis.com/mybucket`.
1673
+ # @param [String] bucket_bound_hostname Use a bucket-bound hostname, which
1674
+ # replaces the `storage.googleapis.com` host with the name of a `CNAME`
1675
+ # bucket, e.g. a bucket named `gcs-subdomain.my.domain.tld`, or a Google
1676
+ # Cloud Load Balancer which routes to a bucket you own, e.g.
1677
+ # `my-load-balancer-domain.tld`.
1440
1678
  # @param [Symbol, String] version The version of the signed credential
1441
1679
  # to create. Must be one of `:v2` or `:v4`. The default value is
1442
1680
  # `:v2`.
1443
1681
  #
1444
- # @return [String]
1682
+ # @return [String] The signed URL.
1683
+ #
1684
+ # @raise [SignedUrlUnavailable] If the service account credentials
1685
+ # are missing. Service account credentials are acquired by following the
1686
+ # steps in [Service Account Authentication](
1687
+ # https://cloud.google.com/iam/docs/service-accounts).
1445
1688
  #
1446
1689
  # @example
1447
1690
  # require "google/cloud/storage"
@@ -1461,7 +1704,7 @@ module Google
1461
1704
  # file = bucket.file "avatars/heidi/400x400.png"
1462
1705
  # shared_url = file.signed_url expires: 300, # 5 minutes from now
1463
1706
  # version: :v4
1464
-
1707
+ #
1465
1708
  # @example Using the `issuer` and `signing_key` options:
1466
1709
  # require "google/cloud/storage"
1467
1710
  #
@@ -1501,28 +1744,85 @@ module Google
1501
1744
  # # Send the `x-goog-resumable:start` header and the content type
1502
1745
  # # with the resumable upload POST request.
1503
1746
  #
1504
- def signed_url method: nil, expires: nil, content_type: nil,
1505
- content_md5: nil, headers: nil, issuer: nil,
1506
- client_email: nil, signing_key: nil, private_key: nil,
1507
- query: nil, version: nil
1747
+ # @example Using Cloud IAMCredentials signBlob to create the signature:
1748
+ # require "google/cloud/storage"
1749
+ # require "google/apis/iamcredentials_v1"
1750
+ # require "googleauth"
1751
+ #
1752
+ # # Issuer is the service account email that the Signed URL will be signed with
1753
+ # # and any permission granted in the Signed URL must be granted to the
1754
+ # # Google Service Account.
1755
+ # issuer = "service-account@project-id.iam.gserviceaccount.com"
1756
+ #
1757
+ # # Create a lambda that accepts the string_to_sign
1758
+ # signer = lambda do |string_to_sign|
1759
+ # IAMCredentials = Google::Apis::IamcredentialsV1
1760
+ # iam_client = IAMCredentials::IAMCredentialsService.new
1761
+ #
1762
+ # # Get the environment configured authorization
1763
+ # scopes = ["https://www.googleapis.com/auth/iam"]
1764
+ # iam_client.authorization = Google::Auth.get_application_default scopes
1765
+ #
1766
+ # request = Google::Apis::IamcredentialsV1::SignBlobRequest.new(
1767
+ # payload: string_to_sign
1768
+ # )
1769
+ # resource = "projects/-/serviceAccounts/#{issuer}"
1770
+ # response = iam_client.sign_service_account_blob resource, request
1771
+ # response.signed_blob
1772
+ # end
1773
+ #
1774
+ # storage = Google::Cloud::Storage.new
1775
+ #
1776
+ # bucket = storage.bucket "my-todo-app"
1777
+ # file = bucket.file "avatars/heidi/400x400.png", skip_lookup: true
1778
+ # url = file.signed_url method: "GET", issuer: issuer,
1779
+ # signer: signer
1780
+ #
1781
+ def signed_url method: "GET",
1782
+ expires: nil,
1783
+ content_type: nil,
1784
+ content_md5: nil,
1785
+ headers: nil,
1786
+ issuer: nil,
1787
+ client_email: nil,
1788
+ signing_key: nil,
1789
+ private_key: nil,
1790
+ signer: nil,
1791
+ query: nil,
1792
+ scheme: "HTTPS",
1793
+ virtual_hosted_style: nil,
1794
+ bucket_bound_hostname: nil,
1795
+ version: nil
1508
1796
  ensure_service!
1509
1797
  version ||= :v2
1510
1798
  case version.to_sym
1511
1799
  when :v2
1512
- signer = File::SignerV2.from_file self
1513
- signer.signed_url method: method, expires: expires,
1514
- headers: headers, content_type: content_type,
1515
- content_md5: content_md5, issuer: issuer,
1516
- client_email: client_email,
1517
- signing_key: signing_key,
1518
- private_key: private_key, query: query
1800
+ sign = File::SignerV2.from_file self
1801
+ sign.signed_url method: method,
1802
+ expires: expires,
1803
+ headers: headers,
1804
+ content_type: content_type,
1805
+ content_md5: content_md5,
1806
+ issuer: issuer,
1807
+ client_email: client_email,
1808
+ signing_key: signing_key,
1809
+ private_key: private_key,
1810
+ signer: signer,
1811
+ query: query
1519
1812
  when :v4
1520
- signer = File::SignerV4.from_file self
1521
- signer.signed_url method: method, expires: expires,
1522
- headers: headers, issuer: issuer,
1523
- client_email: client_email,
1524
- signing_key: signing_key,
1525
- private_key: private_key, query: query
1813
+ sign = File::SignerV4.from_file self
1814
+ sign.signed_url method: method,
1815
+ expires: expires,
1816
+ headers: headers,
1817
+ issuer: issuer,
1818
+ client_email: client_email,
1819
+ signing_key: signing_key,
1820
+ private_key: private_key,
1821
+ signer: signer,
1822
+ query: query,
1823
+ scheme: scheme,
1824
+ virtual_hosted_style: virtual_hosted_style,
1825
+ bucket_bound_hostname: bucket_bound_hostname
1526
1826
  else
1527
1827
  raise ArgumentError, "version '#{version}' not supported"
1528
1828
  end
@@ -1679,6 +1979,21 @@ module Google
1679
1979
  end
1680
1980
  end
1681
1981
 
1982
+ ##
1983
+ # @private
1984
+ #
1985
+ def self.gapi_from_attrs gapi, attributes
1986
+ attributes.flatten!
1987
+ return nil if attributes.empty?
1988
+ attr_params = Hash[attributes.map do |attr|
1989
+ [attr, gapi.send(attr)]
1990
+ end]
1991
+ # Sending nil metadata results in an Apiary runtime error:
1992
+ # NoMethodError: undefined method `each' for nil:NilClass
1993
+ attr_params.reject! { |k, v| k == :metadata && v.nil? }
1994
+ Google::Apis::StorageV1::Object.new(**attr_params)
1995
+ end
1996
+
1682
1997
  protected
1683
1998
 
1684
1999
  ##
@@ -1695,53 +2010,89 @@ module Google
1695
2010
  reload! generation: true
1696
2011
  end
1697
2012
 
1698
- def update_gapi! *attributes
2013
+ def update_gapi! attributes,
2014
+ generation: nil,
2015
+ if_generation_match: nil,
2016
+ if_generation_not_match: nil,
2017
+ if_metageneration_match: nil,
2018
+ if_metageneration_not_match: nil
2019
+ attributes = Array(attributes)
1699
2020
  attributes.flatten!
1700
2021
  return if attributes.empty?
1701
- update_gapi = gapi_from_attrs attributes
2022
+ update_gapi = self.class.gapi_from_attrs @gapi, attributes
1702
2023
  return if update_gapi.nil?
1703
2024
 
1704
2025
  ensure_service!
1705
2026
 
1706
- rewrite_attrs = %i[storage_class kms_key_name]
2027
+ rewrite_attrs = [:storage_class, :kms_key_name]
1707
2028
  @gapi = if attributes.any? { |a| rewrite_attrs.include? a }
1708
- rewrite_gapi \
1709
- bucket, name, update_gapi, user_project: user_project
2029
+ rewrite_gapi bucket,
2030
+ name,
2031
+ update_gapi,
2032
+ generation: generation,
2033
+ if_generation_match: if_generation_match,
2034
+ if_generation_not_match: if_generation_not_match,
2035
+ if_metageneration_match: if_metageneration_match,
2036
+ if_metageneration_not_match: if_metageneration_not_match,
2037
+ user_project: user_project
1710
2038
  else
1711
- service.patch_file \
1712
- bucket, name, update_gapi, user_project: user_project
2039
+ service.patch_file bucket,
2040
+ name,
2041
+ update_gapi,
2042
+ generation: generation,
2043
+ if_generation_match: if_generation_match,
2044
+ if_generation_not_match: if_generation_not_match,
2045
+ if_metageneration_match: if_metageneration_match,
2046
+ if_metageneration_not_match: if_metageneration_not_match,
2047
+ user_project: user_project
1713
2048
  end
1714
2049
  end
1715
2050
 
1716
- def gapi_from_attrs *attributes
1717
- attributes.flatten!
1718
- return nil if attributes.empty?
1719
- attr_params = Hash[attributes.map do |attr|
1720
- [attr, @gapi.send(attr)]
1721
- end]
1722
- Google::Apis::StorageV1::Object.new attr_params
1723
- end
1724
-
1725
- def rewrite_gapi bucket, name, updated_gapi,
1726
- new_bucket: nil, new_name: nil, acl: nil,
1727
- generation: nil, encryption_key: nil,
1728
- new_encryption_key: nil, new_kms_key: nil,
2051
+ def rewrite_gapi bucket,
2052
+ name,
2053
+ updated_gapi,
2054
+ new_bucket: nil,
2055
+ new_name: nil,
2056
+ acl: nil,
2057
+ generation: nil,
2058
+ if_generation_match: nil,
2059
+ if_generation_not_match: nil,
2060
+ if_metageneration_match: nil,
2061
+ if_metageneration_not_match: nil,
2062
+ if_source_generation_match: nil,
2063
+ if_source_generation_not_match: nil,
2064
+ if_source_metageneration_match: nil,
2065
+ if_source_metageneration_not_match: nil,
2066
+ encryption_key: nil,
2067
+ new_encryption_key: nil,
2068
+ new_kms_key: nil,
1729
2069
  user_project: nil
1730
2070
  new_bucket ||= bucket
1731
2071
  new_name ||= name
1732
- options = { acl: File::Acl.predefined_rule_for(acl),
1733
- generation: generation, source_key: encryption_key,
1734
- destination_key: new_encryption_key,
1735
- destination_kms_key: new_kms_key,
1736
- user_project: user_project }.delete_if { |_k, v| v.nil? }
2072
+ options = {
2073
+ acl: File::Acl.predefined_rule_for(acl),
2074
+ generation: generation,
2075
+ if_generation_match: if_generation_match,
2076
+ if_generation_not_match: if_generation_not_match,
2077
+ if_metageneration_match: if_metageneration_match,
2078
+ if_metageneration_not_match: if_metageneration_not_match,
2079
+ if_source_generation_match: if_source_generation_match,
2080
+ if_source_generation_not_match: if_source_generation_not_match,
2081
+ if_source_metageneration_match: if_source_metageneration_match,
2082
+ if_source_metageneration_not_match: if_source_metageneration_not_match,
2083
+ source_key: encryption_key,
2084
+ destination_key: new_encryption_key,
2085
+ destination_kms_key: new_kms_key,
2086
+ user_project: user_project
2087
+ }.delete_if { |_k, v| v.nil? }
1737
2088
 
1738
2089
  resp = service.rewrite_file \
1739
- bucket, name, new_bucket, new_name, updated_gapi, options
2090
+ bucket, name, new_bucket, new_name, updated_gapi, **options
1740
2091
  until resp.done
1741
2092
  sleep 1
1742
2093
  retry_options = options.merge token: resp.rewrite_token
1743
2094
  resp = service.rewrite_file \
1744
- bucket, name, new_bucket, new_name, updated_gapi, retry_options
2095
+ bucket, name, new_bucket, new_name, updated_gapi, **retry_options
1745
2096
  end
1746
2097
  resp.resource
1747
2098
  end
@@ -1792,11 +2143,26 @@ module Google
1792
2143
  # Yielded to a block to accumulate changes for a patch request.
1793
2144
  class Updater < File
1794
2145
  # @private
1795
- attr_reader :updates
2146
+ attr_reader :updates, :gapi
2147
+
2148
+ ##
2149
+ # @private
2150
+ # Whitelist of Google::Apis::StorageV1::Object attributes to be
2151
+ # copied when File#copy or File#rewrite is called with
2152
+ # `force_copy_metadata: true`.
2153
+ COPY_ATTRS = [
2154
+ :cache_control,
2155
+ :content_disposition,
2156
+ :content_encoding,
2157
+ :content_language,
2158
+ :content_type,
2159
+ :metadata
2160
+ ].freeze
1796
2161
 
1797
2162
  ##
1798
2163
  # @private Create an Updater object.
1799
2164
  def initialize gapi
2165
+ super()
1800
2166
  @updates = []
1801
2167
  @gapi = gapi
1802
2168
  @metadata ||= @gapi.metadata.to_h.dup