google-cloud-storage 0.24.0 → 0.25.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/google/cloud/storage.rb +88 -36
- data/lib/google/cloud/storage/bucket.rb +17 -9
- data/lib/google/cloud/storage/bucket/list.rb +6 -6
- data/lib/google/cloud/storage/file.rb +37 -126
- data/lib/google/cloud/storage/file/acl.rb +1 -5
- data/lib/google/cloud/storage/file/list.rb +6 -6
- data/lib/google/cloud/storage/file/signer.rb +142 -0
- data/lib/google/cloud/storage/file/verifier.rb +14 -4
- data/lib/google/cloud/storage/project.rb +106 -2
- data/lib/google/cloud/storage/service.rb +5 -1
- data/lib/google/cloud/storage/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4450abcfeebceca2abde8444b448c4361cd66bba
|
4
|
+
data.tar.gz: 9f9d2b7ec0dfcece945d361f3cc9828b8560d189
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21241923c3f1b341eb21f495ae3e747ee2508df599c7ac1cf27ecccab85960eda2e7cdac878580d6def3343d551ff9de48cba5b2bda3fb73374be9eb77ada937
|
7
|
+
data.tar.gz: a685308ed10e4cece3b05d15c48c9a4f1c04bdba60587332db912646b9fd998e1e4c479f158472ac07a0b4079dc8bbe6c843880fef959f3000d17ee9d078fc85
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[Google Cloud Storage](https://cloud.google.com/storage/) ([docs](https://cloud.google.com/storage/docs/json_api/)) allows you to store data on Google infrastructure with very high reliability, performance and availability, and can be used to distribute large data objects to users via direct download.
|
4
4
|
|
5
|
-
- [google-cloud-storage API documentation](http://googlecloudplatform.github.io/google-cloud-ruby/#/docs/google-cloud-storage/
|
5
|
+
- [google-cloud-storage API documentation](http://googlecloudplatform.github.io/google-cloud-ruby/#/docs/google-cloud-storage/latest)
|
6
6
|
- [google-cloud-storage on RubyGems](https://rubygems.org/gems/google-cloud-storage)
|
7
7
|
- [Google Cloud Storage documentation](https://cloud.google.com/storage/docs)
|
8
8
|
|
data/lib/google/cloud/storage.rb
CHANGED
@@ -55,11 +55,14 @@ module Google
|
|
55
55
|
#
|
56
56
|
# ## Retrieving Buckets
|
57
57
|
#
|
58
|
-
# A Bucket is
|
59
|
-
# of buckets that you can create in a
|
60
|
-
# organize and control access to your data.
|
61
|
-
#
|
62
|
-
#
|
58
|
+
# A {Google::Cloud::Storage::Bucket} instance is a container for your data.
|
59
|
+
# There is no limit on the number of buckets that you can create in a
|
60
|
+
# project. You can use buckets to organize and control access to your data.
|
61
|
+
# For more information, see [Working with
|
62
|
+
# Buckets](https://cloud.google.com/storage/docs/creating-buckets).
|
63
|
+
#
|
64
|
+
# Each bucket has a globally unique name, which is how they are retrieved:
|
65
|
+
# (See {Google::Cloud::Storage::Project#bucket})
|
63
66
|
#
|
64
67
|
# ```ruby
|
65
68
|
# require "google/cloud/storage"
|
@@ -80,27 +83,38 @@ module Google
|
|
80
83
|
# all_buckets = storage.buckets
|
81
84
|
# ```
|
82
85
|
#
|
83
|
-
# If you have a significant number of buckets, you may need to
|
84
|
-
#
|
86
|
+
# If you have a significant number of buckets, you may need to fetch them
|
87
|
+
# in multiple service requests.
|
88
|
+
#
|
89
|
+
# Iterating over each bucket, potentially with multiple API calls, by
|
90
|
+
# invoking `all` with a block:
|
91
|
+
#
|
92
|
+
# ```ruby
|
93
|
+
# require "google/cloud/storage"
|
94
|
+
#
|
95
|
+
# storage = Google::Cloud::Storage.new
|
96
|
+
#
|
97
|
+
# buckets = storage.buckets
|
98
|
+
# buckets.all do |bucket|
|
99
|
+
# puts bucket.name
|
100
|
+
# end
|
101
|
+
# ```
|
102
|
+
#
|
103
|
+
# Limiting the number of API calls made:
|
85
104
|
#
|
86
105
|
# ```ruby
|
87
106
|
# require "google/cloud/storage"
|
88
107
|
#
|
89
108
|
# storage = Google::Cloud::Storage.new
|
90
109
|
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
# tmp_buckets.each do |bucket|
|
95
|
-
# all_buckets << bucket
|
96
|
-
# end
|
97
|
-
# # break loop if no more buckets available
|
98
|
-
# break if tmp_buckets.token.nil?
|
99
|
-
# # get the next group of buckets
|
100
|
-
# tmp_buckets = storage.buckets token: tmp_buckets.token
|
110
|
+
# buckets = storage.buckets
|
111
|
+
# buckets.all(request_limit: 10) do |bucket|
|
112
|
+
# puts bucket.name
|
101
113
|
# end
|
102
114
|
# ```
|
103
115
|
#
|
116
|
+
# See {Google::Cloud::Storage::Bucket::List} for details.
|
117
|
+
#
|
104
118
|
# ## Creating a Bucket
|
105
119
|
#
|
106
120
|
# A unique name is all that is needed to create a new bucket: (See
|
@@ -116,10 +130,12 @@ module Google
|
|
116
130
|
#
|
117
131
|
# ## Retrieving Files
|
118
132
|
#
|
119
|
-
# A File is an individual
|
120
|
-
# Storage. Files contain the data stored as
|
121
|
-
# data. Files belong to a bucket and cannot
|
122
|
-
# is no limit on the number of
|
133
|
+
# A {Google::Cloud::Storage::File} instance is an individual data object
|
134
|
+
# that you store in Google Cloud Storage. Files contain the data stored as
|
135
|
+
# well as metadata describing the data. Files belong to a bucket and cannot
|
136
|
+
# be shared among buckets. There is no limit on the number of files that
|
137
|
+
# you can create in a bucket. For more information, see [Working with
|
138
|
+
# Objects](https://cloud.google.com/storage/docs/object-basics).
|
123
139
|
#
|
124
140
|
# Files are retrieved by their name, which is the path of the file in the
|
125
141
|
# bucket: (See {Google::Cloud::Storage::Bucket#file})
|
@@ -155,32 +171,42 @@ module Google
|
|
155
171
|
# avatar_files = bucket.files prefix: "avatars/"
|
156
172
|
# ```
|
157
173
|
#
|
158
|
-
# If you have a significant number of files, you may need to
|
159
|
-
#
|
174
|
+
# If you have a significant number of files, you may need to fetch them
|
175
|
+
# in multiple service requests.
|
176
|
+
#
|
177
|
+
# Iterating over each file, potentially with multiple API calls, by
|
178
|
+
# invoking `all` with a block:
|
160
179
|
#
|
161
180
|
# ```ruby
|
162
181
|
# require "google/cloud/storage"
|
163
182
|
#
|
164
183
|
# storage = Google::Cloud::Storage.new
|
165
|
-
#
|
166
184
|
# bucket = storage.bucket "my-todo-app"
|
167
185
|
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
# tmp_files.each do |file|
|
172
|
-
# all_files << file
|
173
|
-
# end
|
174
|
-
# # break loop if no more files available
|
175
|
-
# break if tmp_files.token.nil?
|
176
|
-
# # get the next group of files
|
177
|
-
# tmp_files = bucket.files token: tmp_files.token
|
186
|
+
# files = storage.files
|
187
|
+
# files.all do |file|
|
188
|
+
# puts file.name
|
178
189
|
# end
|
179
190
|
# ```
|
180
191
|
#
|
192
|
+
# Limiting the number of API calls made:
|
193
|
+
#
|
194
|
+
# ```ruby
|
195
|
+
# require "google/cloud/storage"
|
196
|
+
#
|
197
|
+
# storage = Google::Cloud::Storage.new
|
198
|
+
#
|
199
|
+
# files = storage.files
|
200
|
+
# files.all(request_limit: 10) do |file|
|
201
|
+
# puts bucket.name
|
202
|
+
# end
|
203
|
+
# ```
|
204
|
+
#
|
205
|
+
# See {Google::Cloud::Storage::File::List} for details.
|
206
|
+
#
|
181
207
|
# ## Creating a File
|
182
208
|
#
|
183
|
-
# A new
|
209
|
+
# A new file can be uploaded by specifying the location of a file on the
|
184
210
|
# local file system, and the name/path that the file should be stored in the
|
185
211
|
# bucket. (See {Google::Cloud::Storage::Bucket#create_file})
|
186
212
|
#
|
@@ -194,6 +220,17 @@ module Google
|
|
194
220
|
# "avatars/heidi/400x400.png"
|
195
221
|
# ```
|
196
222
|
#
|
223
|
+
# Files can also be created from an in-memory StringIO object:
|
224
|
+
#
|
225
|
+
# ```ruby
|
226
|
+
# require "google/cloud/storage"
|
227
|
+
#
|
228
|
+
# storage = Google::Cloud::Storage.new
|
229
|
+
#
|
230
|
+
# bucket = storage.bucket "my-todo-app"
|
231
|
+
# bucket.create_file StringIO.new("Hello world!"), "hello-world.txt"
|
232
|
+
# ```
|
233
|
+
#
|
197
234
|
# ### Customer-supplied encryption keys
|
198
235
|
#
|
199
236
|
# By default, Google Cloud Storage manages server-side encryption keys on
|
@@ -265,9 +302,24 @@ module Google
|
|
265
302
|
# file.download "/var/todo-app/avatars/heidi/400x400.png"
|
266
303
|
# ```
|
267
304
|
#
|
305
|
+
# Files can also be downloaded to an in-memory StringIO object:
|
306
|
+
#
|
307
|
+
# ```ruby
|
308
|
+
# require "google/cloud/storage"
|
309
|
+
#
|
310
|
+
# storage = Google::Cloud::Storage.new
|
311
|
+
#
|
312
|
+
# bucket = storage.bucket "my-todo-app"
|
313
|
+
# file = bucket.file "hello-world.txt"
|
314
|
+
#
|
315
|
+
# downloaded = file.download
|
316
|
+
# downloaded.rewind
|
317
|
+
# downloaded.read #=> "Hello world!"
|
318
|
+
# ```
|
319
|
+
#
|
268
320
|
# ## Using Signed URLs
|
269
321
|
#
|
270
|
-
# Access without authentication can be granted to a
|
322
|
+
# Access without authentication can be granted to a file for a specified
|
271
323
|
# period of time. This URL uses a cryptographic signature of your
|
272
324
|
# credentials to access the file. (See
|
273
325
|
# {Google::Cloud::Storage::File#signed_url})
|
@@ -449,8 +449,9 @@ module Google
|
|
449
449
|
alias_method :find_file, :file
|
450
450
|
|
451
451
|
##
|
452
|
-
# Creates a new {File} object by providing a path to a local file
|
453
|
-
#
|
452
|
+
# Creates a new {File} object by providing a path to a local file (or
|
453
|
+
# any IO or IO-ish object) to upload, along with the path at which to
|
454
|
+
# store it in the bucket.
|
454
455
|
#
|
455
456
|
# #### Customer-supplied encryption keys
|
456
457
|
#
|
@@ -465,7 +466,10 @@ module Google
|
|
465
466
|
# and you can read or update the metadata of an encrypted file without
|
466
467
|
# providing the encryption key.
|
467
468
|
#
|
468
|
-
# @param [String] file Path of the file on the filesystem to
|
469
|
+
# @param [String, IO] file Path of the file on the filesystem to
|
470
|
+
# upload. Can be an IO object, or IO-ish object like StringIO. (If the
|
471
|
+
# IO object does not have path, a `path` argument must be also be
|
472
|
+
# provided.)
|
469
473
|
# @param [String] path Path to store the file in Google Cloud Storage.
|
470
474
|
# @param [String] acl A predefined set of access controls to apply to
|
471
475
|
# this file.
|
@@ -580,9 +584,11 @@ module Google
|
|
580
584
|
content_encoding: content_encoding, metadata: metadata,
|
581
585
|
content_language: content_language, key: encryption_key,
|
582
586
|
storage_class: storage_class_for(storage_class) }
|
583
|
-
|
584
|
-
|
585
|
-
path ||=
|
587
|
+
ensure_io_or_file_exists! file
|
588
|
+
path ||= file.path if file.respond_to? :path
|
589
|
+
path ||= file if file.is_a? String
|
590
|
+
fail ArgumentError, "must provide path" if path.nil?
|
591
|
+
|
586
592
|
gapi = service.insert_file name, file, path, options
|
587
593
|
File.from_gapi gapi, service
|
588
594
|
end
|
@@ -612,7 +618,7 @@ module Google
|
|
612
618
|
# @see https://cloud.google.com/storage/docs/access-control#Signed-URLs
|
613
619
|
# Access Control Signed URLs guide
|
614
620
|
#
|
615
|
-
# @param [String] path Path to
|
621
|
+
# @param [String] path Path to the file in Google Cloud Storage.
|
616
622
|
# @param [String] method The HTTP verb to be used with the signed URL.
|
617
623
|
# Signed URLs can be used
|
618
624
|
# with `GET`, `HEAD`, `PUT`, and `DELETE` requests. Default is `GET`.
|
@@ -649,6 +655,7 @@ module Google
|
|
649
655
|
# bucket = storage.bucket "my-todo-app"
|
650
656
|
# shared_url = bucket.signed_url "avatars/heidi/400x400.png",
|
651
657
|
# method: "PUT",
|
658
|
+
# content_type: "image/png",
|
652
659
|
# expires: 300 # 5 minutes from now
|
653
660
|
#
|
654
661
|
# @example Using the issuer and signing_key options:
|
@@ -705,7 +712,7 @@ module Google
|
|
705
712
|
#
|
706
713
|
# @see https://cloud.google.com/storage/docs/xml-api/post-object
|
707
714
|
#
|
708
|
-
# @param [String] path Path to
|
715
|
+
# @param [String] path Path to the file in Google Cloud Storage.
|
709
716
|
# @param [Hash] policy The security policy that describes what
|
710
717
|
# can and cannot be uploaded in the form. When provided,
|
711
718
|
# the PostObject fields will include a Signature based on the JSON
|
@@ -919,7 +926,8 @@ module Google
|
|
919
926
|
|
920
927
|
##
|
921
928
|
# Raise an error if the file is not found.
|
922
|
-
def
|
929
|
+
def ensure_io_or_file_exists! file
|
930
|
+
return if file.respond_to?(:read) && file.respond_to?(:rewind)
|
923
931
|
return if ::File.file? file
|
924
932
|
fail ArgumentError, "cannot find file #{file}"
|
925
933
|
end
|
@@ -77,15 +77,15 @@ module Google
|
|
77
77
|
end
|
78
78
|
|
79
79
|
##
|
80
|
-
# Retrieves
|
81
|
-
# returns `false`. Calls the given block once for each
|
82
|
-
# is passed as the
|
80
|
+
# Retrieves remaining results by repeatedly invoking {#next} until
|
81
|
+
# {#next?} returns `false`. Calls the given block once for each
|
82
|
+
# result, which is passed as the argument to the block.
|
83
83
|
#
|
84
84
|
# An Enumerator is returned if no block is given.
|
85
85
|
#
|
86
|
-
# This method
|
87
|
-
# retrieved.
|
88
|
-
#
|
86
|
+
# This method will make repeated API calls until all remaining results
|
87
|
+
# are retrieved. (Unlike `#each`, for example, which merely iterates
|
88
|
+
# over the results returned by a single API call.) Use with caution.
|
89
89
|
#
|
90
90
|
# @param [Integer] request_limit The upper limit of API requests to
|
91
91
|
# make to load all buckets. Default is no limit.
|
@@ -17,6 +17,7 @@ require "uri"
|
|
17
17
|
require "google/cloud/storage/file/acl"
|
18
18
|
require "google/cloud/storage/file/list"
|
19
19
|
require "google/cloud/storage/file/verifier"
|
20
|
+
require "google/cloud/storage/file/signer"
|
20
21
|
|
21
22
|
module Google
|
22
23
|
module Cloud
|
@@ -332,7 +333,7 @@ module Google
|
|
332
333
|
end
|
333
334
|
|
334
335
|
##
|
335
|
-
# Download the file's contents to a local file.
|
336
|
+
# Download the file's contents to a local file or an IO instance.
|
336
337
|
#
|
337
338
|
# By default, the download is verified by calculating the MD5 digest.
|
338
339
|
#
|
@@ -341,9 +342,12 @@ module Google
|
|
341
342
|
# was used with {Bucket#create_file}, the `encryption_key` option must
|
342
343
|
# be provided.
|
343
344
|
#
|
344
|
-
# @param [String] path The path on the local file system to write
|
345
|
-
# data to. The path provided must be writable.
|
346
|
-
#
|
345
|
+
# @param [String, IO] path The path on the local file system to write
|
346
|
+
# the data to. The path provided must be writable. Can also be an IO
|
347
|
+
# object, or IO-ish object like StringIO. If an IO object, the object
|
348
|
+
# will be written to, not the filesystem. If omitted, a new StringIO
|
349
|
+
# instance will be written to and returned. Optional.
|
350
|
+
# @param [Symbol] verify The verification algorithm used to ensure the
|
347
351
|
# downloaded file contents are correct. Default is `:md5`.
|
348
352
|
#
|
349
353
|
# Acceptable values are:
|
@@ -357,7 +361,11 @@ module Google
|
|
357
361
|
# AES-256 encryption key used to encrypt the file, if one was provided
|
358
362
|
# to {Bucket#create_file}.
|
359
363
|
#
|
360
|
-
# @return [
|
364
|
+
# @return [IO] Returns an IO object representing the file data. This
|
365
|
+
# will ordinarily be a `::File` object referencing the local file
|
366
|
+
# system. However, if the argument to `path` is `nil`, a StringIO
|
367
|
+
# instance will be returned. If the argument to `path` is an IO
|
368
|
+
# object, then that object will be returned.
|
361
369
|
#
|
362
370
|
# @example
|
363
371
|
# require "google/cloud/storage"
|
@@ -399,12 +407,30 @@ module Google
|
|
399
407
|
# file = bucket.file "path/to/my-file.ext"
|
400
408
|
# file.download "path/to/downloaded/file.ext", verify: :none
|
401
409
|
#
|
402
|
-
|
410
|
+
# @example Download to an in-memory StringIO object.
|
411
|
+
# require "google/cloud/storage"
|
412
|
+
#
|
413
|
+
# storage = Google::Cloud::Storage.new
|
414
|
+
#
|
415
|
+
# bucket = storage.bucket "my-bucket"
|
416
|
+
#
|
417
|
+
# file = bucket.file "path/to/my-file.ext"
|
418
|
+
# downloaded = file.download
|
419
|
+
# downloaded.rewind
|
420
|
+
# downloaded.read #=> "Hello world!"
|
421
|
+
#
|
422
|
+
def download path = nil, verify: :md5, encryption_key: nil
|
403
423
|
ensure_service!
|
404
|
-
|
424
|
+
if path.nil?
|
425
|
+
path = StringIO.new
|
426
|
+
path.set_encoding "ASCII-8BIT"
|
427
|
+
end
|
428
|
+
file = service.download_file \
|
405
429
|
bucket, name, path,
|
406
430
|
key: encryption_key
|
407
|
-
|
431
|
+
# FIX: downloading with encryption key will return nil
|
432
|
+
file ||= ::File.new(path)
|
433
|
+
verify_file! file, verify
|
408
434
|
end
|
409
435
|
|
410
436
|
##
|
@@ -661,7 +687,8 @@ module Google
|
|
661
687
|
#
|
662
688
|
# bucket = storage.bucket "my-todo-app"
|
663
689
|
# file = bucket.file "avatars/heidi/400x400.png"
|
664
|
-
# shared_url = file.signed_url method: "
|
690
|
+
# shared_url = file.signed_url method: "PUT",
|
691
|
+
# content_type: "image/png",
|
665
692
|
# expires: 300 # 5 minutes from now
|
666
693
|
#
|
667
694
|
# @example Using the `issuer` and `signing_key` options:
|
@@ -685,7 +712,7 @@ module Google
|
|
685
712
|
# shared_url = file.signed_url method: "GET",
|
686
713
|
# headers: {
|
687
714
|
# "x-goog-acl" => "public-read",
|
688
|
-
# "x-goog-meta-foo" => bar,baz"
|
715
|
+
# "x-goog-meta-foo" => "bar,baz"
|
689
716
|
# }
|
690
717
|
#
|
691
718
|
def signed_url method: nil, expires: nil, content_type: nil,
|
@@ -823,122 +850,6 @@ module Google
|
|
823
850
|
"standard" => "STANDARD" }[str.to_s.downcase] || str.to_s
|
824
851
|
end
|
825
852
|
|
826
|
-
##
|
827
|
-
# @private Create a signed_url for a file.
|
828
|
-
class Signer
|
829
|
-
def initialize bucket, path, service
|
830
|
-
@bucket = bucket
|
831
|
-
@path = path
|
832
|
-
@service = service
|
833
|
-
end
|
834
|
-
|
835
|
-
def self.from_file file
|
836
|
-
new file.bucket, file.name, file.service
|
837
|
-
end
|
838
|
-
|
839
|
-
def self.from_bucket bucket, path
|
840
|
-
new bucket.name, path, bucket.service
|
841
|
-
end
|
842
|
-
|
843
|
-
##
|
844
|
-
# The external path to the file.
|
845
|
-
def ext_path
|
846
|
-
URI.escape "/#{@bucket}/#{@path}"
|
847
|
-
end
|
848
|
-
|
849
|
-
##
|
850
|
-
# The external url to the file.
|
851
|
-
def ext_url
|
852
|
-
"#{GOOGLEAPIS_URL}#{ext_path}"
|
853
|
-
end
|
854
|
-
|
855
|
-
def apply_option_defaults options
|
856
|
-
adjusted_expires = (Time.now.utc + (options[:expires] || 300)).to_i
|
857
|
-
options[:expires] = adjusted_expires
|
858
|
-
options[:method] ||= "GET"
|
859
|
-
options
|
860
|
-
end
|
861
|
-
|
862
|
-
def signature_str options
|
863
|
-
[options[:method], options[:content_md5],
|
864
|
-
options[:content_type], options[:expires],
|
865
|
-
format_extension_headers(options[:headers]) + ext_path].join "\n"
|
866
|
-
end
|
867
|
-
|
868
|
-
def determine_signing_key options = {}
|
869
|
-
options[:signing_key] || options[:private_key] ||
|
870
|
-
@service.credentials.signing_key
|
871
|
-
end
|
872
|
-
|
873
|
-
def determine_issuer options = {}
|
874
|
-
options[:issuer] || options[:client_email] ||
|
875
|
-
@service.credentials.issuer
|
876
|
-
end
|
877
|
-
|
878
|
-
def post_object options
|
879
|
-
options = apply_option_defaults options
|
880
|
-
|
881
|
-
fields = {
|
882
|
-
key: ext_path.sub("/", "")
|
883
|
-
}
|
884
|
-
|
885
|
-
p = options[:policy] || {}
|
886
|
-
fail "Policy must be given in a Hash" unless p.is_a? Hash
|
887
|
-
|
888
|
-
i = determine_issuer options
|
889
|
-
s = determine_signing_key options
|
890
|
-
|
891
|
-
fail SignedUrlUnavailable unless i && s
|
892
|
-
|
893
|
-
policy_str = p.to_json
|
894
|
-
policy = Base64.strict_encode64(policy_str).delete("\n")
|
895
|
-
|
896
|
-
signature = generate_signature s, policy
|
897
|
-
|
898
|
-
fields[:GoogleAccessId] = i
|
899
|
-
fields[:signature] = signature
|
900
|
-
fields[:policy] = policy
|
901
|
-
|
902
|
-
Google::Cloud::Storage::PostObject.new GOOGLEAPIS_URL, fields
|
903
|
-
end
|
904
|
-
|
905
|
-
def signed_url options
|
906
|
-
options = apply_option_defaults options
|
907
|
-
|
908
|
-
i = determine_issuer options
|
909
|
-
s = determine_signing_key options
|
910
|
-
|
911
|
-
fail SignedUrlUnavailable unless i && s
|
912
|
-
|
913
|
-
sig = generate_signature s, signature_str(options)
|
914
|
-
generate_signed_url i, sig, options[:expires]
|
915
|
-
end
|
916
|
-
|
917
|
-
def generate_signature signing_key, secret
|
918
|
-
unless signing_key.respond_to? :sign
|
919
|
-
signing_key = OpenSSL::PKey::RSA.new signing_key
|
920
|
-
end
|
921
|
-
signature = signing_key.sign OpenSSL::Digest::SHA256.new, secret
|
922
|
-
Base64.strict_encode64(signature).delete("\n")
|
923
|
-
end
|
924
|
-
|
925
|
-
def generate_signed_url issuer, signed_string, expires
|
926
|
-
"#{ext_url}?GoogleAccessId=#{CGI.escape issuer}" \
|
927
|
-
"&Expires=#{expires}" \
|
928
|
-
"&Signature=#{CGI.escape signed_string}"
|
929
|
-
end
|
930
|
-
|
931
|
-
def format_extension_headers headers
|
932
|
-
return "" if headers.nil?
|
933
|
-
fail "Headers must be given in a Hash" unless headers.is_a? Hash
|
934
|
-
flatten = headers.map do |key, value|
|
935
|
-
"#{key.to_s.downcase}:#{value.gsub(/\s+/, ' ')}\n"
|
936
|
-
end
|
937
|
-
flatten.reject! { |h| h.start_with? "x-goog-encryption-key" }
|
938
|
-
flatten.sort.join
|
939
|
-
end
|
940
|
-
end
|
941
|
-
|
942
853
|
##
|
943
854
|
# Yielded to a block to accumulate changes for a patch request.
|
944
855
|
class Updater < File
|
@@ -76,11 +76,7 @@ module Google
|
|
76
76
|
#
|
77
77
|
def reload!
|
78
78
|
gapi = @service.list_file_acls @bucket, @file
|
79
|
-
acls = Array(gapi.items)
|
80
|
-
next acl if acl.is_a? Google::Apis::StorageV1::ObjectAccessControl
|
81
|
-
fail "Unknown ACL format: #{acl.class}" unless acl.is_a? Hash
|
82
|
-
Google::Apis::StorageV1::ObjectAccessControl.from_json acl.to_json
|
83
|
-
end
|
79
|
+
acls = Array(gapi.items)
|
84
80
|
@owners = entities_from_acls acls, "OWNER"
|
85
81
|
@readers = entities_from_acls acls, "READER"
|
86
82
|
end
|
@@ -87,15 +87,15 @@ module Google
|
|
87
87
|
end
|
88
88
|
|
89
89
|
##
|
90
|
-
# Retrieves
|
91
|
-
# returns `false`. Calls the given block once for each
|
92
|
-
# passed as the
|
90
|
+
# Retrieves remaining results by repeatedly invoking {#next} until
|
91
|
+
# {#next?} returns `false`. Calls the given block once for each
|
92
|
+
# result, which is passed as the argument to the block.
|
93
93
|
#
|
94
94
|
# An Enumerator is returned if no block is given.
|
95
95
|
#
|
96
|
-
# This method
|
97
|
-
# retrieved.
|
98
|
-
#
|
96
|
+
# This method will make repeated API calls until all remaining results
|
97
|
+
# are retrieved. (Unlike `#each`, for example, which merely iterates
|
98
|
+
# over the results returned by a single API call.) Use with caution.
|
99
99
|
#
|
100
100
|
# @param [Integer] request_limit The upper limit of API requests to
|
101
101
|
# make to load all files. Default is no limit.
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# Copyright 2017 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 "base64"
|
17
|
+
require "openssl"
|
18
|
+
require "google/cloud/storage/errors"
|
19
|
+
|
20
|
+
module Google
|
21
|
+
module Cloud
|
22
|
+
module Storage
|
23
|
+
class File
|
24
|
+
##
|
25
|
+
# @private Create a signed_url for a file.
|
26
|
+
class Signer
|
27
|
+
def initialize bucket, path, service
|
28
|
+
@bucket = bucket
|
29
|
+
@path = path
|
30
|
+
@service = service
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.from_file file
|
34
|
+
new file.bucket, file.name, file.service
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_bucket bucket, path
|
38
|
+
new bucket.name, path, bucket.service
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# The external path to the file.
|
43
|
+
def ext_path
|
44
|
+
URI.escape "/#{@bucket}/#{@path}"
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# The external url to the file.
|
49
|
+
def ext_url
|
50
|
+
"#{GOOGLEAPIS_URL}#{ext_path}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_option_defaults options
|
54
|
+
adjusted_expires = (Time.now.utc + (options[:expires] || 300)).to_i
|
55
|
+
options[:expires] = adjusted_expires
|
56
|
+
options[:method] ||= "GET"
|
57
|
+
options
|
58
|
+
end
|
59
|
+
|
60
|
+
def signature_str options
|
61
|
+
[options[:method], options[:content_md5],
|
62
|
+
options[:content_type], options[:expires],
|
63
|
+
format_extension_headers(options[:headers]) + ext_path].join "\n"
|
64
|
+
end
|
65
|
+
|
66
|
+
def determine_signing_key options = {}
|
67
|
+
options[:signing_key] || options[:private_key] ||
|
68
|
+
@service.credentials.signing_key
|
69
|
+
end
|
70
|
+
|
71
|
+
def determine_issuer options = {}
|
72
|
+
options[:issuer] || options[:client_email] ||
|
73
|
+
@service.credentials.issuer
|
74
|
+
end
|
75
|
+
|
76
|
+
def post_object options
|
77
|
+
options = apply_option_defaults options
|
78
|
+
|
79
|
+
fields = {
|
80
|
+
key: ext_path.sub("/", "")
|
81
|
+
}
|
82
|
+
|
83
|
+
p = options[:policy] || {}
|
84
|
+
fail "Policy must be given in a Hash" unless p.is_a? Hash
|
85
|
+
|
86
|
+
i = determine_issuer options
|
87
|
+
s = determine_signing_key options
|
88
|
+
|
89
|
+
fail SignedUrlUnavailable unless i && s
|
90
|
+
|
91
|
+
policy_str = p.to_json
|
92
|
+
policy = Base64.strict_encode64(policy_str).delete("\n")
|
93
|
+
|
94
|
+
signature = generate_signature s, policy
|
95
|
+
|
96
|
+
fields[:GoogleAccessId] = i
|
97
|
+
fields[:signature] = signature
|
98
|
+
fields[:policy] = policy
|
99
|
+
|
100
|
+
Google::Cloud::Storage::PostObject.new GOOGLEAPIS_URL, fields
|
101
|
+
end
|
102
|
+
|
103
|
+
def signed_url options
|
104
|
+
options = apply_option_defaults options
|
105
|
+
|
106
|
+
i = determine_issuer options
|
107
|
+
s = determine_signing_key options
|
108
|
+
|
109
|
+
fail SignedUrlUnavailable unless i && s
|
110
|
+
|
111
|
+
sig = generate_signature s, signature_str(options)
|
112
|
+
generate_signed_url i, sig, options[:expires]
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_signature signing_key, secret
|
116
|
+
unless signing_key.respond_to? :sign
|
117
|
+
signing_key = OpenSSL::PKey::RSA.new signing_key
|
118
|
+
end
|
119
|
+
signature = signing_key.sign OpenSSL::Digest::SHA256.new, secret
|
120
|
+
Base64.strict_encode64(signature).delete("\n")
|
121
|
+
end
|
122
|
+
|
123
|
+
def generate_signed_url issuer, signed_string, expires
|
124
|
+
"#{ext_url}?GoogleAccessId=#{CGI.escape issuer}" \
|
125
|
+
"&Expires=#{expires}" \
|
126
|
+
"&Signature=#{CGI.escape signed_string}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def format_extension_headers headers
|
130
|
+
return "" if headers.nil?
|
131
|
+
fail "Headers must be given in a Hash" unless headers.is_a? Hash
|
132
|
+
flatten = headers.map do |key, value|
|
133
|
+
"#{key.to_s.downcase}:#{value.gsub(/\s+/, ' ')}\n"
|
134
|
+
end
|
135
|
+
flatten.reject! { |h| h.start_with? "x-goog-encryption-key" }
|
136
|
+
flatten.sort.join
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -52,14 +52,24 @@ module Google
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def self.md5_for local_file
|
55
|
-
|
56
|
-
::
|
55
|
+
if local_file.respond_to? :path
|
56
|
+
::File.open(Pathname(local_file).to_path, "rb") do |f|
|
57
|
+
::Digest::MD5.file(f).base64digest
|
58
|
+
end
|
59
|
+
else # StringIO
|
60
|
+
local_file.rewind
|
61
|
+
::Digest::MD5.base64digest local_file.read
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
60
65
|
def self.crc32c_for local_file
|
61
|
-
|
62
|
-
::
|
66
|
+
if local_file.respond_to? :path
|
67
|
+
::File.open(Pathname(local_file).to_path, "rb") do |f|
|
68
|
+
::Digest::CRC32c.file(f).base64digest
|
69
|
+
end
|
70
|
+
else # StringIO
|
71
|
+
local_file.rewind
|
72
|
+
::Digest::CRC32c.base64digest local_file.read
|
63
73
|
end
|
64
74
|
end
|
65
75
|
end
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
|
16
|
-
require "google/cloud/
|
16
|
+
require "google/cloud/env"
|
17
17
|
require "google/cloud/storage/errors"
|
18
18
|
require "google/cloud/storage/service"
|
19
19
|
require "google/cloud/storage/credentials"
|
@@ -82,7 +82,7 @@ module Google
|
|
82
82
|
ENV["STORAGE_PROJECT"] ||
|
83
83
|
ENV["GOOGLE_CLOUD_PROJECT"] ||
|
84
84
|
ENV["GCLOUD_PROJECT"] ||
|
85
|
-
Google::Cloud
|
85
|
+
Google::Cloud.env.project_id
|
86
86
|
end
|
87
87
|
|
88
88
|
##
|
@@ -299,6 +299,110 @@ module Google
|
|
299
299
|
Bucket.from_gapi gapi, service
|
300
300
|
end
|
301
301
|
|
302
|
+
##
|
303
|
+
# Access without authentication can be granted to a File for a specified
|
304
|
+
# period of time. This URL uses a cryptographic signature of your
|
305
|
+
# credentials to access the file identified by `path`. A URL can be
|
306
|
+
# created for paths that do not yet exist. For instance, a URL can be
|
307
|
+
# created to `PUT` file contents to.
|
308
|
+
#
|
309
|
+
# Generating a URL requires service account credentials, either by
|
310
|
+
# connecting with a service account when calling
|
311
|
+
# {Google::Cloud.storage}, or by passing in the service account `issuer`
|
312
|
+
# and `signing_key` values. Although the private key can be passed as a
|
313
|
+
# string for convenience, creating and storing an instance of
|
314
|
+
# `OpenSSL::PKey::RSA` is more efficient when making multiple calls to
|
315
|
+
# `signed_url`.
|
316
|
+
#
|
317
|
+
# A {SignedUrlUnavailable} is raised if the service account credentials
|
318
|
+
# are missing. Service account credentials are acquired by following the
|
319
|
+
# steps in [Service Account Authentication](
|
320
|
+
# https://cloud.google.com/storage/docs/authentication#service_accounts).
|
321
|
+
#
|
322
|
+
# @see https://cloud.google.com/storage/docs/access-control#Signed-URLs
|
323
|
+
# Access Control Signed URLs guide
|
324
|
+
#
|
325
|
+
# @param [String] bucket Name of the bucket.
|
326
|
+
# @param [String] path Path to the file in Google Cloud Storage.
|
327
|
+
# @param [String] method The HTTP verb to be used with the signed URL.
|
328
|
+
# Signed URLs can be used
|
329
|
+
# with `GET`, `HEAD`, `PUT`, and `DELETE` requests. Default is `GET`.
|
330
|
+
# @param [Integer] expires The number of seconds until the URL expires.
|
331
|
+
# Default is 300/5 minutes.
|
332
|
+
# @param [String] content_type When provided, the client (browser) must
|
333
|
+
# send this value in the HTTP header. e.g. `text/plain`
|
334
|
+
# @param [String] content_md5 The MD5 digest value in base64. If you
|
335
|
+
# provide this in the string, the client (usually a browser) must
|
336
|
+
# provide this HTTP header with this same value in its request.
|
337
|
+
# @param [Hash] headers Google extension headers (custom HTTP headers
|
338
|
+
# that begin with `x-goog-`) that must be included in requests that
|
339
|
+
# use the signed URL.
|
340
|
+
# @param [String] issuer Service Account's Client Email.
|
341
|
+
# @param [String] client_email Service Account's Client Email.
|
342
|
+
# @param [OpenSSL::PKey::RSA, String] signing_key Service Account's
|
343
|
+
# Private Key.
|
344
|
+
# @param [OpenSSL::PKey::RSA, String] private_key Service Account's
|
345
|
+
# Private Key.
|
346
|
+
#
|
347
|
+
# @example
|
348
|
+
# require "google/cloud/storage"
|
349
|
+
#
|
350
|
+
# storage = Google::Cloud::Storage.new
|
351
|
+
#
|
352
|
+
# bucket_name = "my-todo-app"
|
353
|
+
# file_path = "avatars/heidi/400x400.png"
|
354
|
+
# shared_url = storage.signed_url bucket_name, file_path
|
355
|
+
#
|
356
|
+
# @example Any of the option parameters may be specified:
|
357
|
+
# require "google/cloud/storage"
|
358
|
+
#
|
359
|
+
# storage = Google::Cloud::Storage.new
|
360
|
+
#
|
361
|
+
# bucket_name = "my-todo-app"
|
362
|
+
# file_path = "avatars/heidi/400x400.png"
|
363
|
+
# shared_url = storage.signed_url bucket_name, file_path,
|
364
|
+
# method: "PUT",
|
365
|
+
# content_type: "image/png",
|
366
|
+
# expires: 300 # 5 minutes from now
|
367
|
+
#
|
368
|
+
# @example Using the issuer and signing_key options:
|
369
|
+
# require "google/cloud/storage"
|
370
|
+
#
|
371
|
+
# storage = Google::Cloud.storage
|
372
|
+
#
|
373
|
+
# bucket_name = "my-todo-app"
|
374
|
+
# file_path = "avatars/heidi/400x400.png"
|
375
|
+
# issuer_email = "service-account@gcloud.com"
|
376
|
+
# key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."
|
377
|
+
# shared_url = storage.signed_url bucket_name, file_path,
|
378
|
+
# issuer: issuer_email,
|
379
|
+
# signing_key: key
|
380
|
+
#
|
381
|
+
# @example Using the headers option:
|
382
|
+
# require "google/cloud/storage"
|
383
|
+
#
|
384
|
+
# storage = Google::Cloud.storage
|
385
|
+
#
|
386
|
+
# bucket_name = "my-todo-app"
|
387
|
+
# file_path = "avatars/heidi/400x400.png"
|
388
|
+
# shared_url = storage.signed_url bucket_name, file_path,
|
389
|
+
# headers: {
|
390
|
+
# "x-goog-acl" => "private",
|
391
|
+
# "x-goog-meta-foo" => "bar,baz"
|
392
|
+
# }
|
393
|
+
#
|
394
|
+
def signed_url bucket, path, method: nil, expires: nil,
|
395
|
+
content_type: nil, content_md5: nil, headers: nil,
|
396
|
+
issuer: nil, client_email: nil, signing_key: nil,
|
397
|
+
private_key: nil
|
398
|
+
options = { method: method, expires: expires, headers: headers,
|
399
|
+
content_type: content_type, content_md5: content_md5,
|
400
|
+
issuer: issuer, client_email: client_email,
|
401
|
+
signing_key: signing_key, private_key: private_key }
|
402
|
+
signer = File::Signer.new bucket, path, service
|
403
|
+
signer.signed_url options
|
404
|
+
end
|
405
|
+
|
302
406
|
protected
|
303
407
|
|
304
408
|
def acl_rule option_name
|
@@ -49,6 +49,9 @@ module Google
|
|
49
49
|
@service.request_options.retries = retries || 3
|
50
50
|
@service.request_options.timeout_sec = timeout
|
51
51
|
@service.request_options.open_timeout_sec = timeout
|
52
|
+
@service.request_options.header ||= {}
|
53
|
+
@service.request_options.header["x-goog-api-client"] = \
|
54
|
+
"gl-ruby/#{RUBY_VERSION} gccl/#{Google::Cloud::Storage::VERSION}"
|
52
55
|
@service.authorization = @credentials.client
|
53
56
|
end
|
54
57
|
|
@@ -178,7 +181,8 @@ module Google
|
|
178
181
|
content_encoding: content_encoding, crc32c: crc32c,
|
179
182
|
content_language: content_language, metadata: metadata,
|
180
183
|
storage_class: storage_class }.delete_if { |_k, v| v.nil? })
|
181
|
-
content_type ||= mime_type_for(Pathname(source).to_path)
|
184
|
+
content_type ||= mime_type_for(path || Pathname(source).to_path)
|
185
|
+
|
182
186
|
execute do
|
183
187
|
service.insert_object \
|
184
188
|
bucket_name, file_obj,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: google-cloud-storage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Moore
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-04-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: google-cloud-core
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: '1.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
27
|
+
version: '1.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: google-api-client
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -201,6 +201,7 @@ files:
|
|
201
201
|
- lib/google/cloud/storage/file.rb
|
202
202
|
- lib/google/cloud/storage/file/acl.rb
|
203
203
|
- lib/google/cloud/storage/file/list.rb
|
204
|
+
- lib/google/cloud/storage/file/signer.rb
|
204
205
|
- lib/google/cloud/storage/file/verifier.rb
|
205
206
|
- lib/google/cloud/storage/post_object.rb
|
206
207
|
- lib/google/cloud/storage/project.rb
|
@@ -226,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
226
227
|
version: '0'
|
227
228
|
requirements: []
|
228
229
|
rubyforge_project:
|
229
|
-
rubygems_version: 2.6.
|
230
|
+
rubygems_version: 2.6.11
|
230
231
|
signing_key:
|
231
232
|
specification_version: 4
|
232
233
|
summary: API Client library for Google Cloud Storage
|