swift_client 0.1.3 → 0.2.0
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 +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +3 -4
- data/README.md +81 -21
- data/lib/swift_client.rb +105 -52
- data/lib/swift_client/null_cache.rb +11 -0
- data/lib/swift_client/version.rb +1 -2
- data/test/swift_client_test.rb +79 -1
- data/test/test_helper.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e91d8e86490003310183e57091645c64e9e4b4a4462b99c82d9a804887a8e2a1
|
4
|
+
data.tar.gz: e826b43a18119d2c9057bc1ed9bcbaf774fc505d7c5313b9ec0fecdbfa1a3d55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 429ea5bbf5892ac978d33f633bd7aa5fcb2352d18c6f1d76e69af21a36ad97dc2fb7f08fc08427d39c0e08fb59a80f1591481d52723e3efcf6755ccda7ed02d4
|
7
|
+
data.tar.gz: 333a4e6328fd0b0185aca83fddfd5b07628917b2139148bdbca30ffb425e8eb8fb7600d181edc3d40ec5c0f6428dbdba48fcb6d9045ccc57175d14f64f16c862
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -101,7 +101,8 @@ swift_client = SwiftClient.new(
|
|
101
101
|
:auth_url => "https://auth.example.com/v3",
|
102
102
|
:storage_url => "https://storage.example.com/v1/AUTH_account",
|
103
103
|
:user_id => "user id",
|
104
|
-
:password => "password"
|
104
|
+
:password => "password",
|
105
|
+
:interface => "internal"
|
105
106
|
)
|
106
107
|
|
107
108
|
# OR
|
@@ -126,26 +127,80 @@ access the response body and JSON response. Checkout the
|
|
126
127
|
|
127
128
|
SwiftClient offers the following requests:
|
128
129
|
|
129
|
-
* head_account
|
130
|
-
* post_account(headers = {})
|
131
|
-
* head_containers
|
132
|
-
* get_containers(query = {})
|
133
|
-
* paginate_containers(query = {})
|
134
|
-
* get_container(container_name, query = {})
|
135
|
-
* paginate_container(container_name, query = {})
|
136
|
-
* head_container(container_name)
|
137
|
-
* put_container(container_name, headers = {})
|
138
|
-
* post_container(container_name, headers = {})
|
139
|
-
* delete_container(container_name)
|
140
|
-
* put_object(object_name, data_or_io, container_name, headers = {})
|
141
|
-
* post_object(object_name, container_name, headers = {})
|
142
|
-
* get_object(object_name, container_name) -> HTTParty::Response
|
143
|
-
*
|
144
|
-
*
|
145
|
-
*
|
146
|
-
*
|
147
|
-
*
|
148
|
-
*
|
130
|
+
* `head_account(options = {}) # => HTTParty::Response`
|
131
|
+
* `post_account(headers = {}, options = {}) # => HTTParty::Response`
|
132
|
+
* `head_containers(options = {}) # => HTTParty::Response`
|
133
|
+
* `get_containers(query = {}, options = {}) # => HTTParty::Response`
|
134
|
+
* `paginate_containers(query = {}, options = {}) # => Enumerator`
|
135
|
+
* `get_container(container_name, query = {}, options = {}) # => HTTParty::Response`
|
136
|
+
* `paginate_container(container_name, query = {}, options = {}) # => Enumerator`
|
137
|
+
* `head_container(container_name, options = {}) # => HTTParty::Response`
|
138
|
+
* `put_container(container_name, headers = {}, options = {}) # => HTTParty::Response`
|
139
|
+
* `post_container(container_name, headers = {}, options = {}) # => HTTParty::Response`
|
140
|
+
* `delete_container(container_name, options = {}) # => HTTParty::Response`
|
141
|
+
* `put_object(object_name, data_or_io, container_name, headers = {}, options = {}) # => HTTParty::Response`
|
142
|
+
* `post_object(object_name, container_name, headers = {}, options = {}) # => HTTParty::Response`
|
143
|
+
* `get_object(object_name, container_name, options = {}) -> HTTParty::Response`
|
144
|
+
* `get_object(object_name, container_name, options = {}) { |chunk| save chunk } # => HTTParty::Response`
|
145
|
+
* `head_object(object_name, container_name, options = {}) # => HTTParty::Response`
|
146
|
+
* `delete_object(object_name, container_name, options = {}) # => HTTParty::Response`
|
147
|
+
* `get_objects(container_name, query = {}, options = {}) # => HTTParty::Response`
|
148
|
+
* `paginate_objects(container_name, query = {}, options = {}) # => Enumerator`
|
149
|
+
* `public_url(object_name, container_name) # => HTTParty::Response`
|
150
|
+
* `temp_url(object_name, container_name, options = {}) # => HTTParty::Response`
|
151
|
+
* `bulk_delete(entries, options = {}) # => entries`
|
152
|
+
* `post_head(object_name, container_name, _headers = {}, options = {}) # => HTTParty::Response`
|
153
|
+
|
154
|
+
### Getting large objects
|
155
|
+
The `get_object` method with out a block is suitable for small objects that easily fit in memory. For larger objects, specify a block to process chunked data as it comes in.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
File.open("/tmp/output", "wb") do |file_io|
|
159
|
+
swift_client.get_object("/large/object", "container") do |chunk|
|
160
|
+
file_io.write(chunk)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
## Re-Using/Sharing/Caching Auth Tokens
|
166
|
+
|
167
|
+
Certain OpenStack/Swift providers have limits in place regarding token
|
168
|
+
generation. To re-use auth tokens by caching them via memcached, install dalli
|
169
|
+
|
170
|
+
`gem install dalli`
|
171
|
+
|
172
|
+
and provide an instance of Dalli::Client to SwiftClient:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
swift_client = SwiftClient.new(
|
176
|
+
:auth_url => "https://example.com/auth/v1.0",
|
177
|
+
...
|
178
|
+
:cache_store => Dalli::Client.new
|
179
|
+
)
|
180
|
+
```
|
181
|
+
|
182
|
+
The cache key used to store the auth token will include all neccessary details
|
183
|
+
to ensure the auth token won't be used for a different swift account erroneously.
|
184
|
+
|
185
|
+
The cache implementation of SwiftClient is not restricted to memcached. To use
|
186
|
+
a different one, simply implement a driver for your favorite cache store. See
|
187
|
+
[null_cache.rb](https://github.com/mrkamel/swift_client/blob/master/lib/swift_client/null_cache.rb)
|
188
|
+
for more info.
|
189
|
+
|
190
|
+
## bulk_delete
|
191
|
+
|
192
|
+
Takes an array containing container_name/object_name entries.
|
193
|
+
Automatically slices and sends 1_000 items per request.
|
194
|
+
|
195
|
+
## Non-chunked uploads
|
196
|
+
|
197
|
+
By default files are uploaded in chunks and using a `Transfer-Encoding:
|
198
|
+
chunked` header. You can override this by passing a `Transfer-Encoding:
|
199
|
+
identity` header:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
put_object(object_name, data_or_io, container_name, "Transfer-Encoding" => "identity")
|
203
|
+
```
|
149
204
|
|
150
205
|
## Contributing
|
151
206
|
|
@@ -154,3 +209,8 @@ SwiftClient offers the following requests:
|
|
154
209
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
155
210
|
4. Push to the branch (`git push origin my-new-feature`)
|
156
211
|
5. Create a new Pull Request
|
212
|
+
|
213
|
+
## Semantic Versioning
|
214
|
+
|
215
|
+
Starting with version 0.2.0, SwiftClient uses Semantic Versioning:
|
216
|
+
[SemVer](http://semver.org/)
|
data/lib/swift_client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
|
2
2
|
require "swift_client/version"
|
3
|
+
require "swift_client/null_cache"
|
3
4
|
|
4
5
|
require "httparty"
|
5
6
|
require "mime-types"
|
@@ -25,71 +26,72 @@ class SwiftClient
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
attr_accessor :options, :auth_token, :storage_url
|
29
|
+
attr_accessor :options, :auth_token, :storage_url, :cache_store
|
29
30
|
|
30
31
|
def initialize(options = {})
|
31
32
|
raise(OptionError, "Setting expires_in connection wide is deprecated") if options[:expires_in]
|
32
33
|
|
33
34
|
self.options = options
|
35
|
+
self.cache_store = options[:cache_store] || SwiftClient::NullCache.new
|
34
36
|
|
35
37
|
authenticate
|
36
38
|
end
|
37
39
|
|
38
|
-
def head_account
|
39
|
-
request :head, "/"
|
40
|
+
def head_account(options = {})
|
41
|
+
request :head, "/", options
|
40
42
|
end
|
41
43
|
|
42
|
-
def post_account(headers = {})
|
43
|
-
request :post, "/", :headers => headers
|
44
|
+
def post_account(headers = {}, options = {})
|
45
|
+
request :post, "/", options.merge(:headers => headers)
|
44
46
|
end
|
45
47
|
|
46
|
-
def head_containers
|
47
|
-
request :head, "/"
|
48
|
+
def head_containers(options = {})
|
49
|
+
request :head, "/", options
|
48
50
|
end
|
49
51
|
|
50
|
-
def get_containers(query = {})
|
51
|
-
request :get, "/", :query => query
|
52
|
+
def get_containers(query = {}, options = {})
|
53
|
+
request :get, "/", options.merge(:query => query)
|
52
54
|
end
|
53
55
|
|
54
|
-
def paginate_containers(query = {}, &block)
|
55
|
-
paginate
|
56
|
+
def paginate_containers(query = {}, options = {}, &block)
|
57
|
+
paginate(:get_containers, query, options, &block)
|
56
58
|
end
|
57
59
|
|
58
|
-
def get_container(container_name, query = {})
|
60
|
+
def get_container(container_name, query = {}, options = {})
|
59
61
|
raise(EmptyNameError) if container_name.empty?
|
60
62
|
|
61
|
-
request :get, "/#{container_name}", :query => query
|
63
|
+
request :get, "/#{container_name}", options.merge(:query => query)
|
62
64
|
end
|
63
65
|
|
64
|
-
def paginate_container(container_name, query = {}, &block)
|
65
|
-
paginate
|
66
|
+
def paginate_container(container_name, query = {}, options = {}, &block)
|
67
|
+
paginate(:get_container, container_name, query, options, &block)
|
66
68
|
end
|
67
69
|
|
68
|
-
def head_container(container_name)
|
70
|
+
def head_container(container_name, options = {})
|
69
71
|
raise(EmptyNameError) if container_name.empty?
|
70
72
|
|
71
|
-
request :head, "/#{container_name}"
|
73
|
+
request :head, "/#{container_name}", options
|
72
74
|
end
|
73
75
|
|
74
|
-
def put_container(container_name, headers = {})
|
76
|
+
def put_container(container_name, headers = {}, options = {})
|
75
77
|
raise(EmptyNameError) if container_name.empty?
|
76
78
|
|
77
|
-
request :put, "/#{container_name}", :headers => headers
|
79
|
+
request :put, "/#{container_name}", options.merge(:headers => headers)
|
78
80
|
end
|
79
81
|
|
80
|
-
def post_container(container_name, headers = {})
|
82
|
+
def post_container(container_name, headers = {}, options = {})
|
81
83
|
raise(EmptyNameError) if container_name.empty?
|
82
84
|
|
83
|
-
request :post, "/#{container_name}", :headers => headers
|
85
|
+
request :post, "/#{container_name}", options.merge(:headers => headers)
|
84
86
|
end
|
85
87
|
|
86
|
-
def delete_container(container_name)
|
88
|
+
def delete_container(container_name, options = {})
|
87
89
|
raise(EmptyNameError) if container_name.empty?
|
88
90
|
|
89
|
-
request :delete, "/#{container_name}"
|
91
|
+
request :delete, "/#{container_name}", options
|
90
92
|
end
|
91
93
|
|
92
|
-
def put_object(object_name, data_or_io, container_name, headers = {})
|
94
|
+
def put_object(object_name, data_or_io, container_name, headers = {}, options = {})
|
93
95
|
raise(EmptyNameError) if object_name.empty? || container_name.empty?
|
94
96
|
|
95
97
|
mime_type = MIME::Types.of(object_name).first
|
@@ -101,43 +103,52 @@ class SwiftClient
|
|
101
103
|
extended_headers["Content-Type"] ||= "application/octet-stream"
|
102
104
|
end
|
103
105
|
|
104
|
-
extended_headers["Transfer-Encoding"]
|
106
|
+
if extended_headers["Transfer-Encoding"] == "identity"
|
107
|
+
request :put, "/#{container_name}/#{object_name}", options.merge(:body => data_or_io.respond_to?(:read) ? data_or_io.read : data_or_io, :headers => extended_headers)
|
108
|
+
else
|
109
|
+
extended_headers["Transfer-Encoding"] = "chunked"
|
110
|
+
request :put, "/#{container_name}/#{object_name}", options.merge(:body_stream => data_or_io.respond_to?(:read) ? data_or_io : StringIO.new(data_or_io), :headers => extended_headers)
|
111
|
+
end
|
112
|
+
end
|
105
113
|
|
106
|
-
|
114
|
+
def post_object(object_name, container_name, headers = {}, options = {})
|
115
|
+
raise(EmptyNameError) if object_name.empty? || container_name.empty?
|
116
|
+
|
117
|
+
request :post, "/#{container_name}/#{object_name}", options.merge(:headers => headers)
|
107
118
|
end
|
108
119
|
|
109
|
-
def
|
120
|
+
def get_object(object_name, container_name, options = {}, &block)
|
110
121
|
raise(EmptyNameError) if object_name.empty? || container_name.empty?
|
111
122
|
|
112
|
-
request
|
123
|
+
request(:get, "/#{container_name}/#{object_name}", options.merge(block ? { :stream_body => true } : {}), &block)
|
113
124
|
end
|
114
125
|
|
115
|
-
def
|
126
|
+
def head_object(object_name, container_name, options = {})
|
116
127
|
raise(EmptyNameError) if object_name.empty? || container_name.empty?
|
117
128
|
|
118
|
-
request :
|
129
|
+
request :head, "/#{container_name}/#{object_name}", options
|
119
130
|
end
|
120
131
|
|
121
|
-
def
|
132
|
+
def post_head(object_name, container_name, _headers = {}, options = {})
|
122
133
|
raise(EmptyNameError) if object_name.empty? || container_name.empty?
|
123
134
|
|
124
|
-
request :
|
135
|
+
request :post, "/#{container_name}/#{object_name}", options.merge(headers: _headers)
|
125
136
|
end
|
126
137
|
|
127
|
-
def delete_object(object_name, container_name)
|
138
|
+
def delete_object(object_name, container_name, options = {})
|
128
139
|
raise(EmptyNameError) if object_name.empty? || container_name.empty?
|
129
140
|
|
130
|
-
request :delete, "/#{container_name}/#{object_name}"
|
141
|
+
request :delete, "/#{container_name}/#{object_name}", options
|
131
142
|
end
|
132
143
|
|
133
|
-
def get_objects(container_name, query = {})
|
144
|
+
def get_objects(container_name, query = {}, options = {})
|
134
145
|
raise(EmptyNameError) if container_name.empty?
|
135
146
|
|
136
|
-
request :get, "/#{container_name}", :query => query
|
147
|
+
request :get, "/#{container_name}", options.merge(:query => query)
|
137
148
|
end
|
138
149
|
|
139
|
-
def paginate_objects(container_name, query = {}, &block)
|
140
|
-
paginate
|
150
|
+
def paginate_objects(container_name, query = {}, options = {}, &block)
|
151
|
+
paginate(:get_objects, container_name, query, options, &block)
|
141
152
|
end
|
142
153
|
|
143
154
|
def public_url(object_name, container_name)
|
@@ -158,27 +169,44 @@ class SwiftClient
|
|
158
169
|
"#{storage_url}/#{container_name}/#{object_name}?temp_url_sig=#{signature}&temp_url_expires=#{expires}"
|
159
170
|
end
|
160
171
|
|
172
|
+
def bulk_delete(items, options = {})
|
173
|
+
items.each_slice(1_000) do |slice|
|
174
|
+
request :delete, "/?bulk-delete", options.merge(:body => slice.join("\n"), :headers => { "Content-Type" => "text/plain" })
|
175
|
+
end
|
176
|
+
|
177
|
+
items
|
178
|
+
end
|
179
|
+
|
161
180
|
private
|
162
181
|
|
182
|
+
def cache_key
|
183
|
+
auth_keys = [:auth_url, :username, :access_key, :user_id, :user_domain, :user_domain_id, :domain_name,
|
184
|
+
:domain_id, :token, :project_id, :project_name, :project_domain_name, :project_domain_id, :tenant_name]
|
185
|
+
|
186
|
+
auth_key = auth_keys.collect { |key| options[key] }.inspect
|
187
|
+
|
188
|
+
Digest::SHA1.hexdigest(auth_key)
|
189
|
+
end
|
190
|
+
|
163
191
|
def find_header_key(headers, key)
|
164
192
|
headers.keys.detect { |k| k.downcase == key.downcase }
|
165
193
|
end
|
166
194
|
|
167
|
-
def request(method, path, opts = {})
|
195
|
+
def request(method, path, opts = {}, &block)
|
168
196
|
headers = (opts[:headers] || {}).dup
|
169
197
|
headers["X-Auth-Token"] = auth_token
|
170
198
|
headers["Accept"] = "application/json"
|
171
199
|
|
172
200
|
stream_pos = opts[:body_stream].pos if opts[:body_stream]
|
173
201
|
|
174
|
-
response = HTTParty.send(method, "#{storage_url}#{path}", opts.merge(:headers => headers))
|
202
|
+
response = HTTParty.send(method, "#{storage_url}#{path}", opts.merge(:headers => headers), &block)
|
175
203
|
|
176
204
|
if response.code == 401
|
177
205
|
authenticate
|
178
206
|
|
179
207
|
opts[:body_stream].pos = stream_pos if opts[:body_stream]
|
180
208
|
|
181
|
-
return request(method, path, opts)
|
209
|
+
return request(method, path, opts, &block)
|
182
210
|
end
|
183
211
|
|
184
212
|
raise(ResponseError.new(response.code, response.message)) unless response.success?
|
@@ -187,12 +215,38 @@ class SwiftClient
|
|
187
215
|
end
|
188
216
|
|
189
217
|
def authenticate
|
218
|
+
return if authenticate_from_cache
|
219
|
+
|
190
220
|
return authenticate_v3 if options[:auth_url] =~ /v3/
|
191
221
|
return authenticate_v2 if options[:auth_url] =~ /v2/
|
192
222
|
|
193
223
|
authenticate_v1
|
194
224
|
end
|
195
225
|
|
226
|
+
def authenticate_from_cache
|
227
|
+
cached_auth_token = cache_store.get("swift_client:auth_token:#{cache_key}")
|
228
|
+
cached_storage_url = cache_store.get("swift_client:storage_url:#{cache_key}")
|
229
|
+
|
230
|
+
return false if cached_auth_token.nil? || cached_storage_url.nil?
|
231
|
+
|
232
|
+
if cached_auth_token != auth_token || cached_storage_url != storage_url
|
233
|
+
self.auth_token = cached_auth_token
|
234
|
+
self.storage_url = cached_storage_url
|
235
|
+
|
236
|
+
return true
|
237
|
+
end
|
238
|
+
|
239
|
+
false
|
240
|
+
end
|
241
|
+
|
242
|
+
def set_authentication_details(auth_token, storage_url)
|
243
|
+
cache_store.set("swift_client:auth_token:#{cache_key}", auth_token)
|
244
|
+
cache_store.set("swift_client:storage_url:#{cache_key}", storage_url)
|
245
|
+
|
246
|
+
self.auth_token = auth_token
|
247
|
+
self.storage_url = storage_url
|
248
|
+
end
|
249
|
+
|
196
250
|
def authenticate_v1
|
197
251
|
[:auth_url, :username, :api_key].each do |key|
|
198
252
|
raise(AuthenticationError, "#{key} missing") unless options[key]
|
@@ -202,8 +256,7 @@ class SwiftClient
|
|
202
256
|
|
203
257
|
raise(AuthenticationError, "#{response.code}: #{response.message}") unless response.success?
|
204
258
|
|
205
|
-
|
206
|
-
self.storage_url = options[:storage_url] || response.headers["X-Storage-Url"]
|
259
|
+
set_authentication_details response.headers["X-Auth-Token"], options[:storage_url] || response.headers["X-Storage-Url"]
|
207
260
|
end
|
208
261
|
|
209
262
|
def authenticate_v2
|
@@ -231,8 +284,7 @@ class SwiftClient
|
|
231
284
|
|
232
285
|
raise(AuthenticationError, "#{response.code}: #{response.message}") unless response.success?
|
233
286
|
|
234
|
-
|
235
|
-
self.storage_url = options[:storage_url]
|
287
|
+
set_authentication_details response.parsed_response["access"]["token"]["id"], options[:storage_url]
|
236
288
|
end
|
237
289
|
|
238
290
|
def authenticate_v3
|
@@ -277,10 +329,11 @@ class SwiftClient
|
|
277
329
|
|
278
330
|
raise(AuthenticationError, "#{response.code}: #{response.message}") unless response.success?
|
279
331
|
|
280
|
-
|
281
|
-
self.storage_url = options[:storage_url] || storage_url_from_v3_response(response)
|
332
|
+
storage_url = options[:storage_url] || storage_url_from_v3_response(response)
|
282
333
|
|
283
334
|
raise(AuthenticationError, "storage_url missing") unless storage_url
|
335
|
+
|
336
|
+
set_authentication_details response.headers["X-Subject-Token"], storage_url
|
284
337
|
end
|
285
338
|
|
286
339
|
def storage_url_from_v3_response(response)
|
@@ -289,7 +342,8 @@ class SwiftClient
|
|
289
342
|
|
290
343
|
return unless swift_services.size == 1
|
291
344
|
|
292
|
-
|
345
|
+
interface = options[:interface] || "public"
|
346
|
+
swift_endpoints = swift_service["endpoints"].select { |endpoint| endpoint["interface"] == interface }
|
293
347
|
swift_endpoint = swift_endpoints.first
|
294
348
|
|
295
349
|
return unless swift_endpoints.size == 1
|
@@ -297,13 +351,13 @@ class SwiftClient
|
|
297
351
|
swift_endpoint["url"]
|
298
352
|
end
|
299
353
|
|
300
|
-
def paginate(method, *args, query)
|
301
|
-
return enum_for(:paginate, method, *args, query) unless block_given?
|
354
|
+
def paginate(method, *args, query, options)
|
355
|
+
return enum_for(:paginate, method, *args, query, options) unless block_given?
|
302
356
|
|
303
357
|
marker = nil
|
304
358
|
|
305
359
|
loop do
|
306
|
-
response = send(method, *args, marker ? query.merge(:marker => marker) : query)
|
360
|
+
response = send(method, *args, marker ? query.merge(:marker => marker) : query, options)
|
307
361
|
|
308
362
|
return if response.parsed_response.empty?
|
309
363
|
|
@@ -313,4 +367,3 @@ class SwiftClient
|
|
313
367
|
end
|
314
368
|
end
|
315
369
|
end
|
316
|
-
|
data/lib/swift_client/version.rb
CHANGED
data/test/swift_client_test.rb
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
|
2
2
|
require File.expand_path("../test_helper", __FILE__)
|
3
3
|
|
4
|
+
class MemoryCache
|
5
|
+
def initialize
|
6
|
+
@cache = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def set(key, value)
|
10
|
+
@cache[key] = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(key)
|
14
|
+
@cache[key]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
4
18
|
class SwiftClientTest < MiniTest::Test
|
5
19
|
def setup
|
6
20
|
stub_request(:get, "https://example.com/auth/v1.0").with(:headers => { "X-Auth-Key" => "secret", "X-Auth-User" => "account:username" }).to_return(:status => 200, :body => "", :headers => { "X-Auth-Token" => "Token", "X-Storage-Url" => "https://example.com/v1/AUTH_account" })
|
@@ -11,6 +25,22 @@ class SwiftClientTest < MiniTest::Test
|
|
11
25
|
assert_equal "https://example.com/v1/AUTH_account", @swift_client.storage_url
|
12
26
|
end
|
13
27
|
|
28
|
+
def test_authenticate_from_cache
|
29
|
+
cache = MemoryCache.new
|
30
|
+
cache.set("swift_client:auth_token:49f42f2927701ba93a5bf9750da8fedfa197fa82", "Cached token")
|
31
|
+
cache.set("swift_client:storage_url:49f42f2927701ba93a5bf9750da8fedfa197fa82", "https://cache.example.com/v1/AUTH_account")
|
32
|
+
|
33
|
+
@swift_client = SwiftClient.new(:auth_url => "https://example.com/auth/v1.0", :username => "account:username", :api_key => "secret", :temp_url_key => "Temp url key", :cache_store => cache)
|
34
|
+
|
35
|
+
assert_equal "Cached token", @swift_client.auth_token
|
36
|
+
assert_equal "https://cache.example.com/v1/AUTH_account", @swift_client.storage_url
|
37
|
+
|
38
|
+
@swift_client.send(:authenticate) # Re-authenticate
|
39
|
+
|
40
|
+
assert_equal "Token", @swift_client.auth_token
|
41
|
+
assert_equal "https://example.com/v1/AUTH_account", @swift_client.storage_url
|
42
|
+
end
|
43
|
+
|
14
44
|
def test_v3_authentication_unscoped_with_password
|
15
45
|
stub_request(:post, "https://auth.example.com/v3/auth/tokens").with(:body => JSON.dump("auth" => { "identity" => { "methods" => ["password"], "password" => { "user" => { "name" => "username", "password" => "secret", "domain" => { "name" => "example.com" }}}}})).to_return(:status => 200, :body => JSON.dump("token" => "..."), :headers => { "X-Subject-Token" => "Token", "Content-Type" => "application/json" })
|
16
46
|
|
@@ -47,6 +77,15 @@ class SwiftClientTest < MiniTest::Test
|
|
47
77
|
assert_equal "https://example.com/v1/AUTH_account", @swift_client.storage_url
|
48
78
|
end
|
49
79
|
|
80
|
+
def test_v3_authentication_storage_url_from_catalog_with_multiple_endpoints
|
81
|
+
stub_request(:post, "https://auth.example.com/v3/auth/tokens").with(:body => JSON.dump("auth" => { "identity" => { "methods" => ["password"], "password" => { "user" => { "name" => "username", "password" => "secret", "domain" => { "name" => "example.com" }}}}})).to_return(:status => 200, :body => JSON.dump("token" => { "catalog"=> [{ "type" => "object-store", "endpoints" => [{ "interface"=>"public", "url"=> "https://example.com/v1/AUTH_account" }, { "interface"=>"internal", "url"=> "https://example.com/v2/AUTH_account" }] }] }), :headers => { "X-Subject-Token" => "Token", "Content-Type" => "application/json" })
|
82
|
+
|
83
|
+
@swift_client = SwiftClient.new(:auth_url => "https://auth.example.com/v3", :username => "username", :user_domain => "example.com", :password => "secret", :interface => "internal")
|
84
|
+
|
85
|
+
assert_equal "Token", @swift_client.auth_token
|
86
|
+
assert_equal "https://example.com/v2/AUTH_account", @swift_client.storage_url
|
87
|
+
end
|
88
|
+
|
50
89
|
def test_v3_authentication_without_storage_url_and_multiple_swifts
|
51
90
|
stub_request(:post, "https://auth.example.com/v3/auth/tokens").with(:body => JSON.dump("auth" => { "identity" => { "methods" => ["password"], "password" => { "user" => { "name" => "username", "password" => "secret", "domain" => { "name" => "example.com" }}}}})).to_return(:status => 200, :body => JSON.dump("token" => { "catalog"=> [{ "type" => "object-store", "endpoints" => [{ "interface"=>"public", "url"=> "https://example.com/v1/AUTH_account" }] }, { "type" => "object-store", "endpoints" => [{ "interface"=>"public", "url"=> "https://example.com/v1/AUTH_account" }] }] }), :headers => { "X-Subject-Token" => "Token", "Content-Type" => "application/json" })
|
52
91
|
|
@@ -134,6 +173,18 @@ class SwiftClientTest < MiniTest::Test
|
|
134
173
|
assert_equal containers, @swift_client.get_containers.parsed_response
|
135
174
|
end
|
136
175
|
|
176
|
+
def test_bulk_delete
|
177
|
+
objects = [
|
178
|
+
"container1/object1",
|
179
|
+
"container1/object2",
|
180
|
+
"container2/object1"
|
181
|
+
]
|
182
|
+
|
183
|
+
stub_request(:delete, "https://example.com/v1/AUTH_account/?bulk-delete").with(:body => objects.join("\n"), :headers => { "Content-Type" => "text/plain", "X-Auth-Token" => "Token" }).to_return(:status => 200,:body => "", :headers => { "Content-Type" => "application/json" })
|
184
|
+
|
185
|
+
assert @swift_client.bulk_delete(objects)
|
186
|
+
end
|
187
|
+
|
137
188
|
def test_paginate_containers
|
138
189
|
containers = [
|
139
190
|
{ "count" => 1, "bytes" => 1, "name" => "container-1" },
|
@@ -203,6 +254,12 @@ class SwiftClientTest < MiniTest::Test
|
|
203
254
|
assert_equal 201, @swift_client.put_object("object", "data", "container", "X-Object-Meta-Test" => "Test").code
|
204
255
|
end
|
205
256
|
|
257
|
+
def test_put_object_nonchunked
|
258
|
+
stub_request(:put, "https://example.com/v1/AUTH_account/container/object").with(:body => "data", :headers => { "Transfer-Encoding" => "identity", "Content-Type" => "application/octet-stream", "Accept" => "application/json", "X-Auth-Token" => "Token", "X-Object-Meta-Test" => "Test" }).to_return(:status => 201, :body => "", :headers => {})
|
259
|
+
|
260
|
+
assert_equal 201, @swift_client.put_object("object", "data", "container", "X-Object-Meta-Test" => "Test", "Transfer-Encoding" => "identity").code
|
261
|
+
end
|
262
|
+
|
206
263
|
def test_put_object_with_renewed_authorization
|
207
264
|
stub_request(:put, "https://example.com/v1/AUTH_account/container/object").with(:body => "data", :headers => { "Transfer-Encoding" => "chunked", "Content-Type" => "application/octet-stream", "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return({ :status => 401, :body => "", :headers => {}}, { :status => 201, :body => "", :headers => {}})
|
208
265
|
|
@@ -227,16 +284,38 @@ class SwiftClientTest < MiniTest::Test
|
|
227
284
|
assert_equal 201, @swift_client.put_object("object", StringIO.new("data"), "container", "X-Object-Meta-Test" => "Test").code
|
228
285
|
end
|
229
286
|
|
287
|
+
def test_put_object_with_io_nonchunked
|
288
|
+
stub_request(:put, "https://example.com/v1/AUTH_account/container/object").with(:body => "data", :headers => { "Transfer-Encoding" => "identity", "Accept" => "application/json", "X-Auth-Token" => "Token", "X-Object-Meta-Test" => "Test" }).to_return(:status => 201, :body => "", :headers => {})
|
289
|
+
|
290
|
+
assert_equal 201, @swift_client.put_object("object", StringIO.new("data"), "container", "X-Object-Meta-Test" => "Test", "Transfer-Encoding" => "identity").code
|
291
|
+
end
|
292
|
+
|
230
293
|
def test_post_object
|
231
294
|
stub_request(:post, "https://example.com/v1/AUTH_account/container/object").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token", "X-Object-Meta-Test" => "Test" }).to_return(:status => 201, :body => "", :headers => {})
|
232
295
|
|
233
296
|
assert_equal 201, @swift_client.post_object("object", "container", "X-Object-Meta-Test" => "Test").code
|
234
297
|
end
|
235
298
|
|
299
|
+
def test_post_head
|
300
|
+
stub_request(:post, 'https://example.com/v1/AUTH_account/container/object').with(headers: { 'Accept' => 'application/json', 'X-Auth-Token' => 'Token', 'X-Delete-At' => '1553524860' }).to_return(status: 201, body: '', headers: {})
|
301
|
+
|
302
|
+
assert_equal 201, @swift_client.post_head('object', 'container', 'X-Delete-At' => '1553524860').code
|
303
|
+
end
|
304
|
+
|
236
305
|
def test_get_object
|
237
306
|
stub_request(:get, "https://example.com/v1/AUTH_account/container/object").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => "Body", :headers => {})
|
307
|
+
block_res = 0
|
308
|
+
|
309
|
+
large_body = "Body" * 16384
|
310
|
+
stub_request(:get, "https://example.com/v1/AUTH_account/container/large_object").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => large_body, :headers => {})
|
238
311
|
|
239
312
|
assert_equal "Body", @swift_client.get_object("object", "container").body
|
313
|
+
|
314
|
+
@swift_client.get_object("large_object", "container") do |chunk|
|
315
|
+
block_res += chunk.length
|
316
|
+
end
|
317
|
+
|
318
|
+
assert_equal block_res, large_body.length
|
240
319
|
end
|
241
320
|
|
242
321
|
def test_head_object
|
@@ -298,4 +377,3 @@ class SwiftClientTest < MiniTest::Test
|
|
298
377
|
assert @swift_client.temp_url("object", "container", :expires_in => 86400) =~ %r{https://example.com/v1/AUTH_account/container/object\?temp_url_sig=[a-f0-9]{40}&temp_url_expires=1086400}
|
299
378
|
end
|
300
379
|
end
|
301
|
-
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: swift_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Vetter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -122,6 +122,7 @@ files:
|
|
122
122
|
- README.md
|
123
123
|
- Rakefile
|
124
124
|
- lib/swift_client.rb
|
125
|
+
- lib/swift_client/null_cache.rb
|
125
126
|
- lib/swift_client/version.rb
|
126
127
|
- swift_client.gemspec
|
127
128
|
- test/swift_client_test.rb
|
@@ -145,8 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
146
|
- !ruby/object:Gem::Version
|
146
147
|
version: '0'
|
147
148
|
requirements: []
|
148
|
-
|
149
|
-
rubygems_version: 2.2.2
|
149
|
+
rubygems_version: 3.0.3
|
150
150
|
signing_key:
|
151
151
|
specification_version: 4
|
152
152
|
summary: Small but powerful client to interact with OpenStack Swift
|