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