gcloud 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- Nzc3MzUzZjk2YTg0MTg0NDc5ZTUyOGQwZTUxNmQ4YmRhNmRjY2QwMQ==
4
+ MGI1YjI0MjIxZDhhNmI1YjY0ZWM2MjY0YTg5ZDY3OWZlMmVlYjhhNw==
5
5
  data.tar.gz: !binary |-
6
- YmQ0N2Q3ZWFlZmY2ZGJlZmE5ZWQzYzc2MWM0NzM5ZTEzNTVkNzRhMA==
6
+ MzdkOGRmYTY3MmQ0YzU0ZGM4YmIwZGQwNDdmY2EzNDNjYTY4NjRlYg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZWRjNGM0MzNmZmM4NjQ5YmE0OTY3ZTUxNzRlZjIxYzIyZmJhZTcyZDhmMGFk
10
- N2Q2OWI2YzcyMGM1NDM5YjRiMzM0N2ZlZTNkY2VkNTI1N2Y3YjUzMDUzNGMw
11
- NWI1YTMzNzA5NGVkZTU4ZjAwNGNmZjNhNjRkM2NjOTZmNDk2OWQ=
9
+ MTU4ZGJkZmM4YTg2YWY2YWNiYjQyYmQxNDM3YzBhNTczZDViMzgzMGQyMGIz
10
+ ODVlY2JmNDJjNjljMmRlZTY5MmZhNzdiZTg3ZjUxMTI0MjM5ZTA5OWM4Njg4
11
+ NzdiNmE4ZTVjOTk3MzIwODliYjM0NTcyNmFmNDVmYWFhZDU3OWM=
12
12
  data.tar.gz: !binary |-
13
- YzJkY2FiNDEzZWRlOGViMGU1ZmZiZmM2NGM2YWMyZmRiN2NiNjUyY2JkNGRi
14
- YzAwMTM3NzdmNjNjNzY4MGY3ZmU3MDVmN2MxNzhlODk3YjY1MmRiODliZmRi
15
- NjQxMzBmZmEyNWVjZDUwYjc2MmU5NjFmZmYzMTdiZWRkNjY4MWQ=
13
+ NjNkMTAzNDI0YWE1ZWJjOTVjZGMzZjZlMGNhOTQ2NWNmYWZkZGE2NzFkMDY5
14
+ MGU3OTkxMGRlZWIzNjQzOGM4OTJhNWExY2MyM2E1NzQ1MDNhZTNjMjhkMjAz
15
+ ZjZkZGI2OGJkMjliYzQyNTkyMmFkNzU2MDRkNmNiYWQxYWE4ZmE=
@@ -1,5 +1,26 @@
1
1
  # Release History
2
2
 
3
+ ### 0.4.1 / 2015-10-20
4
+
5
+ #### Changes
6
+
7
+ * Add support for Bucket attributes, including:
8
+ * CORS
9
+ * logging
10
+ * versioning
11
+ * location
12
+ * website
13
+ * storage class
14
+ * Add support for File attributes, including:
15
+ * cache_control
16
+ * content_dispostion
17
+ * content_encoding
18
+ * content_language
19
+ * content_type
20
+ * Add support for File upload validation with MD5 or CRC32c
21
+ * Add File#public_url
22
+ * Improve stability and error reporting of Storage ACL helpers
23
+
3
24
  ### 0.4.0 / 2015-10-12
4
25
 
5
26
  #### Major changes
@@ -117,7 +117,7 @@ module Gcloud
117
117
  #
118
118
  # :category: Attributes
119
119
  #
120
- def url
120
+ def api_url
121
121
  ensure_full_data!
122
122
  @gapi["selfLink"]
123
123
  end
@@ -184,7 +184,7 @@ module Gcloud
184
184
  #
185
185
  # :category: Attributes
186
186
  #
187
- def url
187
+ def api_url
188
188
  ensure_full_data!
189
189
  @gapi["selfLink"]
190
190
  end
@@ -125,7 +125,7 @@ module Gcloud
125
125
  #
126
126
  # :category: Attributes
127
127
  #
128
- def url
128
+ def api_url
129
129
  ensure_full_data!
130
130
  @gapi["selfLink"]
131
131
  end
@@ -15,6 +15,7 @@
15
15
 
16
16
  require "gcloud/storage/bucket/acl"
17
17
  require "gcloud/storage/bucket/list"
18
+ require "gcloud/storage/bucket/cors"
18
19
  require "gcloud/storage/file"
19
20
  require "gcloud/upload"
20
21
 
@@ -69,11 +70,80 @@ module Gcloud
69
70
  end
70
71
 
71
72
  ##
72
- # The URI of this bucket.
73
- def url
73
+ # A URL that can be used to access the bucket using the REST API.
74
+ def api_url
74
75
  @gapi["selfLink"]
75
76
  end
76
77
 
78
+ ##
79
+ # Creation time of the bucket.
80
+ def created_at
81
+ @gapi["timeCreated"]
82
+ end
83
+
84
+ ##
85
+ # Returns the current CORS configuration for a static website served from
86
+ # the bucket. For more information, see {Cross-Origin Resource
87
+ # Sharing (CORS)}[https://cloud.google.com/storage/docs/cross-origin].
88
+ # The return value is a frozen (unmodifiable) array of hashes containing
89
+ # the attributes specified for the Bucket resource field
90
+ # {cors}[https://cloud.google.com/storage/docs/json_api/v1/buckets#cors].
91
+ #
92
+ # This method also accepts a block for updating the bucket's CORS rules.
93
+ # See Bucket::Cors for details.
94
+ #
95
+ # === Examples
96
+ #
97
+ # Retrieving the bucket's CORS rules.
98
+ #
99
+ # require "gcloud"
100
+ #
101
+ # gcloud = Gcloud.new
102
+ # storage = gcloud.storage
103
+ #
104
+ # bucket = storage.bucket "my-todo-app"
105
+ # bucket.cors #=> [{"origin"=>["http://example.org"],
106
+ # # "method"=>["GET","POST","DELETE"],
107
+ # # "responseHeader"=>["X-My-Custom-Header"],
108
+ # # "maxAgeSeconds"=>3600}]
109
+ #
110
+ # Updating the bucket's CORS rules inside a block.
111
+ #
112
+ # require "gcloud"
113
+ #
114
+ # gcloud = Gcloud.new
115
+ # storage = gcloud.storage
116
+ # bucket = storage.bucket "my-todo-app"
117
+ #
118
+ # bucket.update do |b|
119
+ # b.cors do |c|
120
+ # c.add_rule ["http://example.org", "https://example.org"],
121
+ # "*",
122
+ # response_headers: ["X-My-Custom-Header"],
123
+ # max_age: 3600
124
+ # end
125
+ # end
126
+ #
127
+ def cors
128
+ if block_given?
129
+ cors_builder = Bucket::Cors.new @gapi["cors"]
130
+ yield cors_builder
131
+ self.cors = cors_builder if cors_builder.changed?
132
+ end
133
+ deep_dup_and_freeze @gapi["cors"]
134
+ end
135
+
136
+ ##
137
+ # Updates the CORS configuration for a static website served from the
138
+ # bucket. For more information, see {Cross-Origin Resource
139
+ # Sharing (CORS)}[https://cloud.google.com/storage/docs/cross-origin].
140
+ # Accepts an array of hashes containing the attributes specified for the
141
+ # {resource description of
142
+ # cors}[https://cloud.google.com/storage/docs/json_api/v1/buckets#cors].
143
+ def cors= new_cors
144
+ patch_gapi! cors: new_cors
145
+ end
146
+
77
147
  ##
78
148
  # The location of the bucket.
79
149
  # Object data for objects in the bucket resides in physical
@@ -86,13 +156,150 @@ module Gcloud
86
156
  end
87
157
 
88
158
  ##
89
- # Creation time of the bucket.
90
- def created_at
91
- @gapi["timeCreated"]
159
+ # The destination bucket name for the bucket's logs. For more information,
160
+ # see {Access Logs}[https://cloud.google.com/storage/docs/access-logs].
161
+ def logging_bucket
162
+ @gapi["logging"]["logBucket"] if @gapi["logging"]
163
+ end
164
+
165
+ ##
166
+ # Updates the destination bucket for the bucket's logs. For more
167
+ # information, see {Access
168
+ # Logs}[https://cloud.google.com/storage/docs/access-logs]. (+String+)
169
+ def logging_bucket= logging_bucket
170
+ patch_gapi! logging_bucket: logging_bucket
171
+ end
172
+
173
+ ##
174
+ # The logging object prefix for the bucket's logs. For more information,
175
+ # see {Access Logs}[https://cloud.google.com/storage/docs/access-logs].
176
+ def logging_prefix
177
+ @gapi["logging"]["logObjectPrefix"] if @gapi["logging"]
178
+ end
179
+
180
+ ##
181
+ # Updates the logging object prefix. This prefix will be used to create
182
+ # log object names for the bucket. It can be at most 900 characters and
183
+ # must be a {valid object
184
+ # name}[https://cloud.google.com/storage/docs/bucket-naming#objectnames].
185
+ # By default, the object prefix is the name
186
+ # of the bucket for which the logs are enabled. For more information, see
187
+ # {Access Logs}[https://cloud.google.com/storage/docs/access-logs].
188
+ # (+String+)
189
+ def logging_prefix= logging_prefix
190
+ patch_gapi! logging_prefix: logging_prefix
191
+ end
192
+
193
+ ##
194
+ # The bucket's storage class. This defines how objects in the bucket are
195
+ # stored and determines the SLA and the cost of storage. Values include
196
+ # +STANDARD+, +NEARLINE+, and +DURABLE_REDUCED_AVAILABILITY+.
197
+ def storage_class
198
+ @gapi["storageClass"]
199
+ end
200
+
201
+ ##
202
+ # Whether {Object
203
+ # Versioning}[https://cloud.google.com/storage/docs/object-versioning] is
204
+ # enabled for the bucket.
205
+ def versioning?
206
+ !@gapi["versioning"].nil? && @gapi["versioning"]["enabled"]
207
+ end
208
+
209
+ ##
210
+ # Updates whether {Object
211
+ # Versioning}[https://cloud.google.com/storage/docs/object-versioning] is
212
+ # enabled for the bucket. (+Boolean+)
213
+ def versioning= new_versioning
214
+ patch_gapi! versioning: new_versioning
215
+ end
216
+
217
+ ##
218
+ # The index page returned from a static website served from the bucket
219
+ # when a site visitor requests the top level directory. For more
220
+ # information, see {How to Host a Static Website
221
+ # }[https://cloud.google.com/storage/docs/website-configuration#step4].
222
+ def website_main
223
+ @gapi["website"]["mainPageSuffix"] if @gapi["website"]
92
224
  end
93
225
 
94
226
  ##
95
- # Permenently deletes the bucket.
227
+ # Updates the index page returned from a static website served from the
228
+ # bucket when a site visitor requests the top level directory. For more
229
+ # information, see {How to Host a Static Website
230
+ # }[https://cloud.google.com/storage/docs/website-configuration#step4].
231
+ # (+String+)
232
+ def website_main= website_main
233
+ patch_gapi! website_main: website_main
234
+ end
235
+
236
+ ##
237
+ # The page returned from a static website served from the bucket when a
238
+ # site visitor requests a resource that does not exist. For more
239
+ # information, see {How to Host a Static Website
240
+ # }[https://cloud.google.com/storage/docs/website-configuration#step4].
241
+ def website_404
242
+ @gapi["website"]["notFoundPage"] if @gapi["website"]
243
+ end
244
+
245
+ ##
246
+ # Updates the page returned from a static website served from the bucket
247
+ # when a site visitor requests a resource that does not exist. For more
248
+ # information, see {How to Host a Static Website
249
+ # }[https://cloud.google.com/storage/docs/website-configuration#step4].
250
+ # (+String+)
251
+ def website_404= website_404
252
+ patch_gapi! website_404: website_404
253
+ end
254
+
255
+ ##
256
+ # Updates the bucket with changes made in the given block in a single
257
+ # PATCH request. The following attributes may be set: #cors=,
258
+ # #logging_bucket=, #logging_prefix=, #versioning=, #website_main=, and
259
+ # #website_404=. In addition, the #cors configuration accessible in the
260
+ # block is completely mutable and will be included in the request.
261
+ #
262
+ # === Examples
263
+ #
264
+ # require "gcloud"
265
+ #
266
+ # gcloud = Gcloud.new
267
+ # storage = gcloud.storage
268
+ #
269
+ # bucket = storage.bucket "my-bucket"
270
+ # bucket.update do |b|
271
+ # b.website_main = "index.html"
272
+ # b.website_404 = "not_found.html"
273
+ # b.cors[0]["method"] = ["GET","POST","DELETE"]
274
+ # b.cors[1]["responseHeader"] << "X-Another-Custom-Header"
275
+ # end
276
+ #
277
+ # New CORS rules can also be added in a nested block. See Bucket::Cors for
278
+ # details.
279
+ #
280
+ # require "gcloud"
281
+ #
282
+ # gcloud = Gcloud.new
283
+ # storage = gcloud.storage
284
+ # bucket = storage.bucket "my-todo-app"
285
+ #
286
+ # bucket.update do |b|
287
+ # b.cors do |c|
288
+ # c.add_rule ["http://example.org", "https://example.org"],
289
+ # "*",
290
+ # response_headers: ["X-My-Custom-Header"],
291
+ # max_age: 300
292
+ # end
293
+ # end
294
+ #
295
+ def update
296
+ updater = Updater.new @gapi["cors"]
297
+ yield updater
298
+ patch_gapi! updater.updates unless updater.updates.empty?
299
+ end
300
+
301
+ ##
302
+ # Permanently deletes the bucket.
96
303
  # The bucket must be empty before it can be deleted.
97
304
  #
98
305
  # === Parameters
@@ -279,6 +486,43 @@ module Gcloud
279
486
  # and project team members get access according to their roles.
280
487
  # * +public+, +public_read+, +publicRead+ - File owner gets OWNER
281
488
  # access, and allUsers get READER access.
489
+ # <code>options[:cache_control]</code>::
490
+ # The {Cache-Control}[https://tools.ietf.org/html/rfc7234#section-5.2]
491
+ # response header to be returned when the file is downloaded. (+String+)
492
+ # <code>options[:content_disposition]</code>::
493
+ # The {Content-Disposition}[https://tools.ietf.org/html/rfc6266]
494
+ # response header to be returned when the file is downloaded. (+String+)
495
+ # <code>options[:content_encoding]</code>::
496
+ # The {Content-Encoding
497
+ # }[https://tools.ietf.org/html/rfc7231#section-3.1.2.2] response header
498
+ # to be returned when the file is downloaded. (+String+)
499
+ # <code>options[:content_language]</code>::
500
+ # The {Content-Language}[http://tools.ietf.org/html/bcp47] response
501
+ # header to be returned when the file is downloaded. (+String+)
502
+ # <code>options[:content_type]</code>::
503
+ # The {Content-Type}[https://tools.ietf.org/html/rfc2616#section-14.17]
504
+ # response header to be returned when the file is downloaded. (+String+)
505
+ # <code>options[:chunk_size]</code>::
506
+ # The number of bytes per chunk in a resumable upload. Must be divisible
507
+ # by 256KB. If it is not divisible by 265KB then it will be lowered to
508
+ # the nearest acceptable value. (+Integer+)
509
+ # <code>options[:crc32c]</code>::
510
+ # The CRC32c checksum of the file data, as described in
511
+ # {RFC 4960, Appendix B}[http://tools.ietf.org/html/rfc4960#appendix-B].
512
+ # If provided, Cloud Storage will only create the file if the value
513
+ # matches the value calculated by the service. See
514
+ # {Validation}[https://cloud.google.com/storage/docs/hashes-etags]
515
+ # for more information. (+String+)
516
+ # <code>options[:md5]</code>::
517
+ # The MD5 hash of the file data. If provided, Cloud Storage will only
518
+ # create the file if the value matches the value calculated by the
519
+ # service. See
520
+ # {Validation}[https://cloud.google.com/storage/docs/hashes-etags]
521
+ # for more information. (+String+)
522
+ # <code>options[:metadata]</code>::
523
+ # A hash of custom, user-provided web-safe keys and arbitrary string
524
+ # values that will returned with requests for the file as "x-goog-meta-"
525
+ # response headers. (+Hash+)
282
526
  #
283
527
  # === Returns
284
528
  #
@@ -310,7 +554,7 @@ module Gcloud
310
554
  # A chunk_size value can be provided in the options to be used
311
555
  # in resumable uploads. This value is the number of bytes per
312
556
  # chunk and must be divisible by 256KB. If it is not divisible
313
- # by 265KB then it will be lowered to the nearest acceptible
557
+ # by 265KB then it will be lowered to the nearest acceptable
314
558
  # value.
315
559
  #
316
560
  # require "gcloud"
@@ -353,13 +597,14 @@ module Gcloud
353
597
  def create_file file, path = nil, options = {}
354
598
  ensure_connection!
355
599
  ensure_file_exists! file
356
-
357
600
  options[:acl] = File::Acl.predefined_rule_for options[:acl]
601
+ resumable = resumable_upload?(file)
602
+ resp = @connection.upload_file resumable, name, file, path, options
358
603
 
359
- if resumable_upload? file
360
- upload_resumable file, path, options[:chunk_size], options
604
+ if resp.success?
605
+ File.from_gapi resp.data, connection
361
606
  else
362
- upload_multipart file, path, options
607
+ fail ApiError.from_response(resp)
363
608
  end
364
609
  end
365
610
  alias_method :upload_file, :create_file
@@ -503,6 +748,16 @@ module Gcloud
503
748
  fail "Must have active connection" unless connection
504
749
  end
505
750
 
751
+ def patch_gapi! options = {}
752
+ ensure_connection!
753
+ resp = connection.patch_bucket name, options
754
+ if resp.success?
755
+ @gapi = resp.data
756
+ else
757
+ fail ApiError.from_response(resp)
758
+ end
759
+ end
760
+
506
761
  ##
507
762
  # Raise an error if the file is not found.
508
763
  def ensure_file_exists! file
@@ -516,39 +771,53 @@ module Gcloud
516
771
  ::File.size?(file).to_i > Upload.resumable_threshold
517
772
  end
518
773
 
519
- def upload_multipart file, path, options = {}
520
- resp = @connection.insert_file_multipart name, file, path, options
521
-
522
- if resp.success?
523
- File.from_gapi resp.data, connection
524
- else
525
- fail ApiError.from_response(resp)
774
+ ##
775
+ # Given nil, empty array, a gapi array of hashes, or any value, returns a
776
+ # deeply dup'd and frozen array of simple hashes or values (not gapi
777
+ # objects.)
778
+ def deep_dup_and_freeze array
779
+ return [].freeze if array.nil? || array.empty?
780
+ array = Array(array.dup)
781
+ array = array.map do |h|
782
+ h = h.to_hash if h.respond_to? :to_hash
783
+ h.dup.freeze
526
784
  end
785
+ array.freeze
527
786
  end
528
787
 
529
- def upload_resumable file, path, chunk_size, options = {}
530
- chunk_size = verify_chunk_size! chunk_size
788
+ ##
789
+ # Yielded to a block to accumulate changes for a patch request.
790
+ class Updater
791
+ attr_reader :updates
792
+ ##
793
+ # Create an Updater object.
794
+ def initialize cors
795
+ @cors = cors ? Array(cors.dup) : []
796
+ @cors = @cors.map { |x| x.to_hash if x.respond_to? :to_hash }
797
+ @updates = {}
798
+ end
531
799
 
532
- resp = @connection.insert_file_resumable name, file,
533
- path, chunk_size, options
800
+ ATTRS = [:cors, :logging_bucket, :logging_prefix, :versioning,
801
+ :website_main, :website_404]
534
802
 
535
- if resp.success?
536
- File.from_gapi resp.data, connection
537
- else
538
- fail ApiError.from_response(resp)
803
+ ATTRS.each do |attr|
804
+ define_method "#{attr}=" do |arg|
805
+ updates[attr] = arg
806
+ end
539
807
  end
540
- end
541
808
 
542
- ##
543
- # Determines if a chunk_size is valid.
544
- def verify_chunk_size! chunk_size
545
- chunk_size = chunk_size.to_i
546
- chunk_mod = 256 * 1024 # 256KB
547
- if (chunk_size.to_i % chunk_mod) != 0
548
- chunk_size = (chunk_size / chunk_mod) * chunk_mod
809
+ ##
810
+ # Return CORS for mutation. Also adds CORS to @updates so that it
811
+ # is included in the patch request.
812
+ def cors
813
+ updates[:cors] ||= @cors
814
+ if block_given?
815
+ cors_builder = Bucket::Cors.new updates[:cors]
816
+ yield cors_builder
817
+ updates[:cors] = cors_builder if cors_builder.changed?
818
+ end
819
+ updates[:cors]
549
820
  end
550
- return if chunk_size.zero?
551
- chunk_size
552
821
  end
553
822
  end
554
823
  end