gcloud 0.4.0 → 0.4.1
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 +8 -8
- data/CHANGELOG.md +21 -0
- data/lib/gcloud/bigquery/dataset.rb +1 -1
- data/lib/gcloud/bigquery/table.rb +1 -1
- data/lib/gcloud/bigquery/view.rb +1 -1
- data/lib/gcloud/storage/bucket.rb +305 -36
- data/lib/gcloud/storage/bucket/acl.rb +24 -6
- data/lib/gcloud/storage/bucket/cors.rb +112 -0
- data/lib/gcloud/storage/connection.rb +134 -73
- data/lib/gcloud/storage/file.rb +246 -6
- data/lib/gcloud/storage/file/acl.rb +12 -3
- data/lib/gcloud/storage/project.rb +89 -5
- data/lib/gcloud/version.rb +1 -1
- metadata +3 -2
@@ -318,7 +318,7 @@ module Gcloud
|
|
318
318
|
end
|
319
319
|
|
320
320
|
##
|
321
|
-
#
|
321
|
+
# Permanently deletes the entity from the bucket's access control list.
|
322
322
|
#
|
323
323
|
# === Parameters
|
324
324
|
#
|
@@ -468,11 +468,20 @@ module Gcloud
|
|
468
468
|
|
469
469
|
protected
|
470
470
|
|
471
|
+
def clear!
|
472
|
+
@owners = nil
|
473
|
+
@writers = nil
|
474
|
+
@readers = nil
|
475
|
+
self
|
476
|
+
end
|
477
|
+
|
471
478
|
def update_predefined_acl! acl_role
|
472
479
|
resp = @connection.patch_bucket @bucket,
|
473
|
-
|
480
|
+
predefined_acl: acl_role,
|
481
|
+
acl: []
|
474
482
|
|
475
|
-
resp.success?
|
483
|
+
return clear! if resp.success?
|
484
|
+
fail Gcloud::Storage::ApiError.from_response(resp)
|
476
485
|
end
|
477
486
|
|
478
487
|
def entities_from_acls acls, role
|
@@ -785,7 +794,7 @@ module Gcloud
|
|
785
794
|
end
|
786
795
|
|
787
796
|
##
|
788
|
-
#
|
797
|
+
# Permanently deletes the entity from the bucket's default access
|
789
798
|
# control list for files.
|
790
799
|
#
|
791
800
|
# === Parameters
|
@@ -957,11 +966,20 @@ module Gcloud
|
|
957
966
|
|
958
967
|
protected
|
959
968
|
|
969
|
+
def clear!
|
970
|
+
@owners = nil
|
971
|
+
@writers = nil
|
972
|
+
@readers = nil
|
973
|
+
self
|
974
|
+
end
|
975
|
+
|
960
976
|
def update_predefined_default_acl! acl_role
|
961
977
|
resp = @connection.patch_bucket @bucket,
|
962
|
-
|
978
|
+
predefined_default_acl: acl_role,
|
979
|
+
default_acl: []
|
963
980
|
|
964
|
-
resp.success?
|
981
|
+
return clear! if resp.success?
|
982
|
+
fail Gcloud::Storage::ApiError.from_response(resp)
|
965
983
|
end
|
966
984
|
|
967
985
|
def entities_from_acls acls, role
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2015 Google Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Gcloud
|
17
|
+
module Storage
|
18
|
+
class Bucket
|
19
|
+
##
|
20
|
+
# = Bucket Cors
|
21
|
+
#
|
22
|
+
# A special-case Array for managing the website CORS rules for a bucket.
|
23
|
+
# Accessed via a block argument to Project#create_bucket, Bucket#cors, or
|
24
|
+
# Bucket#update.
|
25
|
+
#
|
26
|
+
# For more information about CORS, see {Cross-Origin Resource
|
27
|
+
# Sharing (CORS)}[https://cloud.google.com/storage/docs/cross-origin].
|
28
|
+
#
|
29
|
+
# require "gcloud"
|
30
|
+
#
|
31
|
+
# gcloud = Gcloud.new
|
32
|
+
# storage = gcloud.storage
|
33
|
+
# bucket = storage.bucket "my-todo-app"
|
34
|
+
#
|
35
|
+
# bucket.cors do |c|
|
36
|
+
# # Remove the last CORS rule from the array
|
37
|
+
# c.pop
|
38
|
+
# # Remove all existing rules with the https protocol
|
39
|
+
# c.delete_if { |r| r["origin"].include? "http://example.com" }
|
40
|
+
# c.add_rule ["http://example.org", "https://example.org"],
|
41
|
+
# ["GET", "POST", "DELETE"],
|
42
|
+
# response_headers: ["X-My-Custom-Header"],
|
43
|
+
# max_age: 3600
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
class Cors < DelegateClass(::Array)
|
47
|
+
##
|
48
|
+
# Initialize a new CORS rules builder with existing CORS rules, if any.
|
49
|
+
def initialize cors = [] #:nodoc:
|
50
|
+
super cors.dup
|
51
|
+
@original = cors.dup
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed? #:nodoc:
|
55
|
+
@original != self
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Add a CORS rule to the CORS rules for a bucket. Accepts options for
|
60
|
+
# setting preflight response headers. Preflight requests and responses
|
61
|
+
# are required if the request method and headers are not both {simple
|
62
|
+
# methods}[http://www.w3.org/TR/cors/#simple-method] and {simple
|
63
|
+
# headers}[http://www.w3.org/TR/cors/#simple-header].
|
64
|
+
#
|
65
|
+
# === Parameters
|
66
|
+
#
|
67
|
+
# +origin+::
|
68
|
+
# The {origin}[http://tools.ietf.org/html/rfc6454] or origins
|
69
|
+
# permitted for cross origin resource sharing with the bucket. Note:
|
70
|
+
# "*" is permitted in the list of origins, and means "any Origin".
|
71
|
+
# (+String+ or +Array+)
|
72
|
+
# +methods+::
|
73
|
+
# The list of HTTP methods permitted in cross origin resource sharing
|
74
|
+
# with the bucket. (GET, OPTIONS, POST, etc) Note: "*" is permitted in
|
75
|
+
# the list of methods, and means "any method". (+String+ or +Array+)
|
76
|
+
# +options+::
|
77
|
+
# An optional Hash for controlling additional behavior. (+Hash+)
|
78
|
+
# <code>options[:headers]</code>::
|
79
|
+
# The list of header field names to send in the
|
80
|
+
# Access-Control-Allow-Headers header in the preflight response.
|
81
|
+
# Indicates the custom request headers that may be used in the actual
|
82
|
+
# request. (+String+ or +Array+)
|
83
|
+
# <code>options[:max_age]</code>::
|
84
|
+
# The value to send in the Access-Control-Max-Age header in the
|
85
|
+
# preflight response. Indicates how many seconds the results of a
|
86
|
+
# preflight request can be cached in a preflight result cache. The
|
87
|
+
# default value is +1800+ (30 minutes.) (+Integer+)
|
88
|
+
#
|
89
|
+
# === Example
|
90
|
+
#
|
91
|
+
# require "gcloud"
|
92
|
+
#
|
93
|
+
# gcloud = Gcloud.new
|
94
|
+
# storage = gcloud.storage
|
95
|
+
#
|
96
|
+
# bucket = storage.create_bucket "my-bucket" do |c|
|
97
|
+
# c.add_rule ["http://example.org", "https://example.org"],
|
98
|
+
# "*",
|
99
|
+
# response_headers: ["X-My-Custom-Header"],
|
100
|
+
# max_age: 300
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
def add_rule origin, methods, options = {}
|
104
|
+
rule = { "origin" => Array(origin), "method" => Array(methods) }
|
105
|
+
rule["responseHeader"] = Array(options[:headers]) || []
|
106
|
+
rule["maxAgeSeconds"] = options[:max_age] || 1800
|
107
|
+
push rule
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -75,27 +75,28 @@ module Gcloud
|
|
75
75
|
@client.execute(
|
76
76
|
api_method: @storage.buckets.insert,
|
77
77
|
parameters: params,
|
78
|
-
body_object:
|
78
|
+
body_object: insert_bucket_request(bucket_name, options)
|
79
79
|
)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
83
|
##
|
84
|
-
# Updates a bucket
|
84
|
+
# Updates a bucket, including its ACL metadata.
|
85
85
|
def patch_bucket bucket_name, options = {}
|
86
86
|
params = { bucket: bucket_name,
|
87
|
-
predefinedAcl: options[:
|
88
|
-
predefinedDefaultObjectAcl: options
|
87
|
+
predefinedAcl: options[:predefined_acl],
|
88
|
+
predefinedDefaultObjectAcl: options[:predefined_default_acl]
|
89
89
|
}.delete_if { |_, v| v.nil? }
|
90
90
|
|
91
91
|
@client.execute(
|
92
92
|
api_method: @storage.buckets.patch,
|
93
|
-
parameters: params
|
93
|
+
parameters: params,
|
94
|
+
body_object: patch_bucket_request(options)
|
94
95
|
)
|
95
96
|
end
|
96
97
|
|
97
98
|
##
|
98
|
-
#
|
99
|
+
# Permanently deletes an empty bucket.
|
99
100
|
def delete_bucket bucket_name, opts = {}
|
100
101
|
incremental_backoff opts do
|
101
102
|
@client.execute(
|
@@ -125,7 +126,7 @@ module Gcloud
|
|
125
126
|
end
|
126
127
|
|
127
128
|
##
|
128
|
-
#
|
129
|
+
# Permanently deletes a bucket ACL.
|
129
130
|
def delete_bucket_acl bucket_name, entity
|
130
131
|
@client.execute(
|
131
132
|
api_method: @storage.bucket_access_controls.delete,
|
@@ -153,7 +154,7 @@ module Gcloud
|
|
153
154
|
end
|
154
155
|
|
155
156
|
##
|
156
|
-
#
|
157
|
+
# Permanently deletes a default ACL.
|
157
158
|
def delete_default_acl bucket_name, entity
|
158
159
|
@client.execute(
|
159
160
|
api_method: @storage.default_object_access_controls.delete,
|
@@ -177,73 +178,25 @@ module Gcloud
|
|
177
178
|
)
|
178
179
|
end
|
179
180
|
|
180
|
-
# rubocop:disable Metrics/MethodLength
|
181
|
-
# rubocop:disable Metrics/AbcSize
|
182
|
-
# Disabled rubocop because the API we need to use
|
183
|
-
# is verbose. No getting around it.
|
184
|
-
|
185
|
-
##
|
186
|
-
# Stores a new object and metadata.
|
187
|
-
# Uses a multipart form post.
|
188
|
-
def insert_file_multipart bucket_name, file, path = nil,
|
189
|
-
options = {}
|
190
|
-
local_path = Pathname(file).to_path
|
191
|
-
upload_path = Pathname(path || local_path).to_path
|
192
|
-
mime_type = mime_type_for local_path
|
193
|
-
|
194
|
-
media = Google::APIClient::UploadIO.new local_path, mime_type
|
195
|
-
|
196
|
-
params = { uploadType: "multipart",
|
197
|
-
bucket: bucket_name,
|
198
|
-
name: upload_path,
|
199
|
-
predefinedAcl: options[:acl]
|
200
|
-
}.delete_if { |_, v| v.nil? }
|
201
|
-
|
202
|
-
@client.execute(
|
203
|
-
api_method: @storage.objects.insert,
|
204
|
-
media: media,
|
205
|
-
parameters: params,
|
206
|
-
body_object: { contentType: mime_type }
|
207
|
-
)
|
208
|
-
end
|
209
|
-
|
210
181
|
##
|
211
|
-
# Stores a new object and metadata.
|
212
|
-
#
|
213
|
-
|
214
|
-
|
182
|
+
# Stores a new object and metadata. If resumable is true, a resumable
|
183
|
+
# upload, otherwise uses a multipart form post.
|
184
|
+
#
|
185
|
+
# UploadIO comes from Faraday, which gets it from multipart-post
|
186
|
+
# The initializer signature is:
|
187
|
+
# filename_or_io, content_type, filename = nil, opts = {}
|
188
|
+
def upload_file resumable, bucket_name, file, path = nil, options = {}
|
215
189
|
local_path = Pathname(file).to_path
|
190
|
+
options[:content_type] ||= mime_type_for(local_path)
|
191
|
+
media = file_media local_path, options, resumable
|
216
192
|
upload_path = Pathname(path || local_path).to_path
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
# This comes from Faraday, which gets it from multipart-post
|
221
|
-
# The signature is:
|
222
|
-
# filename_or_io, content_type, filename = nil, opts = {}
|
223
|
-
|
224
|
-
media = Google::APIClient::UploadIO.new local_path, mime_type
|
225
|
-
media.chunk_size = chunk_size
|
226
|
-
|
227
|
-
params = { uploadType: "resumable",
|
228
|
-
bucket: bucket_name,
|
229
|
-
name: upload_path,
|
230
|
-
predefinedAcl: options[:acl]
|
231
|
-
}.delete_if { |_, v| v.nil? }
|
232
|
-
|
233
|
-
result = @client.execute(
|
234
|
-
api_method: @storage.objects.insert,
|
235
|
-
media: media,
|
236
|
-
parameters: params,
|
237
|
-
body_object: { contentType: mime_type }
|
238
|
-
)
|
193
|
+
result = insert_file resumable, bucket_name, upload_path, media, options
|
194
|
+
return result unless resumable
|
239
195
|
upload = result.resumable_upload
|
240
196
|
result = @client.execute upload while upload.resumable?
|
241
197
|
result
|
242
198
|
end
|
243
199
|
|
244
|
-
# rubocop:enable Metrics/MethodLength
|
245
|
-
# rubocop:enable Metrics/AbcSize
|
246
|
-
|
247
200
|
##
|
248
201
|
# Retrieves an object or its metadata.
|
249
202
|
def get_file bucket_name, file_path, options = {}
|
@@ -259,12 +212,12 @@ module Gcloud
|
|
259
212
|
## Copy a file from source bucket/object to a
|
260
213
|
# destination bucket/object.
|
261
214
|
def copy_file source_bucket_name, source_file_path,
|
262
|
-
destination_bucket_name, destination_file_path,
|
263
|
-
options = {}
|
215
|
+
destination_bucket_name, destination_file_path, options = {}
|
264
216
|
@client.execute(
|
265
217
|
api_method: @storage.objects.copy,
|
266
218
|
parameters: { sourceBucket: source_bucket_name,
|
267
219
|
sourceObject: source_file_path,
|
220
|
+
sourceGeneration: options[:generation],
|
268
221
|
destinationBucket: destination_bucket_name,
|
269
222
|
destinationObject: destination_file_path,
|
270
223
|
predefinedAcl: options[:acl]
|
@@ -287,17 +240,18 @@ module Gcloud
|
|
287
240
|
def patch_file bucket_name, file_path, options = {}
|
288
241
|
params = { bucket: bucket_name,
|
289
242
|
object: file_path,
|
290
|
-
predefinedAcl: options
|
243
|
+
predefinedAcl: options[:predefined_acl]
|
291
244
|
}.delete_if { |_, v| v.nil? }
|
292
245
|
|
293
246
|
@client.execute(
|
294
247
|
api_method: @storage.objects.patch,
|
295
|
-
parameters: params
|
248
|
+
parameters: params,
|
249
|
+
body_object: patch_file_request(options)
|
296
250
|
)
|
297
251
|
end
|
298
252
|
|
299
253
|
##
|
300
|
-
#
|
254
|
+
# Permanently deletes a file.
|
301
255
|
def delete_file bucket_name, file_path
|
302
256
|
@client.execute(
|
303
257
|
api_method: @storage.objects.delete,
|
@@ -329,7 +283,7 @@ module Gcloud
|
|
329
283
|
end
|
330
284
|
|
331
285
|
##
|
332
|
-
#
|
286
|
+
# Permanently deletes a file ACL.
|
333
287
|
def delete_file_acl bucket_name, file_name, entity, options = {}
|
334
288
|
query = { bucket: bucket_name, object: file_name, entity: entity }
|
335
289
|
query[:generation] = options[:generation] if options[:generation]
|
@@ -353,11 +307,118 @@ module Gcloud
|
|
353
307
|
|
354
308
|
protected
|
355
309
|
|
310
|
+
def insert_bucket_request name, options = {}
|
311
|
+
{
|
312
|
+
"name" => name,
|
313
|
+
"location" => options[:location],
|
314
|
+
"cors" => options[:cors],
|
315
|
+
"logging" => logging_config(options),
|
316
|
+
"storageClass" => storage_class(options[:storage_class]),
|
317
|
+
"versioning" => versioning_config(options[:versioning]),
|
318
|
+
"website" => website_config(options)
|
319
|
+
}.delete_if { |_, v| v.nil? }
|
320
|
+
end
|
321
|
+
|
322
|
+
def patch_bucket_request options = {}
|
323
|
+
{
|
324
|
+
"cors" => options[:cors],
|
325
|
+
"logging" => logging_config(options),
|
326
|
+
"versioning" => versioning_config(options[:versioning]),
|
327
|
+
"website" => website_config(options),
|
328
|
+
"acl" => options[:acl],
|
329
|
+
"defaultObjectAcl" => options[:default_acl]
|
330
|
+
}.delete_if { |_, v| v.nil? }
|
331
|
+
end
|
332
|
+
|
333
|
+
def versioning_config enabled
|
334
|
+
{ "enabled" => enabled } unless enabled.nil?
|
335
|
+
end
|
336
|
+
|
337
|
+
def logging_config options
|
338
|
+
bucket = options[:logging_bucket]
|
339
|
+
prefix = options[:logging_prefix]
|
340
|
+
{
|
341
|
+
"logBucket" => bucket,
|
342
|
+
"logObjectPrefix" => prefix
|
343
|
+
}.delete_if { |_, v| v.nil? } if bucket || prefix
|
344
|
+
end
|
345
|
+
|
346
|
+
def website_config options
|
347
|
+
website_main = options[:website_main]
|
348
|
+
website_404 = options[:website_404]
|
349
|
+
{
|
350
|
+
"mainPageSuffix" => website_main,
|
351
|
+
"notFoundPage" => website_404
|
352
|
+
}.delete_if { |_, v| v.nil? } if website_main || website_404
|
353
|
+
end
|
354
|
+
|
355
|
+
def storage_class str #:nodoc:
|
356
|
+
{ "durable_reduced_availability" => "DURABLE_REDUCED_AVAILABILITY",
|
357
|
+
"dra" => "DURABLE_REDUCED_AVAILABILITY",
|
358
|
+
"durable" => "DURABLE_REDUCED_AVAILABILITY",
|
359
|
+
"nearline" => "NEARLINE",
|
360
|
+
"standard" => "STANDARD" }[str.to_s.downcase]
|
361
|
+
end
|
362
|
+
|
363
|
+
def insert_file resumable, bucket_name, path, media, options
|
364
|
+
params = { uploadType: (resumable ? "resumable" : "multipart"),
|
365
|
+
bucket: bucket_name,
|
366
|
+
name: path,
|
367
|
+
predefinedAcl: options[:acl]
|
368
|
+
}.delete_if { |_, v| v.nil? }
|
369
|
+
|
370
|
+
@client.execute api_method: @storage.objects.insert,
|
371
|
+
media: media,
|
372
|
+
parameters: params,
|
373
|
+
body_object: insert_file_request(options)
|
374
|
+
end
|
375
|
+
|
376
|
+
def file_media local_path, options, resumable
|
377
|
+
media = Google::APIClient::UploadIO.new local_path,
|
378
|
+
options[:content_type]
|
379
|
+
return media unless resumable && options[:chunk_size]
|
380
|
+
media.chunk_size = verify_chunk_size!(options.delete(:chunk_size))
|
381
|
+
media
|
382
|
+
end
|
383
|
+
|
384
|
+
def insert_file_request options = {}
|
385
|
+
request = {
|
386
|
+
"crc32c" => options[:crc32c],
|
387
|
+
"md5Hash" => options[:md5],
|
388
|
+
"metadata" => options[:metadata]
|
389
|
+
}.delete_if { |_, v| v.nil? }
|
390
|
+
request.merge patch_file_request(options)
|
391
|
+
end
|
392
|
+
|
393
|
+
def patch_file_request options = {}
|
394
|
+
{
|
395
|
+
"cacheControl" => options[:cache_control],
|
396
|
+
"contentDisposition" => options[:content_disposition],
|
397
|
+
"contentEncoding" => options[:content_encoding],
|
398
|
+
"contentLanguage" => options[:content_language],
|
399
|
+
"contentType" => options[:content_type],
|
400
|
+
"metadata" => options[:metadata],
|
401
|
+
"acl" => options[:acl]
|
402
|
+
}.delete_if { |_, v| v.nil? }
|
403
|
+
end
|
404
|
+
|
356
405
|
def incremental_backoff options = {}
|
357
406
|
Gcloud::Backoff.new(options).execute do
|
358
407
|
yield
|
359
408
|
end
|
360
409
|
end
|
410
|
+
|
411
|
+
##
|
412
|
+
# Determines if a chunk_size is valid.
|
413
|
+
def verify_chunk_size! chunk_size
|
414
|
+
chunk_size = chunk_size.to_i
|
415
|
+
chunk_mod = 256 * 1024 # 256KB
|
416
|
+
if (chunk_size.to_i % chunk_mod) != 0
|
417
|
+
chunk_size = (chunk_size / chunk_mod) * chunk_mod
|
418
|
+
end
|
419
|
+
return if chunk_size.zero?
|
420
|
+
chunk_size
|
421
|
+
end
|
361
422
|
end
|
362
423
|
end
|
363
424
|
end
|