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.
@@ -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