swift_client 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|