google-cloud-storage 1.18.1 → 1.44.0

Sign up to get free protection for your applications and to get access to all the features.
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