gcloud 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -318,7 +318,7 @@ module Gcloud
318
318
  end
319
319
 
320
320
  ##
321
- # Permenently deletes the entity from the bucket's access control list.
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
- acl: acl_role
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
- # Permenently deletes the entity from the bucket's default access
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
- default_acl: acl_role
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: { name: bucket_name }
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's metadata.
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[:acl],
88
- predefinedDefaultObjectAcl: options.delete(:default_acl)
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
- # Permenently deletes an empty bucket.
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
- # Permenently deletes a bucket ACL.
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
- # Permenently deletes a default ACL.
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
- # Uses a resumable upload.
213
- def insert_file_resumable bucket_name, file, path = nil,
214
- chunk_size = nil, options = {}
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
- # mime_type = options[:mime_type] || mime_type_for local_path
218
- mime_type = mime_type_for local_path
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.delete(:acl)
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
- # Permenently deletes a file.
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
- # Permenently deletes a file ACL.
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