att-swift 1.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 +7 -0
- checksums.yaml.gz.sig +6 -0
- data.tar.gz.sig +0 -0
- data/.autotest +17 -0
- data/.gemtest +0 -0
- data/History.rdoc +5 -0
- data/LICENSE.rdoc +20 -0
- data/Manifest.txt +9 -0
- data/README.rdoc +33 -0
- data/Rakefile +21 -0
- data/bin/swift_dump +27 -0
- data/lib/att/swift.rb +582 -0
- data/test/test_att_swift.rb +564 -0
- metadata +144 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a7af7bfed0c75cb7448a6d6551aa6106af7a694
|
4
|
+
data.tar.gz: a7e6de50f392136adec0125a0300dfce947efe23
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aac182861476e6c7f88095b0dfff587efd24e5f48b0323d7273ac2336344630434a9672a48dd63102fd11ed537995e26d2c67076a72d9c80249ac7db7cf61047
|
7
|
+
data.tar.gz: 43d852f419dc752077a6252d31358397f0d2f2664b5e96282c540dadd70cc1709cb03977cfb7fa4d482efb12ab07c48e1e7d1c587af5c1cfcb5ca1219986bbf0
|
checksums.yaml.gz.sig
ADDED
data.tar.gz.sig
ADDED
Binary file
|
data/.autotest
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
Autotest.add_hook :initialize do |at|
|
6
|
+
at.testlib = 'minitest/autorun'
|
7
|
+
at.add_exception '.git'
|
8
|
+
|
9
|
+
def at.path_to_classname s
|
10
|
+
sep = File::SEPARATOR
|
11
|
+
f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split(sep)
|
12
|
+
f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
|
13
|
+
f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}" }
|
14
|
+
f.join('::').gsub('Att', 'ATT')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
data/.gemtest
ADDED
File without changes
|
data/History.rdoc
ADDED
data/LICENSE.rdoc
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= att-swift
|
2
|
+
|
3
|
+
home :: https://github.com/att-cloud/att-swift
|
4
|
+
bugs :: https://github.com/att-cloud/att-swift/issues
|
5
|
+
docs :: http://docs.seattlerb.org/att-swift
|
6
|
+
|
7
|
+
== Description
|
8
|
+
|
9
|
+
A wrapper for OpenStack Object Storage v1 (aka Swift). Swift provides
|
10
|
+
redundant storage similar to AWS S3. This gem is a wrapper around the Object
|
11
|
+
Storage RESTful API and provides the ability to manage containers
|
12
|
+
(directories) and objects (files).
|
13
|
+
|
14
|
+
== Features and Problems
|
15
|
+
|
16
|
+
* Supports basic container actions
|
17
|
+
* Supports basic object actions
|
18
|
+
* Missing easy large object creation
|
19
|
+
* Missing most metadata actions
|
20
|
+
|
21
|
+
== Install
|
22
|
+
|
23
|
+
sudo gem install att-swift
|
24
|
+
|
25
|
+
== Developers
|
26
|
+
|
27
|
+
After checking out the source, run:
|
28
|
+
|
29
|
+
$ rake newb
|
30
|
+
|
31
|
+
This task will install any missing dependencies, run the tests/specs,
|
32
|
+
and generate the RDoc.
|
33
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.plugin :git
|
7
|
+
Hoe.plugin :minitest
|
8
|
+
Hoe.plugin :travis
|
9
|
+
|
10
|
+
Hoe.spec 'att-swift' do
|
11
|
+
developer 'Eric Hodel', 'drbrain@segment7.net'
|
12
|
+
|
13
|
+
self.licenses << 'MIT'
|
14
|
+
|
15
|
+
rdoc_locations <<
|
16
|
+
'docs.seattlerb.org:/data/www/docs.seattlerb.org/att-swift/'
|
17
|
+
|
18
|
+
extra_deps << ['net-http-persistent', '~> 2.7']
|
19
|
+
end
|
20
|
+
|
21
|
+
# vim: syntax=ruby
|
data/bin/swift_dump
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'att/swift'
|
4
|
+
|
5
|
+
uri = ARGV.shift
|
6
|
+
user = ARGV.shift
|
7
|
+
key = ARGV.shift
|
8
|
+
path = ARGV.shift
|
9
|
+
|
10
|
+
abort "swift_dump AUTH_URI USER KEY container[/object]" unless path
|
11
|
+
|
12
|
+
swift = ATT::Swift.new uri, user, key
|
13
|
+
|
14
|
+
container, object = path.split '/', 2
|
15
|
+
|
16
|
+
if object then
|
17
|
+
swift.read_object container, object do |res|
|
18
|
+
res.read_body do |chunk|
|
19
|
+
$stdout.write chunk
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
swift.paginate_objects container do |object_info|
|
24
|
+
puts object_info['name']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/att/swift.rb
ADDED
@@ -0,0 +1,582 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'digest'
|
3
|
+
require 'json'
|
4
|
+
require 'net/http/persistent'
|
5
|
+
|
6
|
+
module ATT # :nodoc:
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# A wrapper for OpenStack Object Storage v1 (aka Swift). Swift provides
|
11
|
+
# redundant storage similar to AWS S3. This gem is a wrapper around the
|
12
|
+
# Object # Storage RESTful API and provides the ability to manage containers
|
13
|
+
# (directories) and objects (files).
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# require 'pp'
|
18
|
+
# require 'att/swift'
|
19
|
+
#
|
20
|
+
# auth_uri = 'http://swift.example/auth/'
|
21
|
+
# swift = ATT::Swift.new auth_uri, 'username', 'password'
|
22
|
+
#
|
23
|
+
# pp swift.containers
|
24
|
+
#
|
25
|
+
# See also http://docs.openstack.org/api/openstack-object-storage/1.0/content/
|
26
|
+
|
27
|
+
class ATT::Swift
|
28
|
+
|
29
|
+
##
|
30
|
+
# The version of att-swift you are using
|
31
|
+
|
32
|
+
VERSION = '1.0'
|
33
|
+
|
34
|
+
attr_accessor :http # :nodoc:
|
35
|
+
|
36
|
+
attr_reader :auth_token # :nodoc:
|
37
|
+
attr_reader :storage_uri # :nodoc:
|
38
|
+
|
39
|
+
##
|
40
|
+
# Base error class for exceptions raised by Swift
|
41
|
+
|
42
|
+
class Error < RuntimeError
|
43
|
+
|
44
|
+
##
|
45
|
+
# Net::HTTP response for this error
|
46
|
+
|
47
|
+
attr_reader :http_response
|
48
|
+
|
49
|
+
##
|
50
|
+
# Creates a new Swift::Error for a Net::HTTP +response+ with the
|
51
|
+
# additional +message+ describing why the response caused the error.
|
52
|
+
|
53
|
+
def initialize response, message
|
54
|
+
@http_response = response
|
55
|
+
super "#{message} - #{response.code}"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Creates a new Swift object for communication with the server at
|
62
|
+
# +auth_uri+. +user+ and +key+ are your credentials.
|
63
|
+
#
|
64
|
+
# For AT&T Cloud storage:
|
65
|
+
#
|
66
|
+
# swift = ATT::Swift.new \
|
67
|
+
# 'https://data.iad1.attstorage.com/auth/',
|
68
|
+
# 'tennant:username', 'password'
|
69
|
+
|
70
|
+
def initialize auth_uri, user, key
|
71
|
+
auth_uri = URI auth_uri unless URI === auth_uri
|
72
|
+
|
73
|
+
@auth_uri = auth_uri
|
74
|
+
@user = user
|
75
|
+
@key = key
|
76
|
+
|
77
|
+
@http = Net::HTTP::Persistent.new 'att-swift'
|
78
|
+
|
79
|
+
@storage_uri = nil
|
80
|
+
@auth_token = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Authenticates you with the swift server. This method is called
|
85
|
+
# automatically by other API methods.
|
86
|
+
|
87
|
+
def authenticate
|
88
|
+
return if @auth_token
|
89
|
+
|
90
|
+
res = get @auth_uri + 'v1.0', 'X-Auth-User' => @user, 'X-Auth-Key' => @key
|
91
|
+
|
92
|
+
case res
|
93
|
+
when Net::HTTPSuccess
|
94
|
+
@auth_token = res['X-Auth-Token']
|
95
|
+
|
96
|
+
storage_uri = res['X-Storage-Url']
|
97
|
+
storage_uri << '/' unless storage_uri.end_with? '/'
|
98
|
+
@storage_uri = URI storage_uri
|
99
|
+
|
100
|
+
@http.override_headers['X-Auth-Token'] = @auth_token
|
101
|
+
else
|
102
|
+
raise Error.new(res, 'authentication failed')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Like paginate_objects, but yields chunks of up to +limit+ object
|
108
|
+
# information at a time from the swift server to the given block.
|
109
|
+
# +marker+ may be used to start pagination after a particular entry.
|
110
|
+
#
|
111
|
+
# swift.chunk_objects 'container' do |object_infos|
|
112
|
+
# object_infos.each do |object_info|
|
113
|
+
# # ...
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
|
117
|
+
def chunk_objects container, marker = nil, limit = 1_000
|
118
|
+
return enum_for __method__, container, marker, limit unless block_given?
|
119
|
+
|
120
|
+
loop do
|
121
|
+
chunk = objects container, marker, limit
|
122
|
+
|
123
|
+
break if chunk.empty?
|
124
|
+
|
125
|
+
yield chunk
|
126
|
+
|
127
|
+
marker = chunk.last['name']
|
128
|
+
end
|
129
|
+
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Retrieves the containers for your server. Example output:
|
135
|
+
#
|
136
|
+
# [{"name" => "public_bucket", "count" => 2, "bytes" => 9},
|
137
|
+
# {"name" => "public_bucket_segments", "count" => 22, "bytes" => 21875}]
|
138
|
+
|
139
|
+
def containers marker = nil, limit = nil
|
140
|
+
authenticate
|
141
|
+
|
142
|
+
params = { 'format' => 'json' }
|
143
|
+
params['marker'] = marker if marker
|
144
|
+
params['limit'] = limit if limit
|
145
|
+
|
146
|
+
uri = @storage_uri + "?#{escape_params params}"
|
147
|
+
|
148
|
+
res = get uri
|
149
|
+
|
150
|
+
case res
|
151
|
+
when Net::HTTPSuccess then
|
152
|
+
JSON.parse res.body
|
153
|
+
else
|
154
|
+
raise Error.new(res, 'error listing containers')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Copies +source_object+ in +source_container+ to +destination_object+ in
|
160
|
+
# +destination_container+.
|
161
|
+
#
|
162
|
+
# Returns true if the copy was successful, false if the source object was
|
163
|
+
# not found or the destination container did not exist.
|
164
|
+
|
165
|
+
def copy_object source_container, source_object,
|
166
|
+
destination_container, destination_object
|
167
|
+
authenticate
|
168
|
+
|
169
|
+
source_path = "#{source_container}/#{source_object}"
|
170
|
+
destination_path = "#{destination_container}/#{destination_object}"
|
171
|
+
|
172
|
+
uri = @storage_uri + destination_path
|
173
|
+
|
174
|
+
req = Net::HTTP::Put.new uri.request_uri
|
175
|
+
req['X-Copy-From'] = "/#{source_path}"
|
176
|
+
req.body = ''
|
177
|
+
|
178
|
+
res = @http.request uri, req
|
179
|
+
|
180
|
+
case res
|
181
|
+
when Net::HTTPSuccess then
|
182
|
+
true
|
183
|
+
when Net::HTTPNotFound
|
184
|
+
false
|
185
|
+
else
|
186
|
+
raise
|
187
|
+
Error.new(res, "error copying #{source_path} to #{destination_path}")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# HTTP DELETE
|
193
|
+
|
194
|
+
def delete uri, headers = {} # :nodoc:
|
195
|
+
request Net::HTTP::Delete, uri, headers
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Creates a new +container+. Returns true if the container was created,
|
200
|
+
# false if it already existed.
|
201
|
+
|
202
|
+
def create_container container
|
203
|
+
authenticate
|
204
|
+
|
205
|
+
uri = @storage_uri + container
|
206
|
+
|
207
|
+
res = put uri
|
208
|
+
|
209
|
+
case res
|
210
|
+
when Net::HTTPCreated then
|
211
|
+
true
|
212
|
+
when Net::HTTPSuccess then
|
213
|
+
false
|
214
|
+
else
|
215
|
+
raise Error.new(res, 'error creating container')
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# Deletes +container+. Returns true if the container existed, false if not.
|
221
|
+
# Raises an exception otherwise.
|
222
|
+
|
223
|
+
def delete_container container
|
224
|
+
authenticate
|
225
|
+
|
226
|
+
uri = @storage_uri + container
|
227
|
+
|
228
|
+
res = delete uri
|
229
|
+
|
230
|
+
case res
|
231
|
+
when Net::HTTPNoContent then
|
232
|
+
true
|
233
|
+
when Net::HTTPNotFound then
|
234
|
+
false
|
235
|
+
when Net::HTTPConflict then
|
236
|
+
raise Error.new(res, "container #{container} is not empty")
|
237
|
+
else
|
238
|
+
raise Error.new(res, "error deleting container")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# Deletes +object+ from +container+ Returns true if the object existed, false
|
244
|
+
# if not. Raises an exception otherwise.
|
245
|
+
|
246
|
+
def delete_object container, object
|
247
|
+
authenticate
|
248
|
+
|
249
|
+
uri = @storage_uri + "#{container}/#{object}"
|
250
|
+
|
251
|
+
res = delete uri
|
252
|
+
|
253
|
+
case res
|
254
|
+
when Net::HTTPNoContent then
|
255
|
+
true
|
256
|
+
when Net::HTTPNotFound then
|
257
|
+
false
|
258
|
+
else
|
259
|
+
raise Error.new(res, "error deleting #{object} in #{container}")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def escape_params params # :nodoc:
|
264
|
+
params.map do |k, v|
|
265
|
+
[CGI.escape(k.to_s), CGI.escape(v.to_s)].join '='
|
266
|
+
end.compact.join '&'
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# HTTP GET
|
271
|
+
|
272
|
+
def get uri, headers = {}, &block # :nodoc:
|
273
|
+
request Net::HTTP::Get, uri, headers, &block
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# HTTP HEAD
|
278
|
+
|
279
|
+
def head uri, headers = {} # :nodoc:
|
280
|
+
request Net::HTTP::Head, uri, headers
|
281
|
+
end
|
282
|
+
|
283
|
+
def inspect # :nodoc:
|
284
|
+
"#<%s:0x%x %s %s>" % [
|
285
|
+
self.class,
|
286
|
+
object_id,
|
287
|
+
@auth_uri,
|
288
|
+
@user
|
289
|
+
]
|
290
|
+
end
|
291
|
+
|
292
|
+
##
|
293
|
+
# Lists objects in +container+. +marker+ lists objects after the given
|
294
|
+
# name, +limit+ restricts the object listing to that many items.
|
295
|
+
#
|
296
|
+
# Example output:
|
297
|
+
#
|
298
|
+
# [{"name"=>"test-0",
|
299
|
+
# "hash"=>"b1946ac92492d2347c6235b4d2611184",
|
300
|
+
# "bytes"=>6,
|
301
|
+
# "content_type"=>"application/octet-stream",
|
302
|
+
# "last_modified"=>"2012-08-22T19:24:03.102100"},
|
303
|
+
# {"name"=>"test-1",
|
304
|
+
# "hash"=>"802eeebb01b647913806a870cbb5394a",
|
305
|
+
# "bytes"=>52707,
|
306
|
+
# "content_type"=>"application/octet-stream",
|
307
|
+
# "last_modified"=>"2012-08-22T19:32:24.474980"},
|
308
|
+
# {"name"=>"test-2",
|
309
|
+
# "hash"=>"90affbd9a1954ec9ff029b7ad7183a16",
|
310
|
+
# "bytes"=>5,
|
311
|
+
# "content_type"=>"application/octet-stream",
|
312
|
+
# "last_modified"=>"2012-08-22T21:10:05.258080"}]
|
313
|
+
|
314
|
+
def objects container, marker = nil, limit = nil
|
315
|
+
authenticate
|
316
|
+
|
317
|
+
params = { 'format' => 'json' }
|
318
|
+
params['marker'] = marker if marker
|
319
|
+
params['limit'] = limit if limit
|
320
|
+
|
321
|
+
uri = @storage_uri + "#{container}?#{escape_params params}"
|
322
|
+
|
323
|
+
res = get uri
|
324
|
+
|
325
|
+
case res
|
326
|
+
when Net::HTTPSuccess then
|
327
|
+
JSON.parse res.body
|
328
|
+
else
|
329
|
+
raise Error.new(res, "error retrieving object list in #{container}")
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
# Retrieves information for +object+ in +container+ including metadata.
|
335
|
+
# Example result:
|
336
|
+
#
|
337
|
+
# {
|
338
|
+
# 'metadata' => {
|
339
|
+
# 'meat' => 'Bacon',
|
340
|
+
# },
|
341
|
+
#
|
342
|
+
# 'content-length' => '3072',
|
343
|
+
# 'content-type' => 'application/octet-stream',
|
344
|
+
# 'etag' => 'a2a5648d09a83c6a85ddd62e4a22309c',
|
345
|
+
# 'last-modified' => 'Wed, 22 Aug 2012 22:51:28 GMT',
|
346
|
+
# }
|
347
|
+
|
348
|
+
def object_info container, object
|
349
|
+
authenticate
|
350
|
+
|
351
|
+
uri = @storage_uri + "#{container}/#{object}"
|
352
|
+
|
353
|
+
res = head uri
|
354
|
+
|
355
|
+
case res
|
356
|
+
when Net::HTTPSuccess then
|
357
|
+
metadata = {}
|
358
|
+
info = { 'metadata' => metadata }
|
359
|
+
|
360
|
+
res.each do |name, value|
|
361
|
+
case name
|
362
|
+
when /^accept/i, 'connection', 'date' then
|
363
|
+
next
|
364
|
+
when /^x-object-meta-(.*)/ then
|
365
|
+
metadata[$1] = value
|
366
|
+
when /^x-/ then
|
367
|
+
next
|
368
|
+
else
|
369
|
+
info[name] = value
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
info
|
374
|
+
when Net::HTTPNotFound then
|
375
|
+
nil
|
376
|
+
else
|
377
|
+
raise Error.new(res,
|
378
|
+
"error retrieving metadata for #{object} in #{container}")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
##
|
383
|
+
# Retrieves metadata for +object+ in +container+. See also
|
384
|
+
# object_info. Example result:
|
385
|
+
#
|
386
|
+
# 'metadata' => {
|
387
|
+
# 'meat' => 'Bacon',
|
388
|
+
# }
|
389
|
+
|
390
|
+
def object_metadata container, object
|
391
|
+
info = object_info container, object
|
392
|
+
|
393
|
+
return unless info
|
394
|
+
|
395
|
+
info['metadata']
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
# Like #objects, but only retrieves +limit+ objects at a time from the
|
400
|
+
# swift server and yields the object information to the given block.
|
401
|
+
# +marker+ may be used to start pagination after a particular entry.
|
402
|
+
#
|
403
|
+
# swift.paginate_objects 'container' do |object_info|
|
404
|
+
# p object_info.name
|
405
|
+
# end
|
406
|
+
|
407
|
+
def paginate_objects container, marker = nil, limit = 1_000
|
408
|
+
return enum_for __method__, container, marker, limit unless block_given?
|
409
|
+
|
410
|
+
chunk_objects container, marker, limit do |chunk|
|
411
|
+
chunk.each do |object_info|
|
412
|
+
yield object_info
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
self
|
417
|
+
end
|
418
|
+
|
419
|
+
##
|
420
|
+
# HTTP POST
|
421
|
+
|
422
|
+
def post uri, headers = {} # :nodoc:
|
423
|
+
request Net::HTTP::Post, uri, headers
|
424
|
+
end
|
425
|
+
|
426
|
+
##
|
427
|
+
# HTTP PUT
|
428
|
+
|
429
|
+
def put uri, headers = {} # :nodoc:
|
430
|
+
request Net::HTTP::Put, uri, headers
|
431
|
+
end
|
432
|
+
|
433
|
+
##
|
434
|
+
# Reads +object+ from +container+. Unless the response is HTTP 200 OK an
|
435
|
+
# exception is raised.
|
436
|
+
#
|
437
|
+
# If no block is given the response body is read, checked for a matching MD5
|
438
|
+
# checksum and returned.
|
439
|
+
#
|
440
|
+
# If a block is given the response is yielded for custom handling and no MD5
|
441
|
+
# checksum is calculated.
|
442
|
+
#
|
443
|
+
# Example:
|
444
|
+
#
|
445
|
+
# swift.read_object 'test-container', 'test-object' do |res|
|
446
|
+
# open destination, 'wb' do |io|
|
447
|
+
# res.read_body do |chunk|
|
448
|
+
# io.write chunk
|
449
|
+
# end
|
450
|
+
# end
|
451
|
+
# end
|
452
|
+
|
453
|
+
def read_object container, object
|
454
|
+
authenticate
|
455
|
+
|
456
|
+
uri = @storage_uri + "#{container}/#{object}"
|
457
|
+
|
458
|
+
get uri do |res|
|
459
|
+
case res
|
460
|
+
when Net::HTTPOK then
|
461
|
+
if block_given? then
|
462
|
+
yield res
|
463
|
+
else
|
464
|
+
body = res.body
|
465
|
+
|
466
|
+
if res['ETag'] != Digest::MD5.hexdigest(body) then
|
467
|
+
raise Error.new(res,
|
468
|
+
"checksum retrieving object #{object} in #{container}")
|
469
|
+
end
|
470
|
+
|
471
|
+
body
|
472
|
+
end
|
473
|
+
when Net::HTTPNotFound then
|
474
|
+
raise Error.new(res, "object #{object} in #{container} not found")
|
475
|
+
else
|
476
|
+
raise Error.new(res, "error retrieving object #{object} in #{container}")
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
##
|
482
|
+
# net-http-persistent request wrapper
|
483
|
+
|
484
|
+
def request klass, uri, headers, &block # :nodoc:
|
485
|
+
path = klass::REQUEST_HAS_BODY ? uri.path : uri.request_uri
|
486
|
+
|
487
|
+
req = klass.new path, headers
|
488
|
+
|
489
|
+
@http.request uri, req, &block
|
490
|
+
end
|
491
|
+
|
492
|
+
##
|
493
|
+
# Sets the metadata for +object+ in +container+. Existing metadata will be
|
494
|
+
# overwritten.
|
495
|
+
|
496
|
+
def set_object_metadata container, object, metadata = {}
|
497
|
+
authenticate
|
498
|
+
|
499
|
+
uri = @storage_uri + "#{container}/#{object}"
|
500
|
+
|
501
|
+
headers = {}
|
502
|
+
|
503
|
+
metadata.each do |name, value|
|
504
|
+
headers["X-Object-Meta-#{name}"] = value
|
505
|
+
end
|
506
|
+
|
507
|
+
res = post uri, headers
|
508
|
+
|
509
|
+
case res
|
510
|
+
when Net::HTTPSuccess then
|
511
|
+
true
|
512
|
+
else
|
513
|
+
raise Error.new(res,
|
514
|
+
"error setting metadata on #{object} in #{container}")
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
##
|
519
|
+
# Writes to +object+ in +container+. +content+ may be a String or IO-like
|
520
|
+
# object. If +content+ is a String the response ETag is checked against the
|
521
|
+
# MD5 hash of the local copy.
|
522
|
+
#
|
523
|
+
# A block may be given instead of +content+. You will be given a pipe you
|
524
|
+
# can write the content to:
|
525
|
+
#
|
526
|
+
# r = Random.new
|
527
|
+
#
|
528
|
+
# swift.write_object 'test-container', 'test-object' do |io|
|
529
|
+
# r.rand(10).times do
|
530
|
+
# io.write r.bytes r.rand 10_000
|
531
|
+
# end
|
532
|
+
# end
|
533
|
+
#
|
534
|
+
# In all cases the response ETag is returned when the content was
|
535
|
+
# successfully uploaded.
|
536
|
+
|
537
|
+
def write_object container, object, content = nil
|
538
|
+
raise ArgumentError, 'provide block or content' if
|
539
|
+
content and block_given?
|
540
|
+
|
541
|
+
if block_given? then
|
542
|
+
content, write = IO.pipe
|
543
|
+
|
544
|
+
Thread.start do
|
545
|
+
begin
|
546
|
+
yield write
|
547
|
+
ensure
|
548
|
+
write.close
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
authenticate
|
554
|
+
|
555
|
+
uri = @storage_uri + "#{container}/#{object}"
|
556
|
+
|
557
|
+
req = Net::HTTP::Put.new uri.path
|
558
|
+
|
559
|
+
case content
|
560
|
+
when String then
|
561
|
+
req.body = content
|
562
|
+
req['ETag'] = Digest::MD5.hexdigest content
|
563
|
+
else
|
564
|
+
req['Transfer-Encoding'] = 'chunked'
|
565
|
+
req.body_stream = content
|
566
|
+
end
|
567
|
+
|
568
|
+
req.content_type = 'application/octet-stream'
|
569
|
+
|
570
|
+
res = @http.request uri, req
|
571
|
+
|
572
|
+
case res
|
573
|
+
when Net::HTTPCreated then
|
574
|
+
res['ETag']
|
575
|
+
else
|
576
|
+
raise Error.new(res,
|
577
|
+
"error creating object #{object} in container #{container}")
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
end
|
582
|
+
|