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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4555d9e6b744150b80127f8a144e358666a4d70c
4
- data.tar.gz: f6da1e97fa79dc31ee538f5c3fa08981d9e76c06
2
+ SHA256:
3
+ metadata.gz: e91d8e86490003310183e57091645c64e9e4b4a4462b99c82d9a804887a8e2a1
4
+ data.tar.gz: e826b43a18119d2c9057bc1ed9bcbaf774fc505d7c5313b9ec0fecdbfa1a3d55
5
5
  SHA512:
6
- metadata.gz: 34f5162ea5b2b2fcfa3f5647eaf8c2d31dbf8947678a32cb8f6c0134f299c63a6c1b89b5c85a19c82d660bb0510ac48bbdd25e5247aa41b00a632b99e9d29ec2
7
- data.tar.gz: 570eaee9130d6c3bd43af82dabe763ab8af06b60add3fc577d805ddeb28623889c50de51c1bd28efad0e6edc2f2e04b8dd6461e760a4eb0a6674cb20e92d5b7c
6
+ metadata.gz: 429ea5bbf5892ac978d33f633bd7aa5fcb2352d18c6f1d76e69af21a36ad97dc2fb7f08fc08427d39c0e08fb59a80f1591481d52723e3efcf6755ccda7ed02d4
7
+ data.tar.gz: 333a4e6328fd0b0185aca83fddfd5b07628917b2139148bdbca30ffb425e8eb8fb7600d181edc3d40ec5c0f6428dbdba48fcb6d9045ccc57175d14f64f16c862
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+ .ruby-version
@@ -1,9 +1,8 @@
1
1
 
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.7
6
- - 2.2.3
3
+ - 2.1.10
4
+ - 2.2.5
5
+ - 2.3.1
7
6
 
8
7
  install:
9
8
  - "travis_retry bundle install"
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 -> HTTParty::Response
130
- * post_account(headers = {}) -> HTTParty::Response
131
- * head_containers -> HTTParty::Response
132
- * get_containers(query = {}) -> HTTParty::Response
133
- * paginate_containers(query = {}) -> Enumerator
134
- * get_container(container_name, query = {}) -> HTTParty::Response
135
- * paginate_container(container_name, query = {}) -> Enumerator
136
- * head_container(container_name) -> HTTParty::Response
137
- * put_container(container_name, headers = {}) -> HTTParty::Response
138
- * post_container(container_name, headers = {}) -> HTTParty::Response
139
- * delete_container(container_name) -> HTTParty::Response
140
- * put_object(object_name, data_or_io, container_name, headers = {}) -> HTTParty::Response
141
- * post_object(object_name, container_name, headers = {}) -> HTTParty::Response
142
- * get_object(object_name, container_name) -> HTTParty::Response
143
- * head_object(object_name, container_name) -> HTTParty::Response
144
- * delete_object(object_name, container_name) -> HTTParty::Response
145
- * get_objects(container_name, query = {}) -> HTTParty::Response
146
- * paginate_objects(container_name, query = {}) -> Enumerator
147
- * public_url(object_name, container_name) -> HTTParty::Response
148
- * temp_url(object_name, container_name, options = {}) -> HTTParty::Response
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/)
@@ -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 :get_containers, query, &block
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 :get_container, container_name, query, &block
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"] = "chunked"
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
- request :put, "/#{container_name}/#{object_name}", :body_stream => data_or_io.respond_to?(:read) ? data_or_io : StringIO.new(data_or_io), :headers => extended_headers
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 post_object(object_name, container_name, headers = {})
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 :post, "/#{container_name}/#{object_name}", :headers => headers
123
+ request(:get, "/#{container_name}/#{object_name}", options.merge(block ? { :stream_body => true } : {}), &block)
113
124
  end
114
125
 
115
- def get_object(object_name, container_name)
126
+ def head_object(object_name, container_name, options = {})
116
127
  raise(EmptyNameError) if object_name.empty? || container_name.empty?
117
128
 
118
- request :get, "/#{container_name}/#{object_name}"
129
+ request :head, "/#{container_name}/#{object_name}", options
119
130
  end
120
131
 
121
- def head_object(object_name, container_name)
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 :head, "/#{container_name}/#{object_name}"
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 :get_objects, container_name, query, &block
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
- self.auth_token = response.headers["X-Auth-Token"]
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
- self.auth_token = response.parsed_response["access"]["token"]["id"]
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
- self.auth_token = response.headers["X-Subject-Token"]
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
- swift_endpoints = swift_service["endpoints"].select { |endpoint| endpoint["interface"] == "public" }
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
-
@@ -0,0 +1,11 @@
1
+
2
+ class SwiftClient::NullCache
3
+ def get(key)
4
+ nil
5
+ end
6
+
7
+ def set(key, value)
8
+ true
9
+ end
10
+ end
11
+
@@ -1,5 +1,4 @@
1
1
 
2
2
  class SwiftClient
3
- VERSION = "0.1.3"
3
+ VERSION = "0.2.0"
4
4
  end
5
-
@@ -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
-
@@ -4,4 +4,4 @@ require "minitest"
4
4
  require "minitest/autorun"
5
5
  require "webmock/minitest"
6
6
  require "minitest/unit"
7
- require "mocha/mini_test"
7
+ require "mocha/minitest"
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.1.3
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: 2016-03-14 00:00:00.000000000 Z
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
- rubyforge_project:
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