google-cloud-storage 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/google-cloud-storage.rb +124 -0
- data/lib/google/cloud/storage.rb +412 -0
- data/lib/google/cloud/storage/bucket.rb +783 -0
- data/lib/google/cloud/storage/bucket/acl.rb +815 -0
- data/lib/google/cloud/storage/bucket/cors.rb +157 -0
- data/lib/google/cloud/storage/bucket/list.rb +174 -0
- data/lib/google/cloud/storage/credentials.rb +31 -0
- data/lib/google/cloud/storage/errors.rb +67 -0
- data/lib/google/cloud/storage/file.rb +849 -0
- data/lib/google/cloud/storage/file/acl.rb +429 -0
- data/lib/google/cloud/storage/file/list.rb +193 -0
- data/lib/google/cloud/storage/file/verifier.rb +69 -0
- data/lib/google/cloud/storage/project.rb +321 -0
- data/lib/google/cloud/storage/service.rb +310 -0
- data/lib/google/cloud/storage/version.rb +22 -0
- metadata +215 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
# Copyright 2015 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "delegate"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module Storage
|
21
|
+
class Bucket
|
22
|
+
##
|
23
|
+
# # Bucket Cors
|
24
|
+
#
|
25
|
+
# A special-case Array for managing the website CORS rules for a bucket.
|
26
|
+
# Accessed via {Bucket#cors}.
|
27
|
+
#
|
28
|
+
# @see https://cloud.google.com/storage/docs/cross-origin Cross-Origin
|
29
|
+
# Resource Sharing (CORS)
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# require "google/cloud"
|
33
|
+
#
|
34
|
+
# gcloud = Google::Cloud.new
|
35
|
+
# storage = gcloud.storage
|
36
|
+
# bucket = storage.bucket "my-todo-app"
|
37
|
+
#
|
38
|
+
# bucket = storage.bucket "my-bucket"
|
39
|
+
# bucket.cors do |c|
|
40
|
+
# # Remove the last CORS rule from the array
|
41
|
+
# c.pop
|
42
|
+
# # Remove all existing rules with the https protocol
|
43
|
+
# c.delete_if { |r| r.origin.include? "http://example.com" }
|
44
|
+
# c.add_rule ["http://example.org", "https://example.org"],
|
45
|
+
# ["GET", "POST", "DELETE"],
|
46
|
+
# response_headers: ["X-My-Custom-Header"],
|
47
|
+
# max_age: 3600
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
class Cors < DelegateClass(::Array)
|
51
|
+
##
|
52
|
+
# @private Initialize a new CORS rules builder with existing CORS
|
53
|
+
# rules, if any.
|
54
|
+
def initialize rules = []
|
55
|
+
super rules
|
56
|
+
@original = to_gapi.map(&:to_json)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @private
|
60
|
+
def changed?
|
61
|
+
@original != to_gapi.map(&:to_json)
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Add a CORS rule to the CORS rules for a bucket. Accepts options for
|
66
|
+
# setting preflight response headers. Preflight requests and responses
|
67
|
+
# are required if the request method and headers are not both [simple
|
68
|
+
# methods](http://www.w3.org/TR/cors/#simple-method) and [simple
|
69
|
+
# headers](http://www.w3.org/TR/cors/#simple-header).
|
70
|
+
#
|
71
|
+
# @param [String, Array<String>] origin The
|
72
|
+
# [origin](http://tools.ietf.org/html/rfc6454) or origins permitted
|
73
|
+
# for cross origin resource sharing with the bucket. Note: "*" is
|
74
|
+
# permitted in the list of origins, and means "any Origin".
|
75
|
+
# @param [String, Array<String>] methods The list of HTTP methods
|
76
|
+
# permitted in cross origin resource sharing with the bucket. (GET,
|
77
|
+
# OPTIONS, POST, etc) Note: "*" is permitted in the list of methods,
|
78
|
+
# and means "any method".
|
79
|
+
# @param [String, Array<String>] headers The list of header field
|
80
|
+
# names to send in the Access-Control-Allow-Headers header in the
|
81
|
+
# preflight response. Indicates the custom request headers that may
|
82
|
+
# be used in the actual request.
|
83
|
+
# @param [Integer] max_age The value to send in the
|
84
|
+
# Access-Control-Max-Age header in the preflight response. Indicates
|
85
|
+
# how many seconds the results of a preflight request can be cached
|
86
|
+
# in a preflight result cache. The default value is `1800` (30
|
87
|
+
# minutes.)
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# require "google/cloud"
|
91
|
+
#
|
92
|
+
# gcloud = Google::Cloud.new
|
93
|
+
# storage = gcloud.storage
|
94
|
+
#
|
95
|
+
# bucket = storage.create_bucket "my-bucket" do |c|
|
96
|
+
# c.add_rule ["http://example.org", "https://example.org"],
|
97
|
+
# "*",
|
98
|
+
# response_headers: ["X-My-Custom-Header"],
|
99
|
+
# max_age: 300
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
def add_rule origin, methods, headers: nil, max_age: nil
|
103
|
+
push Rule.new(origin, methods, headers: headers, max_age: max_age)
|
104
|
+
end
|
105
|
+
|
106
|
+
# @private
|
107
|
+
def to_gapi
|
108
|
+
map(&:to_gapi)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @private
|
112
|
+
def self.from_gapi gapi_list
|
113
|
+
rules = Array(gapi_list).map { |gapi| Rule.from_gapi gapi }
|
114
|
+
new rules
|
115
|
+
end
|
116
|
+
|
117
|
+
# @private
|
118
|
+
def freeze
|
119
|
+
each(&:freeze)
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
123
|
+
class Rule
|
124
|
+
attr_accessor :origin, :methods, :headers, :max_age
|
125
|
+
|
126
|
+
def initialize origin, methods, headers: nil, max_age: nil
|
127
|
+
@origin = Array(origin)
|
128
|
+
@methods = Array(methods)
|
129
|
+
@headers = Array(headers)
|
130
|
+
@max_age = (max_age||1800)
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_gapi
|
134
|
+
Google::Apis::StorageV1::Bucket::CorsConfiguration.new(
|
135
|
+
origin: Array(origin).dup, http_method: Array(methods).dup,
|
136
|
+
response_header: Array(headers).dup, max_age_seconds: max_age
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.from_gapi gapi
|
141
|
+
new gapi.origin.dup, gapi.http_method.dup, \
|
142
|
+
headers: gapi.response_header.dup,
|
143
|
+
max_age: gapi.max_age_seconds
|
144
|
+
end
|
145
|
+
|
146
|
+
def freeze
|
147
|
+
@origin.freeze
|
148
|
+
@methods.freeze
|
149
|
+
@headers.freeze
|
150
|
+
super
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# Copyright 2015 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "delegate"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module Storage
|
21
|
+
class Bucket
|
22
|
+
##
|
23
|
+
# Bucket::List is a special case Array with additional values.
|
24
|
+
class List < DelegateClass(::Array)
|
25
|
+
##
|
26
|
+
# If not empty, indicates that there are more buckets
|
27
|
+
# that match the request and this value should be passed to
|
28
|
+
# the next {Google::Cloud::Storage::Project#buckets} to continue.
|
29
|
+
attr_accessor :token
|
30
|
+
|
31
|
+
##
|
32
|
+
# @private Create a new Bucket::List with an array of values.
|
33
|
+
def initialize arr = []
|
34
|
+
super arr
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Whether there is a next page of buckets.
|
39
|
+
#
|
40
|
+
# @return [Boolean]
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# require "google/cloud"
|
44
|
+
#
|
45
|
+
# gcloud = Google::Cloud.new
|
46
|
+
# storage = gcloud.storage
|
47
|
+
#
|
48
|
+
# buckets = storage.buckets
|
49
|
+
# if buckets.next?
|
50
|
+
# next_buckets = buckets.next
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
def next?
|
54
|
+
!token.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Retrieve the next page of buckets.
|
59
|
+
#
|
60
|
+
# @return [Bucket::List]
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# require "google/cloud"
|
64
|
+
#
|
65
|
+
# gcloud = Google::Cloud.new
|
66
|
+
# storage = gcloud.storage
|
67
|
+
#
|
68
|
+
# buckets = storage.buckets
|
69
|
+
# if buckets.next?
|
70
|
+
# next_buckets = buckets.next
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
def next
|
74
|
+
return nil unless next?
|
75
|
+
ensure_service!
|
76
|
+
options = { prefix: @prefix, token: @token, max: @max }
|
77
|
+
gapi = @service.list_buckets options
|
78
|
+
Bucket::List.from_gapi gapi, @service, @prefix, @max
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Retrieves all buckets by repeatedly loading {#next} until {#next?}
|
83
|
+
# returns `false`. Calls the given block once for each bucket, which
|
84
|
+
# is passed as the parameter.
|
85
|
+
#
|
86
|
+
# An Enumerator is returned if no block is given.
|
87
|
+
#
|
88
|
+
# This method may make several API calls until all buckets are
|
89
|
+
# retrieved. Be sure to use as narrow a search criteria as possible.
|
90
|
+
# Please use with caution.
|
91
|
+
#
|
92
|
+
# @param [Integer] request_limit The upper limit of API requests to
|
93
|
+
# make to load all buckets. Default is no limit.
|
94
|
+
# @yield [bucket] The block for accessing each bucket.
|
95
|
+
# @yieldparam [Bucket] bucket The bucket object.
|
96
|
+
#
|
97
|
+
# @return [Enumerator]
|
98
|
+
#
|
99
|
+
# @example Iterating each bucket by passing a block:
|
100
|
+
# require "google/cloud"
|
101
|
+
#
|
102
|
+
# gcloud = Google::Cloud.new
|
103
|
+
# storage = gcloud.storage
|
104
|
+
#
|
105
|
+
# buckets = storage.buckets
|
106
|
+
# buckets.all do |bucket|
|
107
|
+
# puts bucket.name
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# @example Using the enumerator by not passing a block:
|
111
|
+
# require "google/cloud"
|
112
|
+
#
|
113
|
+
# gcloud = Google::Cloud.new
|
114
|
+
# storage = gcloud.storage
|
115
|
+
#
|
116
|
+
# buckets = storage.buckets
|
117
|
+
# all_names = buckets.all.map do |bucket|
|
118
|
+
# bucket.name
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# @example Limit the number of API calls made:
|
122
|
+
# require "google/cloud"
|
123
|
+
#
|
124
|
+
# gcloud = Google::Cloud.new
|
125
|
+
# storage = gcloud.storage
|
126
|
+
#
|
127
|
+
# buckets = storage.buckets
|
128
|
+
# buckets.all(request_limit: 10) do |bucket|
|
129
|
+
# puts bucket.name
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
def all request_limit: nil
|
133
|
+
request_limit = request_limit.to_i if request_limit
|
134
|
+
unless block_given?
|
135
|
+
return enum_for(:all, request_limit: request_limit)
|
136
|
+
end
|
137
|
+
results = self
|
138
|
+
loop do
|
139
|
+
results.each { |r| yield r }
|
140
|
+
if request_limit
|
141
|
+
request_limit -= 1
|
142
|
+
break if request_limit < 0
|
143
|
+
end
|
144
|
+
break unless results.next?
|
145
|
+
results = results.next
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# @private New Bucket::List from a Google API Client
|
151
|
+
# Google::Apis::StorageV1::Buckets object.
|
152
|
+
def self.from_gapi gapi_list, service, prefix = nil, max = nil
|
153
|
+
buckets = new(Array(gapi_list.items).map do |gapi_object|
|
154
|
+
Bucket.from_gapi gapi_object, service
|
155
|
+
end)
|
156
|
+
buckets.instance_variable_set :@token, gapi_list.next_page_token
|
157
|
+
buckets.instance_variable_set :@service, service
|
158
|
+
buckets.instance_variable_set :@prefix, prefix
|
159
|
+
buckets.instance_variable_set :@max, max
|
160
|
+
buckets
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
##
|
166
|
+
# Raise an error unless an active connection is available.
|
167
|
+
def ensure_service!
|
168
|
+
fail "Must have active connection" unless @service
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright 2014 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "google/cloud/credentials"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module Storage
|
21
|
+
##
|
22
|
+
# @private Represents the OAuth 2.0 signing logic for Storage.
|
23
|
+
class Credentials < Google::Cloud::Credentials
|
24
|
+
SCOPE = ["https://www.googleapis.com/auth/devstorage.full_control"]
|
25
|
+
PATH_ENV_VARS = %w(STORAGE_KEYFILE GOOGLE_CLOUD_KEYFILE GCLOUD_KEYFILE)
|
26
|
+
JSON_ENV_VARS = %w(STORAGE_KEYFILE_JSON GOOGLE_CLOUD_KEYFILE_JSON
|
27
|
+
GCLOUD_KEYFILE_JSON)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright 2014 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "google/cloud/errors"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module Storage
|
21
|
+
##
|
22
|
+
# # FileVerificationError
|
23
|
+
#
|
24
|
+
# Raised when a File download fails the verification.
|
25
|
+
class FileVerificationError < Google::Cloud::Error
|
26
|
+
##
|
27
|
+
# The type of digest that failed verification,
|
28
|
+
# :md5 or :crc32c.
|
29
|
+
attr_accessor :type
|
30
|
+
|
31
|
+
##
|
32
|
+
# The value of the digest on the google-cloud file.
|
33
|
+
attr_accessor :gcloud_digest
|
34
|
+
|
35
|
+
##
|
36
|
+
# The value of the digest on the downloaded file.
|
37
|
+
attr_accessor :local_digest
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def self.for_md5 gcloud_digest, local_digest
|
41
|
+
new("The downloaded file failed MD5 verification.").tap do |e|
|
42
|
+
e.type = :md5
|
43
|
+
e.gcloud_digest = gcloud_digest
|
44
|
+
e.local_digest = local_digest
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def self.for_crc32c gcloud_digest, local_digest
|
50
|
+
new("The downloaded file failed CRC32c verification.").tap do |e|
|
51
|
+
e.type = :crc32c
|
52
|
+
e.gcloud_digest = gcloud_digest
|
53
|
+
e.local_digest = local_digest
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# # SignedUrlUnavailable Error
|
60
|
+
#
|
61
|
+
# This is raised when File#signed_url is unable to generate a URL due to
|
62
|
+
# missing credentials needed to create the URL.
|
63
|
+
class SignedUrlUnavailable < Google::Cloud::Error
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,849 @@
|
|
1
|
+
# Copyright 2014 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "google/cloud/storage/file/acl"
|
17
|
+
require "google/cloud/storage/file/list"
|
18
|
+
require "google/cloud/storage/file/verifier"
|
19
|
+
|
20
|
+
module Google
|
21
|
+
module Cloud
|
22
|
+
module Storage
|
23
|
+
##
|
24
|
+
# # File
|
25
|
+
#
|
26
|
+
# Represents a File
|
27
|
+
# ([Object](https://cloud.google.com/storage/docs/json_api/v1/objects))
|
28
|
+
# that belongs to a {Bucket}. Files (Objects) are the individual pieces of
|
29
|
+
# data that you store in Google Cloud Storage. A file can be up to 5 TB in
|
30
|
+
# size. Files have two components: data and metadata. The data component
|
31
|
+
# is the data from an external file or other data source that you want to
|
32
|
+
# store in Google Cloud Storage. The metadata component is a collection of
|
33
|
+
# name-value pairs that describe various qualities of the data.
|
34
|
+
#
|
35
|
+
# @see https://cloud.google.com/storage/docs/concepts-techniques Concepts
|
36
|
+
# and Techniques
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# require "google/cloud"
|
40
|
+
#
|
41
|
+
# gcloud = Google::Cloud.new
|
42
|
+
# storage = gcloud.storage
|
43
|
+
#
|
44
|
+
# bucket = storage.bucket "my-bucket"
|
45
|
+
#
|
46
|
+
# file = bucket.file "path/to/my-file.ext"
|
47
|
+
# file.download "path/to/downloaded/file.ext"
|
48
|
+
#
|
49
|
+
class File
|
50
|
+
##
|
51
|
+
# @private The Connection object.
|
52
|
+
attr_accessor :service
|
53
|
+
|
54
|
+
##
|
55
|
+
# @private The Google API Client object.
|
56
|
+
attr_accessor :gapi
|
57
|
+
|
58
|
+
##
|
59
|
+
# @private Create an empty File object.
|
60
|
+
def initialize
|
61
|
+
@service = nil
|
62
|
+
@gapi = Google::Apis::StorageV1::Object.new
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# The kind of item this is.
|
67
|
+
# For files, this is always storage#object.
|
68
|
+
def kind
|
69
|
+
@gapi.kind
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# The ID of the file.
|
74
|
+
def id
|
75
|
+
@gapi.id
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# The name of this file.
|
80
|
+
def name
|
81
|
+
@gapi.name
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# The name of the {Bucket} containing this file.
|
86
|
+
def bucket
|
87
|
+
@gapi.bucket
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# The content generation of this file.
|
92
|
+
# Used for object versioning.
|
93
|
+
def generation
|
94
|
+
@gapi.generation
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# The version of the metadata for this file at this generation.
|
99
|
+
# Used for preconditions and for detecting changes in metadata.
|
100
|
+
# A metageneration number is only meaningful in the context of a
|
101
|
+
# particular generation of a particular file.
|
102
|
+
def metageneration
|
103
|
+
@gapi.metageneration
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# A URL that can be used to access the file using the REST API.
|
108
|
+
def api_url
|
109
|
+
@gapi.self_link
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# A URL that can be used to download the file using the REST API.
|
114
|
+
def media_url
|
115
|
+
@gapi.media_link
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Content-Length of the data in bytes.
|
120
|
+
def size
|
121
|
+
@gapi.size.to_i if @gapi.size
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Creation time of the file.
|
126
|
+
def created_at
|
127
|
+
@gapi.time_created
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# The creation or modification time of the file.
|
132
|
+
# For buckets with versioning enabled, changing an object's
|
133
|
+
# metadata does not change this property.
|
134
|
+
def updated_at
|
135
|
+
@gapi.updated
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# MD5 hash of the data; encoded using base64.
|
140
|
+
def md5
|
141
|
+
@gapi.md5_hash
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# The CRC32c checksum of the data, as described in
|
146
|
+
# [RFC 4960, Appendix B](http://tools.ietf.org/html/rfc4960#appendix-B).
|
147
|
+
# Encoded using base64 in big-endian byte order.
|
148
|
+
def crc32c
|
149
|
+
@gapi.crc32c
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# HTTP 1.1 Entity tag for the file.
|
154
|
+
def etag
|
155
|
+
@gapi.etag
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# The [Cache-Control](https://tools.ietf.org/html/rfc7234#section-5.2)
|
160
|
+
# directive for the file data.
|
161
|
+
def cache_control
|
162
|
+
@gapi.cache_control
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# Updates the
|
167
|
+
# [Cache-Control](https://tools.ietf.org/html/rfc7234#section-5.2)
|
168
|
+
# directive for the file data.
|
169
|
+
def cache_control= cache_control
|
170
|
+
@gapi.cache_control = cache_control
|
171
|
+
patch_gapi! :cache_control
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# The [Content-Disposition](https://tools.ietf.org/html/rfc6266) of the
|
176
|
+
# file data.
|
177
|
+
def content_disposition
|
178
|
+
@gapi.content_disposition
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Updates the [Content-Disposition](https://tools.ietf.org/html/rfc6266)
|
183
|
+
# of the file data.
|
184
|
+
def content_disposition= content_disposition
|
185
|
+
@gapi.content_disposition = content_disposition
|
186
|
+
patch_gapi! :content_disposition
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# The [Content-Encoding
|
191
|
+
# ](https://tools.ietf.org/html/rfc7231#section-3.1.2.2) of the file
|
192
|
+
# data.
|
193
|
+
def content_encoding
|
194
|
+
@gapi.content_encoding
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Updates the [Content-Encoding
|
199
|
+
# ](https://tools.ietf.org/html/rfc7231#section-3.1.2.2) of the file
|
200
|
+
# data.
|
201
|
+
def content_encoding= content_encoding
|
202
|
+
@gapi.content_encoding = content_encoding
|
203
|
+
patch_gapi! :content_encoding
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# The [Content-Language](http://tools.ietf.org/html/bcp47) of the file
|
208
|
+
# data.
|
209
|
+
def content_language
|
210
|
+
@gapi.content_language
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Updates the [Content-Language](http://tools.ietf.org/html/bcp47) of
|
215
|
+
# the file data.
|
216
|
+
def content_language= content_language
|
217
|
+
@gapi.content_language = content_language
|
218
|
+
patch_gapi! :content_language
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# The [Content-Type](https://tools.ietf.org/html/rfc2616#section-14.17)
|
223
|
+
# of the file data.
|
224
|
+
def content_type
|
225
|
+
@gapi.content_type
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Updates the
|
230
|
+
# [Content-Type](https://tools.ietf.org/html/rfc2616#section-14.17) of
|
231
|
+
# the file data.
|
232
|
+
def content_type= content_type
|
233
|
+
@gapi.content_type = content_type
|
234
|
+
patch_gapi! :content_type
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# A hash of custom, user-provided web-safe keys and arbitrary string
|
239
|
+
# values that will returned with requests for the file as "x-goog-meta-"
|
240
|
+
# response headers.
|
241
|
+
def metadata
|
242
|
+
m = @gapi.metadata
|
243
|
+
m = m.to_h if m.respond_to? :to_h
|
244
|
+
m.dup.freeze
|
245
|
+
end
|
246
|
+
|
247
|
+
##
|
248
|
+
# Updates the hash of custom, user-provided web-safe keys and arbitrary
|
249
|
+
# string values that will returned with requests for the file as
|
250
|
+
# "x-goog-meta-" response headers.
|
251
|
+
def metadata= metadata
|
252
|
+
@gapi.metadata = metadata
|
253
|
+
patch_gapi! :metadata
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# An [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4)
|
258
|
+
# Base64-encoded string of the SHA256 hash of the [customer-supplied
|
259
|
+
# encryption
|
260
|
+
# key](https://cloud.google.com/storage/docs/encryption#customer-supplied).
|
261
|
+
# You can use this SHA256 hash to uniquely identify the AES-256
|
262
|
+
# encryption key required to decrypt this file.
|
263
|
+
def encryption_key_sha256
|
264
|
+
return nil unless @gapi.customer_encryption
|
265
|
+
Base64.decode64 @gapi.customer_encryption.key_sha256
|
266
|
+
end
|
267
|
+
|
268
|
+
##
|
269
|
+
# Updates the file with changes made in the given block in a single
|
270
|
+
# PATCH request. The following attributes may be set: {#cache_control=},
|
271
|
+
# {#content_disposition=}, {#content_encoding=}, {#content_language=},
|
272
|
+
# {#content_type=}, and {#metadata=}. The {#metadata} hash accessible in
|
273
|
+
# the block is completely mutable and will be included in the request.
|
274
|
+
#
|
275
|
+
# @yield [file] a block yielding a delegate object for updating the file
|
276
|
+
#
|
277
|
+
# @example
|
278
|
+
# require "google/cloud"
|
279
|
+
#
|
280
|
+
# gcloud = Google::Cloud.new
|
281
|
+
# storage = gcloud.storage
|
282
|
+
#
|
283
|
+
# bucket = storage.bucket "my-bucket"
|
284
|
+
#
|
285
|
+
# file = bucket.file "path/to/my-file.ext"
|
286
|
+
#
|
287
|
+
# file.update do |f|
|
288
|
+
# f.cache_control = "private, max-age=0, no-cache"
|
289
|
+
# f.content_disposition = "inline; filename=filename.ext"
|
290
|
+
# f.content_encoding = "deflate"
|
291
|
+
# f.content_language = "de"
|
292
|
+
# f.content_type = "application/json"
|
293
|
+
# f.metadata["player"] = "Bob"
|
294
|
+
# f.metadata["score"] = "10"
|
295
|
+
# end
|
296
|
+
#
|
297
|
+
def update
|
298
|
+
updater = Updater.new gapi
|
299
|
+
yield updater
|
300
|
+
updater.check_for_changed_metadata!
|
301
|
+
patch_gapi! updater.updates unless updater.updates.empty?
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Download the file's contents to a local file.
|
306
|
+
#
|
307
|
+
# By default, the download is verified by calculating the MD5 digest.
|
308
|
+
#
|
309
|
+
# If a [customer-supplied encryption
|
310
|
+
# key](https://cloud.google.com/storage/docs/encryption#customer-supplied)
|
311
|
+
# was used with {Bucket#create_file}, the `encryption_key` and
|
312
|
+
# `encryption_key_sha256` options must be provided.
|
313
|
+
#
|
314
|
+
# @param [String] path The path on the local file system to write the
|
315
|
+
# data to. The path provided must be writable.
|
316
|
+
# @param [Symbol] verify The verification algoruthm used to ensure the
|
317
|
+
# downloaded file contents are correct. Default is `:md5`.
|
318
|
+
#
|
319
|
+
# Acceptable values are:
|
320
|
+
#
|
321
|
+
# * `md5` - Verify file content match using the MD5 hash.
|
322
|
+
# * `crc32c` - Verify file content match using the CRC32c hash.
|
323
|
+
# * `all` - Perform all available file content verification.
|
324
|
+
# * `none` - Don't perform file content verification.
|
325
|
+
#
|
326
|
+
# @param [String] encryption_key Optional. The customer-supplied,
|
327
|
+
# AES-256 encryption key used to encrypt the file, if one was provided
|
328
|
+
# to {Bucket#create_file}. Must be provided if `encryption_key_sha256`
|
329
|
+
# is provided.
|
330
|
+
# @param [String] encryption_key_sha256 Optional. The SHA256 hash of the
|
331
|
+
# customer-supplied, AES-256 encryption key used to encrypt the file,
|
332
|
+
# if one was provided to {Bucket#create_file}. Must be provided if
|
333
|
+
# `encryption_key` is provided.
|
334
|
+
#
|
335
|
+
# @return [File] Returns a `::File` object on the local file system
|
336
|
+
#
|
337
|
+
# @example
|
338
|
+
# require "google/cloud"
|
339
|
+
#
|
340
|
+
# gcloud = Google::Cloud.new
|
341
|
+
# storage = gcloud.storage
|
342
|
+
#
|
343
|
+
# bucket = storage.bucket "my-bucket"
|
344
|
+
#
|
345
|
+
# file = bucket.file "path/to/my-file.ext"
|
346
|
+
# file.download "path/to/downloaded/file.ext"
|
347
|
+
#
|
348
|
+
# @example Use the CRC32c digest by passing :crc32c.
|
349
|
+
# require "google/cloud"
|
350
|
+
#
|
351
|
+
# gcloud = Google::Cloud.new
|
352
|
+
# storage = gcloud.storage
|
353
|
+
#
|
354
|
+
# bucket = storage.bucket "my-bucket"
|
355
|
+
#
|
356
|
+
# file = bucket.file "path/to/my-file.ext"
|
357
|
+
# file.download "path/to/downloaded/file.ext", verify: :crc32c
|
358
|
+
#
|
359
|
+
# @example Use the MD5 and CRC32c digests by passing :all.
|
360
|
+
# require "google/cloud"
|
361
|
+
#
|
362
|
+
# gcloud = Google::Cloud.new
|
363
|
+
# storage = gcloud.storage
|
364
|
+
#
|
365
|
+
# bucket = storage.bucket "my-bucket"
|
366
|
+
#
|
367
|
+
# file = bucket.file "path/to/my-file.ext"
|
368
|
+
# file.download "path/to/downloaded/file.ext", verify: :all
|
369
|
+
#
|
370
|
+
# @example Disable the download verification by passing :none.
|
371
|
+
# require "google/cloud"
|
372
|
+
#
|
373
|
+
# gcloud = Google::Cloud.new
|
374
|
+
# storage = gcloud.storage
|
375
|
+
#
|
376
|
+
# bucket = storage.bucket "my-bucket"
|
377
|
+
#
|
378
|
+
# file = bucket.file "path/to/my-file.ext"
|
379
|
+
# file.download "path/to/downloaded/file.ext", verify: :none
|
380
|
+
#
|
381
|
+
def download path, verify: :md5, encryption_key: nil,
|
382
|
+
encryption_key_sha256: nil
|
383
|
+
ensure_service!
|
384
|
+
service.download_file \
|
385
|
+
bucket, name, path,
|
386
|
+
key: encryption_key, key_sha256: encryption_key_sha256
|
387
|
+
verify_file! ::File.new(path), verify
|
388
|
+
end
|
389
|
+
|
390
|
+
##
|
391
|
+
# Copy the file to a new location.
|
392
|
+
#
|
393
|
+
# If a [customer-supplied encryption
|
394
|
+
# key](https://cloud.google.com/storage/docs/encryption#customer-supplied)
|
395
|
+
# was used with {Bucket#create_file}, the `encryption_key` and
|
396
|
+
# `encryption_key_sha256` options must be provided.
|
397
|
+
#
|
398
|
+
# @param [String] dest_bucket_or_path Either the bucket to copy the file
|
399
|
+
# to, or the path to copy the file to in the current bucket.
|
400
|
+
# @param [String] dest_path If a bucket was provided in the first
|
401
|
+
# parameter, this contains the path to copy the file to in the given
|
402
|
+
# bucket.
|
403
|
+
# @param [String] acl A predefined set of access controls to apply to
|
404
|
+
# new file.
|
405
|
+
#
|
406
|
+
# Acceptable values are:
|
407
|
+
#
|
408
|
+
# * `auth`, `auth_read`, `authenticated`, `authenticated_read`,
|
409
|
+
# `authenticatedRead` - File owner gets OWNER access, and
|
410
|
+
# allAuthenticatedUsers get READER access.
|
411
|
+
# * `owner_full`, `bucketOwnerFullControl` - File owner gets OWNER
|
412
|
+
# access, and project team owners get OWNER access.
|
413
|
+
# * `owner_read`, `bucketOwnerRead` - File owner gets OWNER access,
|
414
|
+
# and project team owners get READER access.
|
415
|
+
# * `private` - File owner gets OWNER access.
|
416
|
+
# * `project_private`, `projectPrivate` - File owner gets OWNER
|
417
|
+
# access, and project team members get access according to their
|
418
|
+
# roles.
|
419
|
+
# * `public`, `public_read`, `publicRead` - File owner gets OWNER
|
420
|
+
# access, and allUsers get READER access.
|
421
|
+
# @param [Integer] generation Select a specific revision of the file to
|
422
|
+
# copy. The default is the latest version.
|
423
|
+
# @param [String] encryption_key Optional. The customer-supplied,
|
424
|
+
# AES-256 encryption key used to encrypt the file, if one was provided
|
425
|
+
# to {Bucket#create_file}. Must be provided if `encryption_key_sha256`
|
426
|
+
# is provided.
|
427
|
+
# @param [String] encryption_key_sha256 Optional. The SHA256 hash of the
|
428
|
+
# customer-supplied, AES-256 encryption key used to encrypt the file,
|
429
|
+
# if one was provided to {Bucket#create_file}. Must be provided if
|
430
|
+
# `encryption_key` is provided.
|
431
|
+
#
|
432
|
+
# @return [Google::Cloud::Storage::File]
|
433
|
+
#
|
434
|
+
# @example The file can be copied to a new path in the current bucket:
|
435
|
+
# require "google/cloud"
|
436
|
+
#
|
437
|
+
# gcloud = Google::Cloud.new
|
438
|
+
# storage = gcloud.storage
|
439
|
+
#
|
440
|
+
# bucket = storage.bucket "my-bucket"
|
441
|
+
#
|
442
|
+
# file = bucket.file "path/to/my-file.ext"
|
443
|
+
# file.copy "path/to/destination/file.ext"
|
444
|
+
#
|
445
|
+
# @example The file can also be copied to a different bucket:
|
446
|
+
# require "google/cloud"
|
447
|
+
#
|
448
|
+
# gcloud = Google::Cloud.new
|
449
|
+
# storage = gcloud.storage
|
450
|
+
#
|
451
|
+
# bucket = storage.bucket "my-bucket"
|
452
|
+
#
|
453
|
+
# file = bucket.file "path/to/my-file.ext"
|
454
|
+
# file.copy "new-destination-bucket",
|
455
|
+
# "path/to/destination/file.ext"
|
456
|
+
#
|
457
|
+
# @example The file can also be copied by specifying a generation:
|
458
|
+
# file.copy "copy/of/previous/generation/file.ext",
|
459
|
+
# generation: 123456
|
460
|
+
#
|
461
|
+
def copy dest_bucket_or_path, dest_path = nil, acl: nil,
|
462
|
+
generation: nil, encryption_key: nil,
|
463
|
+
encryption_key_sha256: nil
|
464
|
+
ensure_service!
|
465
|
+
options = { acl: acl, generation: generation,
|
466
|
+
key: encryption_key, key_sha256: encryption_key_sha256 }
|
467
|
+
dest_bucket, dest_path, options = fix_copy_args dest_bucket_or_path,
|
468
|
+
dest_path, options
|
469
|
+
|
470
|
+
gapi = service.copy_file bucket, name,
|
471
|
+
dest_bucket, dest_path, options
|
472
|
+
File.from_gapi gapi, service
|
473
|
+
end
|
474
|
+
|
475
|
+
##
|
476
|
+
# Permanently deletes the file.
|
477
|
+
#
|
478
|
+
# @return [Boolean] Returns `true` if the file was deleted.
|
479
|
+
#
|
480
|
+
# @example
|
481
|
+
# require "google/cloud"
|
482
|
+
#
|
483
|
+
# gcloud = Google::Cloud.new
|
484
|
+
# storage = gcloud.storage
|
485
|
+
#
|
486
|
+
# bucket = storage.bucket "my-bucket"
|
487
|
+
#
|
488
|
+
# file = bucket.file "path/to/my-file.ext"
|
489
|
+
# file.delete
|
490
|
+
#
|
491
|
+
def delete
|
492
|
+
ensure_service!
|
493
|
+
service.delete_file bucket, name
|
494
|
+
true
|
495
|
+
end
|
496
|
+
|
497
|
+
##
|
498
|
+
# Public URL to access the file. If the file is not public, requests to
|
499
|
+
# the URL will return an error. (See {File::Acl#public!} and
|
500
|
+
# {Bucket::DefaultAcl#public!}) To share a file that is not public see
|
501
|
+
# {#signed_url}.
|
502
|
+
#
|
503
|
+
# @see https://cloud.google.com/storage/docs/access-public-data
|
504
|
+
# Accessing Public Data
|
505
|
+
#
|
506
|
+
# @param [String] protocol The protocol to use for the URL. Default is
|
507
|
+
# `HTTPS`.
|
508
|
+
#
|
509
|
+
# @example
|
510
|
+
# require "google/cloud"
|
511
|
+
#
|
512
|
+
# gcloud = Google::Cloud.new
|
513
|
+
# storage = gcloud.storage
|
514
|
+
#
|
515
|
+
# bucket = storage.bucket "my-todo-app"
|
516
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
517
|
+
# public_url = file.public_url
|
518
|
+
#
|
519
|
+
# @example Generate the URL with a protocol other than HTTPS:
|
520
|
+
# require "google/cloud"
|
521
|
+
#
|
522
|
+
# gcloud = Google::Cloud.new
|
523
|
+
# storage = gcloud.storage
|
524
|
+
#
|
525
|
+
# bucket = storage.bucket "my-todo-app"
|
526
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
527
|
+
# public_url = file.public_url protocol: "http"
|
528
|
+
#
|
529
|
+
def public_url protocol: :https
|
530
|
+
"#{protocol}://storage.googleapis.com/#{bucket}/#{name}"
|
531
|
+
end
|
532
|
+
alias_method :url, :public_url
|
533
|
+
|
534
|
+
##
|
535
|
+
# Access without authentication can be granted to a File for a specified
|
536
|
+
# period of time. This URL uses a cryptographic signature of your
|
537
|
+
# credentials to access the file.
|
538
|
+
#
|
539
|
+
# Generating a URL requires service account credentials, either by
|
540
|
+
# connecting with a service account when calling
|
541
|
+
# {Google::Cloud.storage}, or by passing in the service account `issuer`
|
542
|
+
# and `signing_key` values. Although the private key can be passed as a
|
543
|
+
# string for convenience, creating and storing an instance of
|
544
|
+
# `OpenSSL::PKey::RSA` is more efficient when making multiple calls to
|
545
|
+
# `signed_url`.
|
546
|
+
#
|
547
|
+
# A {SignedUrlUnavailable} is raised if the service account credentials
|
548
|
+
# are missing. Service account credentials are acquired by following the
|
549
|
+
# steps in [Service Account Authentication](
|
550
|
+
# https://cloud.google.com/storage/docs/authentication#service_accounts).
|
551
|
+
#
|
552
|
+
# @see https://cloud.google.com/storage/docs/access-control#Signed-URLs
|
553
|
+
# Access Control Signed URLs guide
|
554
|
+
#
|
555
|
+
# @param [String] method The HTTP verb to be used with the signed URL.
|
556
|
+
# Signed URLs can be used
|
557
|
+
# with `GET`, `HEAD`, `PUT`, and `DELETE` requests. Default is `GET`.
|
558
|
+
# @param [Integer] expires The number of seconds until the URL expires.
|
559
|
+
# Default is 300/5 minutes.
|
560
|
+
# @param [String] content_type When provided, the client (browser) must
|
561
|
+
# send this value in the HTTP header. e.g. `text/plain`
|
562
|
+
# @param [String] content_md5 The MD5 digest value in base64. If you
|
563
|
+
# provide this in the string, the client (usually a browser) must
|
564
|
+
# provide this HTTP header with this same value in its request.
|
565
|
+
# @param [String] issuer Service Account's Client Email.
|
566
|
+
# @param [String] client_email Service Account's Client Email.
|
567
|
+
# @param [OpenSSL::PKey::RSA, String] signing_key Service Account's
|
568
|
+
# Private Key.
|
569
|
+
# @param [OpenSSL::PKey::RSA, String] private_key Service Account's
|
570
|
+
# Private Key.
|
571
|
+
#
|
572
|
+
# @example
|
573
|
+
# require "google/cloud"
|
574
|
+
#
|
575
|
+
# gcloud = Google::Cloud.new
|
576
|
+
# storage = gcloud.storage
|
577
|
+
#
|
578
|
+
# bucket = storage.bucket "my-todo-app"
|
579
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
580
|
+
# shared_url = file.signed_url
|
581
|
+
#
|
582
|
+
# @example Any of the option parameters may be specified:
|
583
|
+
# require "google/cloud"
|
584
|
+
#
|
585
|
+
# gcloud = Google::Cloud.new
|
586
|
+
# storage = gcloud.storage
|
587
|
+
#
|
588
|
+
# bucket = storage.bucket "my-todo-app"
|
589
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
590
|
+
# shared_url = file.signed_url method: "GET",
|
591
|
+
# expires: 300 # 5 minutes from now
|
592
|
+
#
|
593
|
+
# @example Using the `issuer` and `signing_key` options:
|
594
|
+
# require "google/cloud/storage"
|
595
|
+
#
|
596
|
+
# storage = Google::Cloud.storage
|
597
|
+
#
|
598
|
+
# bucket = storage.bucket "my-todo-app"
|
599
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
600
|
+
# key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."
|
601
|
+
# shared_url = file.signed_url issuer: "service-account@gcloud.com",
|
602
|
+
# signing_key: key
|
603
|
+
#
|
604
|
+
def signed_url method: nil, expires: nil, content_type: nil,
|
605
|
+
content_md5: nil, issuer: nil, client_email: nil,
|
606
|
+
signing_key: nil, private_key: nil
|
607
|
+
ensure_service!
|
608
|
+
options = { method: method, expires: expires,
|
609
|
+
content_type: content_type, content_md5: content_md5,
|
610
|
+
issuer: issuer, client_email: client_email,
|
611
|
+
signing_key: signing_key, private_key: private_key }
|
612
|
+
signer = File::Signer.new self
|
613
|
+
signer.signed_url options
|
614
|
+
end
|
615
|
+
|
616
|
+
##
|
617
|
+
# The {File::Acl} instance used to control access to the file.
|
618
|
+
#
|
619
|
+
# A file has owners, writers, and readers. Permissions can be granted to
|
620
|
+
# an individual user's email address, a group's email address, as well
|
621
|
+
# as many predefined lists.
|
622
|
+
#
|
623
|
+
# @see https://cloud.google.com/storage/docs/access-control Access
|
624
|
+
# Control guide
|
625
|
+
#
|
626
|
+
# @example Grant access to a user by prepending `"user-"` to an email:
|
627
|
+
# require "google/cloud"
|
628
|
+
#
|
629
|
+
# gcloud = Google::Cloud.new
|
630
|
+
# storage = gcloud.storage
|
631
|
+
#
|
632
|
+
# bucket = storage.bucket "my-todo-app"
|
633
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
634
|
+
#
|
635
|
+
# email = "heidi@example.net"
|
636
|
+
# file.acl.add_reader "user-#{email}"
|
637
|
+
#
|
638
|
+
# @example Grant access to a group by prepending `"group-"` to an email:
|
639
|
+
# require "google/cloud"
|
640
|
+
#
|
641
|
+
# gcloud = Google::Cloud.new
|
642
|
+
# storage = gcloud.storage
|
643
|
+
#
|
644
|
+
# bucket = storage.bucket "my-todo-app"
|
645
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
646
|
+
#
|
647
|
+
# email = "authors@example.net"
|
648
|
+
# file.acl.add_reader "group-#{email}"
|
649
|
+
#
|
650
|
+
# @example Or, grant access via a predefined permissions list:
|
651
|
+
# require "google/cloud"
|
652
|
+
#
|
653
|
+
# gcloud = Google::Cloud.new
|
654
|
+
# storage = gcloud.storage
|
655
|
+
#
|
656
|
+
# bucket = storage.bucket "my-todo-app"
|
657
|
+
# file = bucket.file "avatars/heidi/400x400.png"
|
658
|
+
#
|
659
|
+
# file.acl.public!
|
660
|
+
#
|
661
|
+
def acl
|
662
|
+
@acl ||= File::Acl.new self
|
663
|
+
end
|
664
|
+
|
665
|
+
##
|
666
|
+
# Reloads the file with current data from the Storage service.
|
667
|
+
def reload!
|
668
|
+
ensure_service!
|
669
|
+
@gapi = service.get_file bucket, name
|
670
|
+
end
|
671
|
+
alias_method :refresh!, :reload!
|
672
|
+
|
673
|
+
##
|
674
|
+
# @private URI of the location and file name in the format of
|
675
|
+
# <code>gs://my-bucket/file-name.json</code>.
|
676
|
+
def to_gs_url
|
677
|
+
"gs://#{bucket}/#{name}"
|
678
|
+
end
|
679
|
+
|
680
|
+
##
|
681
|
+
# @private New File from a Google API Client object.
|
682
|
+
def self.from_gapi gapi, service
|
683
|
+
new.tap do |f|
|
684
|
+
f.gapi = gapi
|
685
|
+
f.service = service
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
protected
|
690
|
+
|
691
|
+
##
|
692
|
+
# Raise an error unless an active service is available.
|
693
|
+
def ensure_service!
|
694
|
+
fail "Must have active connection" unless service
|
695
|
+
end
|
696
|
+
|
697
|
+
def patch_gapi! *attributes
|
698
|
+
attributes.flatten!
|
699
|
+
return if attributes.empty?
|
700
|
+
ensure_service!
|
701
|
+
patch_args = Hash[attributes.map do |attr|
|
702
|
+
[attr, @gapi.send(attr)]
|
703
|
+
end]
|
704
|
+
patch_gapi = Google::Apis::StorageV1::Object.new patch_args
|
705
|
+
@gapi = service.patch_file bucket, name, patch_gapi
|
706
|
+
end
|
707
|
+
|
708
|
+
def fix_copy_args dest_bucket, dest_path, options = {}
|
709
|
+
if dest_path.respond_to?(:to_hash) && options.empty?
|
710
|
+
options = dest_path
|
711
|
+
dest_path = nil
|
712
|
+
end
|
713
|
+
if dest_path.nil?
|
714
|
+
dest_path = dest_bucket
|
715
|
+
dest_bucket = bucket
|
716
|
+
end
|
717
|
+
dest_bucket = dest_bucket.name if dest_bucket.respond_to? :name
|
718
|
+
options[:acl] = File::Acl.predefined_rule_for options[:acl]
|
719
|
+
[dest_bucket, dest_path, options]
|
720
|
+
end
|
721
|
+
|
722
|
+
def verify_file! file, verify = :md5
|
723
|
+
verify_md5 = verify == :md5 || verify == :all
|
724
|
+
verify_crc32c = verify == :crc32c || verify == :all
|
725
|
+
Verifier.verify_md5! self, file if verify_md5
|
726
|
+
Verifier.verify_crc32c! self, file if verify_crc32c
|
727
|
+
file
|
728
|
+
end
|
729
|
+
|
730
|
+
##
|
731
|
+
# @private Create a signed_url for a file.
|
732
|
+
class Signer
|
733
|
+
def initialize file
|
734
|
+
@file = file
|
735
|
+
end
|
736
|
+
|
737
|
+
##
|
738
|
+
# The external path to the file.
|
739
|
+
def ext_path
|
740
|
+
"/#{@file.bucket}/#{@file.name}"
|
741
|
+
end
|
742
|
+
|
743
|
+
##
|
744
|
+
# The external url to the file.
|
745
|
+
def ext_url
|
746
|
+
"https://storage.googleapis.com#{ext_path}"
|
747
|
+
end
|
748
|
+
|
749
|
+
def apply_option_defaults options
|
750
|
+
adjusted_expires = (Time.now.utc + (options[:expires] || 300)).to_i
|
751
|
+
options[:expires] = adjusted_expires
|
752
|
+
options[:method] ||= "GET"
|
753
|
+
options
|
754
|
+
end
|
755
|
+
|
756
|
+
def signature_str options
|
757
|
+
[options[:method], options[:content_md5],
|
758
|
+
options[:content_type], options[:expires],
|
759
|
+
ext_path].join "\n"
|
760
|
+
end
|
761
|
+
|
762
|
+
def determine_signing_key options = {}
|
763
|
+
options[:signing_key] || options[:private_key] ||
|
764
|
+
@file.service.credentials.signing_key
|
765
|
+
end
|
766
|
+
|
767
|
+
def determine_issuer options = {}
|
768
|
+
options[:issuer] || options[:client_email] ||
|
769
|
+
@file.service.credentials.issuer
|
770
|
+
end
|
771
|
+
|
772
|
+
def signed_url options
|
773
|
+
options = apply_option_defaults options
|
774
|
+
|
775
|
+
i = determine_issuer options
|
776
|
+
s = determine_signing_key options
|
777
|
+
|
778
|
+
fail SignedUrlUnavailable unless i && s
|
779
|
+
|
780
|
+
sig = generate_signature s, options
|
781
|
+
generate_signed_url i, sig, options[:expires]
|
782
|
+
end
|
783
|
+
|
784
|
+
def generate_signature signing_key, options = {}
|
785
|
+
unless signing_key.respond_to? :sign
|
786
|
+
signing_key = OpenSSL::PKey::RSA.new signing_key
|
787
|
+
end
|
788
|
+
signing_key.sign OpenSSL::Digest::SHA256.new, signature_str(options)
|
789
|
+
end
|
790
|
+
|
791
|
+
def generate_signed_url issuer, signed_string, expires
|
792
|
+
signature = Base64.strict_encode64(signed_string).delete("\n")
|
793
|
+
"#{ext_url}?GoogleAccessId=#{CGI.escape issuer}" \
|
794
|
+
"&Expires=#{expires}" \
|
795
|
+
"&Signature=#{CGI.escape signature}"
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
##
|
800
|
+
# Yielded to a block to accumulate changes for a patch request.
|
801
|
+
class Updater < File
|
802
|
+
attr_reader :updates
|
803
|
+
##
|
804
|
+
# Create an Updater object.
|
805
|
+
def initialize gapi
|
806
|
+
@updates = []
|
807
|
+
@gapi = gapi
|
808
|
+
end
|
809
|
+
|
810
|
+
##
|
811
|
+
# A hash of custom, user-provided web-safe keys and arbitrary string
|
812
|
+
# values that will returned with requests for the file as
|
813
|
+
# "x-goog-meta-" response headers.
|
814
|
+
def metadata
|
815
|
+
# do not freeze metadata
|
816
|
+
@metadata ||= @gapi.metadata.to_h.dup
|
817
|
+
end
|
818
|
+
|
819
|
+
##
|
820
|
+
# Updates the hash of custom, user-provided web-safe keys and
|
821
|
+
# arbitrary string values that will returned with requests for the
|
822
|
+
# file as "x-goog-meta-" response headers.
|
823
|
+
def metadata= metadata
|
824
|
+
@metadata = metadata
|
825
|
+
@gapi.metadata = @metadata
|
826
|
+
patch_gapi! :metadata
|
827
|
+
end
|
828
|
+
|
829
|
+
##
|
830
|
+
# @private Make sure any metadata changes are saved
|
831
|
+
def check_for_changed_metadata!
|
832
|
+
return if @metadata == @gapi.metadata
|
833
|
+
@gapi.metadata = @metadata
|
834
|
+
patch_gapi! :metadata
|
835
|
+
end
|
836
|
+
|
837
|
+
protected
|
838
|
+
|
839
|
+
##
|
840
|
+
# Queue up all the updates instead of making them.
|
841
|
+
def patch_gapi! attribute
|
842
|
+
@updates << attribute
|
843
|
+
@updates.uniq!
|
844
|
+
end
|
845
|
+
end
|
846
|
+
end
|
847
|
+
end
|
848
|
+
end
|
849
|
+
end
|