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