swift_client 0.0.5 → 0.0.6

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.
data/README.md CHANGED
@@ -23,13 +23,29 @@ Or install it yourself as:
23
23
  First, connect to a Swift cluster:
24
24
 
25
25
  ```ruby
26
- swift_client = SwiftClient.new(:auth_url => "https://example.com/auth/v1.0", :username => "account:username", :api_key => "secret api key", :temp_url_key => "optional temp url key")
26
+ swift_client = SwiftClient.new(:auth_url => "https://example.com/auth/v1.0", :username => "account:username", :api_key => "api key", :temp_url_key => "temp url key", :storage_url => "https://example.com/v1/AUTH_account")
27
27
  ```
28
28
 
29
+ To connect via v2 you have to add version and method specific details:
30
+
31
+ ```ruby
32
+ swift_client = SwiftClient.new(:auth_url => "https://auth.example.com/v2.0", :storage_url => "https://storage.example.com/v1/AUTH_account", :tenant_name => "tenant", :username => "username", :password => "password")
33
+
34
+ # OR
35
+
36
+ swift_client = SwiftClient.new(:auth_url => "https://auth.example.com/v2.0", :storage_url => "https://storage.example.com/v1/AUTH_account", :tenant_name => "tenant", :access_key => "access key", :secret_key => "secret key")
37
+ ```
38
+
39
+ where `temp_url_key` and `storage_url` are optional.
40
+
29
41
  SwiftClient will automatically reconnect in case the endpoint responds with 401
30
- Unauthorized to one of your requests using the provided credentials.
31
- Otherwise, i.e. in case the endpoint does not respond with 2xx to any of
32
- SwiftClient's requests, SwiftClient will raise a `SwiftClient::ResponseError`.
42
+ Unauthorized to one of your requests using the provided credentials. In case
43
+ the endpoint does not respond with 2xx to any of SwiftClient's requests,
44
+ SwiftClient will raise a `SwiftClient::ResponseError`. Otherwise, SwiftClient
45
+ responds with an `HTTParty::Response` object, such that you can call `#headers`
46
+ to access the response headers or `#body` as well as `#parsed_response` to
47
+ access the response body and JSON response. Checkout the
48
+ [HTTParty](https://github.com/jnunemaker/httparty) gem to learn more.
33
49
 
34
50
  SwiftClient offers the following requests:
35
51
 
@@ -37,19 +53,22 @@ SwiftClient offers the following requests:
37
53
  * post_account(headers = {}) -> HTTParty::Response
38
54
  * head_containers -> HTTParty::Response
39
55
  * get_containers(query = {}) -> HTTParty::Response
40
- * get_container(container, query = {}) -> HTTParty::Response
41
- * head_container(container) -> HTTParty::Response
42
- * put_container(container, headers = {}) -> HTTParty::Response
43
- * post_container(container, headers = {}) -> HTTParty::Response
44
- * delete_container(container) -> HTTParty::Response
45
- * put_object(object, data_or_io, container, headers = {}) -> HTTParty::Response
46
- * post_object(object, container, headers = {}) -> HTTParty::Response
47
- * get_object(object, container) -> HTTParty::Response
48
- * head_object(object, container) -> HTTParty::Response
49
- * delete_object(object, container) -> HTTParty::Response
50
- * get_objects(container, query = {}) -> HTTParty::Response
51
- * public_url(object, container) -> HTTParty::Response
52
- * temp_url(object, container) -> HTTParty::Response
56
+ * paginate_containers(query = {}) -> Enumerator
57
+ * get_container(container_name, query = {}) -> HTTParty::Response
58
+ * paginate_container(container_name, query = {}) -> Enumerator
59
+ * head_container(container_name) -> HTTParty::Response
60
+ * put_container(container_name, headers = {}) -> HTTParty::Response
61
+ * post_container(container_name, headers = {}) -> HTTParty::Response
62
+ * delete_container(container_name) -> HTTParty::Response
63
+ * put_object(object_name, data_or_io, container_name, headers = {}) -> HTTParty::Response
64
+ * post_object(object_name, container_name, headers = {}) -> HTTParty::Response
65
+ * get_object(object_name, container_name) -> HTTParty::Response
66
+ * head_object(object_name, container_name) -> HTTParty::Response
67
+ * delete_object(object_name, container_name) -> HTTParty::Response
68
+ * get_objects(container_name, query = {}) -> HTTParty::Response
69
+ * paginate_objetcs(container_name, query = {}) -> Enumerator
70
+ * public_url(object_name, container_name) -> HTTParty::Response
71
+ * temp_url(object_name, container_name) -> HTTParty::Response
53
72
 
54
73
  ## Contributing
55
74
 
@@ -1,5 +1,5 @@
1
1
 
2
2
  class SwiftClient
3
- VERSION = "0.0.5"
3
+ VERSION = "0.0.6"
4
4
  end
5
5
 
data/lib/swift_client.rb CHANGED
@@ -27,10 +27,6 @@ class SwiftClient
27
27
  attr_accessor :options, :auth_token, :storage_url
28
28
 
29
29
  def initialize(options = {})
30
- [:auth_url, :username, :api_key].each do |key|
31
- raise(OptionError, "#{key} is missing") unless options.key?(key)
32
- end
33
-
34
30
  self.options = options
35
31
 
36
32
  authenticate
@@ -52,93 +48,105 @@ class SwiftClient
52
48
  request :get, "/", :query => query
53
49
  end
54
50
 
55
- def get_container(container, query = {})
56
- raise(EmptyNameError) if container.empty?
51
+ def paginate_containers(query = {}, &block)
52
+ paginate :get_containers, query, &block
53
+ end
54
+
55
+ def get_container(container_name, query = {})
56
+ raise(EmptyNameError) if container_name.empty?
57
57
 
58
- request :get, "/#{container}", :query => query
58
+ request :get, "/#{container_name}", :query => query
59
59
  end
60
60
 
61
- def head_container(container)
62
- raise(EmptyNameError) if container.empty?
61
+ def paginate_container(container_name, query = {}, &block)
62
+ paginate :get_container, container_name, query, &block
63
+ end
64
+
65
+ def head_container(container_name)
66
+ raise(EmptyNameError) if container_name.empty?
63
67
 
64
- request :head, "/#{container}"
68
+ request :head, "/#{container_name}"
65
69
  end
66
70
 
67
- def put_container(container, headers = {})
68
- raise(EmptyNameError) if container.empty?
71
+ def put_container(container_name, headers = {})
72
+ raise(EmptyNameError) if container_name.empty?
69
73
 
70
- request :put, "/#{container}", :headers => headers
74
+ request :put, "/#{container_name}", :headers => headers
71
75
  end
72
76
 
73
- def post_container(container, headers = {})
74
- raise(EmptyNameError) if container.empty?
77
+ def post_container(container_name, headers = {})
78
+ raise(EmptyNameError) if container_name.empty?
75
79
 
76
- request :post, "/#{container}", :headers => headers
80
+ request :post, "/#{container_name}", :headers => headers
77
81
  end
78
82
 
79
- def delete_container(container)
80
- raise(EmptyNameError) if container.empty?
83
+ def delete_container(container_name)
84
+ raise(EmptyNameError) if container_name.empty?
81
85
 
82
- request :delete, "/#{container}"
86
+ request :delete, "/#{container_name}"
83
87
  end
84
88
 
85
- def put_object(object, data_or_io, container, headers = {})
86
- raise(EmptyNameError) if object.empty? || container.empty?
89
+ def put_object(object_name, data_or_io, container_name, headers = {})
90
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
87
91
 
88
- mime_type = MIME::Types.of(object).first
92
+ mime_type = MIME::Types.of(object_name).first
89
93
 
90
94
  extended_headers = headers.dup
91
95
  extended_headers["Content-Type"] ||= mime_type.content_type if mime_type
92
96
 
93
- request :put, "/#{container}/#{object}", :body => data_or_io.respond_to?(:read) ? data_or_io.read : data_or_io, :headers => extended_headers
97
+ request :put, "/#{container_name}/#{object_name}", :body => data_or_io.respond_to?(:read) ? data_or_io.read : data_or_io, :headers => extended_headers
94
98
  end
95
99
 
96
- def post_object(object, container, headers = {})
97
- raise(EmptyNameError) if object.empty? || container.empty?
100
+ def post_object(object_name, container_name, headers = {})
101
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
98
102
 
99
- request :post, "/#{container}/#{object}", :headers => headers
103
+ request :post, "/#{container_name}/#{object_name}", :headers => headers
100
104
  end
101
105
 
102
- def get_object(object, container)
103
- raise(EmptyNameError) if object.empty? || container.empty?
106
+ def get_object(object_name, container_name)
107
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
104
108
 
105
- request :get, "/#{container}/#{object}"
109
+ request :get, "/#{container_name}/#{object_name}"
106
110
  end
107
111
 
108
- def head_object(object, container)
109
- raise(EmptyNameError) if object.empty? || container.empty?
112
+ def head_object(object_name, container_name)
113
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
110
114
 
111
- request :head, "/#{container}/#{object}"
115
+ request :head, "/#{container_name}/#{object_name}"
112
116
  end
113
117
 
114
- def delete_object(object, container)
115
- raise(EmptyNameError) if object.empty? || container.empty?
118
+ def delete_object(object_name, container_name)
119
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
116
120
 
117
- request :delete, "/#{container}/#{object}"
121
+ request :delete, "/#{container_name}/#{object_name}"
118
122
  end
119
123
 
120
- def get_objects(container, query = {})
121
- raise(EmptyNameError) if container.empty?
124
+ def get_objects(container_name, query = {})
125
+ raise(EmptyNameError) if container_name.empty?
126
+
127
+ request :get, "/#{container_name}", :query => query
128
+ end
122
129
 
123
- request :get, "/#{container}", :query => query
130
+ def paginate_objects(container_name, query = {}, &block)
131
+ paginate :get_objects, container_name, query, &block
124
132
  end
125
133
 
126
- def public_url(object, container)
127
- raise(EmptyNameError) if object.empty? || container.empty?
134
+ def public_url(object_name, container_name)
135
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
128
136
 
129
- "#{storage_url}/#{container}/#{object}"
137
+ "#{storage_url}/#{container_name}/#{object_name}"
130
138
  end
131
139
 
132
- def temp_url(object, container, opts = {})
133
- raise(EmptyNameError) if object.empty? || container.empty?
140
+ def temp_url(object_name, container_name, opts = {})
141
+ raise(EmptyNameError) if object_name.empty? || container_name.empty?
134
142
  raise(TempUrlKeyMissing) unless options[:temp_url_key]
135
143
 
136
144
  expires = (Time.now + (options[:expires_in] || 3600).to_i).to_i
137
- path = URI.parse("#{storage_url}/#{container}/#{object}").path
145
+ path = URI.parse("#{storage_url}/#{container_name}/#{object_name}").path
138
146
 
139
147
  signature = OpenSSL::HMAC.hexdigest("sha1", options[:temp_url_key], "GET\n#{expires}\n#{path}")
140
148
 
141
- "#{storage_url}/#{container}/#{object}?temp_url_sig=#{signature}&temp_url_expires=#{expires}"
149
+ "#{storage_url}/#{container_name}/#{object_name}?temp_url_sig=#{signature}&temp_url_expires=#{expires}"
142
150
  end
143
151
 
144
152
  private
@@ -162,6 +170,14 @@ class SwiftClient
162
170
  end
163
171
 
164
172
  def authenticate
173
+ options[:auth_url] =~ /v2/ ? authenticate_v2 : authenticate_v1
174
+ end
175
+
176
+ def authenticate_v1
177
+ [:auth_url, :username, :api_key].each do |key|
178
+ raise(AuthenticationError, "#{key} missing") unless options[key]
179
+ end
180
+
165
181
  response = HTTParty.get(options[:auth_url], :headers => { "X-Auth-User" => options[:username], "X-Auth-Key" => options[:api_key] })
166
182
 
167
183
  raise(AuthenticationError, "#{response.code}: #{response.message}") unless response.success?
@@ -169,5 +185,50 @@ class SwiftClient
169
185
  self.auth_token = response.headers["X-Auth-Token"]
170
186
  self.storage_url = options[:storage_url] || response.headers["X-Storage-Url"]
171
187
  end
188
+
189
+ def authenticate_v2
190
+ [:auth_url, :storage_url].each do |key|
191
+ raise(AuthenticationError, "#{key} missing") unless options[key]
192
+ end
193
+
194
+ auth = { "auth" => {} }
195
+
196
+ if options[:tenant_name]
197
+ auth["auth"]["tenantName"] = options[:tenant_name]
198
+ else
199
+ raise AuthenticationError, "No tenant specified"
200
+ end
201
+
202
+ if options[:username] && options[:password]
203
+ auth["auth"]["passwordCredentials"] = { "username" => options[:username], "password" => options[:password] }
204
+ elsif options[:access_key] && options[:secret_key]
205
+ auth["auth"]["apiAccessKeyCredentials"] = { "accessKey" => options[:access_key], "secretKey" => options[:secret_key] }
206
+ else
207
+ raise AuthenticationError, "Unknown authentication method"
208
+ end
209
+
210
+ response = HTTParty.post("#{options[:auth_url].gsub(/\/+$/, "")}/tokens", :body => JSON.dump(auth), :headers => { "Content-Type" => "application/json" })
211
+
212
+ raise(AuthenticationError, "#{response.code}: #{response.message}") unless response.success?
213
+
214
+ self.auth_token = response.parsed_response["access"]["token"]["id"]
215
+ self.storage_url = options[:storage_url]
216
+ end
217
+
218
+ def paginate(method, *args, query)
219
+ return enum_for(:paginate, method, *args, query) unless block_given?
220
+
221
+ marker = nil
222
+
223
+ loop do
224
+ response = send(method, *args, marker ? query.merge(:marker => marker) : query)
225
+
226
+ return if response.parsed_response.empty?
227
+
228
+ yield response
229
+
230
+ marker = response.parsed_response.last["name"]
231
+ end
232
+ end
172
233
  end
173
234
 
@@ -11,6 +11,24 @@ class SwiftClientTest < MiniTest::Test
11
11
  assert_equal "https://example.com/v1/AUTH_account", @swift_client.storage_url
12
12
  end
13
13
 
14
+ def test_v2_authentication_with_password
15
+ stub_request(:post, "https://auth.example.com/v2.0/tokens").with(:body => JSON.dump("auth" => { "tenantName" => "Tenant", "passwordCredentials" => { "username" => "Username", :password => "Password" }})).to_return(:status => 200, :body => JSON.dump("access" => { "token" => { "id" => "Token" }}), :headers => { "Content-Type" => "application/json" })
16
+
17
+ @swift_client = SwiftClient.new(:storage_url => "https://example.com/v1/AUTH_account", :auth_url => "https://auth.example.com/v2.0", :tenant_name => "Tenant", :username => "Username", :password => "Password")
18
+
19
+ assert_equal "Token", @swift_client.auth_token
20
+ assert_equal "https://example.com/v1/AUTH_account", @swift_client.storage_url
21
+ end
22
+
23
+ def test_v2_authentication_with_key
24
+ stub_request(:post, "https://auth.example.com/v2.0/tokens").with(:body => JSON.dump("auth" => { "tenantName" => "Tenant", "apiAccessKeyCredentials" => { "accessKey" => "AccessKey", :secretKey => "SecretKey" }})).to_return(:status => 200, :body => JSON.dump("access" => { "token" => { "id" => "Token" }}), :headers => { "Content-Type" => "application/json" })
25
+
26
+ @swift_client = SwiftClient.new(:storage_url => "https://example.com/v1/AUTH_account", :auth_url => "https://auth.example.com/v2.0", :tenant_name => "Tenant", :access_key => "AccessKey", :secret_key => "SecretKey")
27
+
28
+ assert_equal "Token", @swift_client.auth_token
29
+ assert_equal "https://example.com/v1/AUTH_account", @swift_client.storage_url
30
+ end
31
+
14
32
  def test_storage_url
15
33
  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" })
16
34
 
@@ -49,6 +67,20 @@ class SwiftClientTest < MiniTest::Test
49
67
  assert_equal containers, @swift_client.get_containers.parsed_response
50
68
  end
51
69
 
70
+ def test_paginate_containers
71
+ containers = [
72
+ { "count" => 1, "bytes" => 1, "name" => "container-1" },
73
+ { "count" => 1, "bytes" => 1, "name" => "container-2" },
74
+ { "count" => 1, "bytes" => 1, "name" => "container-3" }
75
+ ]
76
+
77
+ stub_request(:get, "https://example.com/v1/AUTH_account/?limit=2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(containers[0 .. 1]), :headers => { "Content-Type" => "application/json" })
78
+ stub_request(:get, "https://example.com/v1/AUTH_account/?limit=2&marker=container-2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(containers[2 .. 3]), :headers => { "Content-Type" => "application/json" })
79
+ stub_request(:get, "https://example.com/v1/AUTH_account/?limit=2&marker=container-3").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump([]), :headers => { "Content-Type" => "application/json" })
80
+
81
+ assert_equal containers, @swift_client.paginate_containers(:limit => 2).collect(&:parsed_response).flatten
82
+ end
83
+
52
84
  def test_get_container
53
85
  objects = [
54
86
  { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-2", "content_type" => "Content type" },
@@ -60,6 +92,20 @@ class SwiftClientTest < MiniTest::Test
60
92
  assert_equal objects, @swift_client.get_container("container-1", :limit => 2, :marker => "object-2").parsed_response
61
93
  end
62
94
 
95
+ def test_paginate_container
96
+ objects = [
97
+ { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-1", "content_type" => "Content type" },
98
+ { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-2", "content_type" => "Content type" },
99
+ { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-3", "content_type" => "Content type" },
100
+ ]
101
+
102
+ stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(objects[0 .. 1]), :headers => { "Content-Type" => "application/json" })
103
+ stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2&marker=object-2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(objects[2 .. 3]), :headers => { "Content-Type" => "application/json" })
104
+ stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2&marker=object-3").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump([]), :headers => { "Content-Type" => "application/json" })
105
+
106
+ assert_equal objects, @swift_client.paginate_container("container-1", :limit => 2).collect(&:parsed_response).flatten
107
+ end
108
+
63
109
  def test_head_container
64
110
  stub_request(:head, "https://example.com/v1/AUTH_account/container-1").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 204, :body => "", :headers => { "Content-Type" => "application/json" })
65
111
 
@@ -128,7 +174,21 @@ class SwiftClientTest < MiniTest::Test
128
174
 
129
175
  stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2&marker=object-2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(objects), :headers => { "Content-Type" => "application/json" })
130
176
 
131
- assert_equal objects, @swift_client.get_container("container-1", :limit => 2, :marker => "object-2").parsed_response
177
+ assert_equal objects, @swift_client.get_objects("container-1", :limit => 2, :marker => "object-2").parsed_response
178
+ end
179
+
180
+ def test_paginate_objects
181
+ objects = [
182
+ { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-1", "content_type" => "Content type" },
183
+ { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-2", "content_type" => "Content type" },
184
+ { "hash" => "Hash", "last_modified" => "Last modified", "bytes" => 1, "name" => "object-3", "content_type" => "Content type" }
185
+ ]
186
+
187
+ stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(objects[0 .. 1]), :headers => { "Content-Type" => "application/json" })
188
+ stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2&marker=object-2").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump(objects[2 .. 3]), :headers => { "Content-Type" => "application/json" })
189
+ stub_request(:get, "https://example.com/v1/AUTH_account/container-1?limit=2&marker=object-3").with(:headers => { "Accept" => "application/json", "X-Auth-Token" => "Token" }).to_return(:status => 200, :body => JSON.dump([]), :headers => { "Content-Type" => "application/json" })
190
+
191
+ assert_equal objects, @swift_client.paginate_objects("container-1", :limit => 2).collect(&:parsed_response).flatten
132
192
  end
133
193
 
134
194
  def test_not_found
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swift_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-22 00:00:00.000000000 Z
12
+ date: 2015-01-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty