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