google-cloud-storage 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|