swift_client 0.0.5 → 0.0.6

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