ruby-atmos-pure 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +8 -0
- data/README +129 -0
- data/Rakefile +103 -0
- data/lib/atmos.rb +35 -0
- data/lib/atmos/attributes.rb +545 -0
- data/lib/atmos/exceptions.rb +28 -0
- data/lib/atmos/object.rb +321 -0
- data/lib/atmos/parser.rb +110 -0
- data/lib/atmos/parser/rexml.rb +1 -0
- data/lib/atmos/request.rb +221 -0
- data/lib/atmos/response.rb +116 -0
- data/lib/atmos/rest.rb +145 -0
- data/lib/atmos/store.rb +220 -0
- data/lib/atmos/util.rb +40 -0
- data/lib/atmos/version.rb +12 -0
- data/test/credentials.rb +141 -0
- data/test/esutest.rb +499 -0
- data/test/files/SmallImageForTest.iso +0 -0
- data/test/files/dragaf-tiny-from-vsphere.ova +0 -0
- data/test/files/something.txt +1 -0
- data/test/require_test.rb +2 -0
- data/test/suite.rb +11 -0
- data/test/suite_noproxy.rb +12 -0
- data/test/suite_proxy.rb +14 -0
- data/test/test_acl.rb +283 -0
- data/test/test_metadata.rb +162 -0
- data/test/test_object_create.rb +167 -0
- data/test/test_object_get.rb +55 -0
- data/test/test_object_misc.rb +80 -0
- data/test/test_object_read.rb +98 -0
- data/test/test_object_update.rb +59 -0
- data/test/test_request.rb +49 -0
- data/test/test_store.rb +125 -0
- data/test/test_util.rb +58 -0
- metadata +155 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Atmos
|
2
|
+
module Exceptions
|
3
|
+
|
4
|
+
class AtmosException < Exception
|
5
|
+
end
|
6
|
+
|
7
|
+
class ArgumentException < AtmosException
|
8
|
+
end
|
9
|
+
|
10
|
+
class AuthException < AtmosException
|
11
|
+
end
|
12
|
+
|
13
|
+
class InvalidStateException < AtmosException
|
14
|
+
end
|
15
|
+
|
16
|
+
class NoSuchObjectException < AtmosException
|
17
|
+
end
|
18
|
+
|
19
|
+
class ServerException < AtmosException
|
20
|
+
end
|
21
|
+
|
22
|
+
class NotImplementedException < AtmosException
|
23
|
+
end
|
24
|
+
|
25
|
+
class InternalLibraryException < AtmosException
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/atmos/object.rb
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
module Atmos
|
2
|
+
|
3
|
+
#
|
4
|
+
# == Object
|
5
|
+
# This class represents an object in an Atmos store.
|
6
|
+
#
|
7
|
+
# === Object Data
|
8
|
+
# When an object is instantiated, it's data is not loaded
|
9
|
+
# due to memory considerations. You can access an object's
|
10
|
+
# data as a ruby String or as a progressive download via a block.
|
11
|
+
#
|
12
|
+
# ==== all at once
|
13
|
+
#
|
14
|
+
# obj.data
|
15
|
+
#
|
16
|
+
# ==== progressive download
|
17
|
+
#
|
18
|
+
# obj.data_as_stream do |chunk|
|
19
|
+
# datafile.write(chunk)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# ==== partial data
|
23
|
+
#
|
24
|
+
# obj.data_as_stream(0...59) do |chunk|
|
25
|
+
# readin.concat(chunk)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# === System Metadata
|
30
|
+
# Each object has some information about it stored by the system.
|
31
|
+
# This information is read only, and available as a hash on the object:
|
32
|
+
#
|
33
|
+
# obj.system_metadata => Hash
|
34
|
+
#
|
35
|
+
# obj.system_metadata.each do |key,value|
|
36
|
+
# puts "#{key}=#{value}"
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# See Atmos::Metadata for more detailed information.
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# === User Metadata
|
43
|
+
# There are two kinds of user metadata, listable and non-listable.
|
44
|
+
# Each of these is available as a hash
|
45
|
+
# on the object class. These can both be modified.
|
46
|
+
#
|
47
|
+
# obj.listable_metadata => Hash
|
48
|
+
#
|
49
|
+
# obj.listable_metadata.each do |key,value|
|
50
|
+
# puts "#{key}=#{value}"
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# obj.metadata => Hash
|
54
|
+
#
|
55
|
+
# obj.metadata.each do |key,value|
|
56
|
+
# puts "#{key}=#{value}"
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# See Atmos::Metadata for more detailed information.
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# === Access Control Lists (ACLs)
|
63
|
+
#
|
64
|
+
# There are two hashes for access control available as properties
|
65
|
+
# on the object: +user_acl+ and +group_acl+.
|
66
|
+
# The keys are the Atmos usernames and the values are one of
|
67
|
+
# <tt>:none</tt>, <tt>:read</tt>, <tt>:write</tt>, <tt>:full</tt>.
|
68
|
+
#
|
69
|
+
# puts obj.user_acl.inspect => {user => :full}
|
70
|
+
# puts obj.group_acl.inspect => {other => :none}
|
71
|
+
#
|
72
|
+
# See Atmos::ACL for more detailed information.
|
73
|
+
#
|
74
|
+
#
|
75
|
+
class Object
|
76
|
+
attr_reader :aoid, :request, :user # :nodoc:
|
77
|
+
|
78
|
+
@request = nil
|
79
|
+
@checksum = nil
|
80
|
+
@user_acl = nil
|
81
|
+
@group_acl = nil
|
82
|
+
@metadata = nil
|
83
|
+
@system_metadata = nil
|
84
|
+
@listable_metadata = nil
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# This constructor is only meant for internal use. Get or create an object
|
89
|
+
# with an Atmos::Store object:
|
90
|
+
#
|
91
|
+
# obj = store.create
|
92
|
+
# obj = store.get(:id => obj_id)
|
93
|
+
# obj = store.get(:namespace => obj_id)
|
94
|
+
#
|
95
|
+
def initialize(store, action, options = {})
|
96
|
+
Atmos::LOG.debug("obj.new options: #{options.inspect}")
|
97
|
+
validate_options(options)
|
98
|
+
|
99
|
+
@deleted = false
|
100
|
+
@aoid = aoid
|
101
|
+
@store = store
|
102
|
+
@request = Atmos::Request.new(:store => @store)
|
103
|
+
@user = @store.user
|
104
|
+
|
105
|
+
if (action == :create)
|
106
|
+
|
107
|
+
if (options[:id])
|
108
|
+
raise Atmos::Exceptions::ArgumentException, "You can't specify an id on object creation."
|
109
|
+
end
|
110
|
+
|
111
|
+
Atmos::LOG.debug("Object.new: creating new object")
|
112
|
+
response = @request.do(:create_object, options)
|
113
|
+
@aoid = response.id
|
114
|
+
|
115
|
+
elsif (action == :get)
|
116
|
+
|
117
|
+
Atmos::LOG.debug("Retrieving object: id: #{options[:id]}; namespace: #{options[:namespace]}")
|
118
|
+
response = @request.do(:get_object_info, options)
|
119
|
+
@aoid = Atmos::Parser::response_get_string(response.http_response, '//xmlns:objectId')
|
120
|
+
Atmos::LOG.debug("Retrieved object id for namespace: #{@aoid}")
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Lazy evaluation of hash-like object containing user access control properties of the object.
|
127
|
+
#
|
128
|
+
def user_acl
|
129
|
+
#Atmos::LOG.warn("user_acl: #{@user_acl.inspect}")
|
130
|
+
if (@user_acl.nil?)
|
131
|
+
@user_acl = Atmos::ACL.new(self, Atmos::ACL::USER)
|
132
|
+
#Atmos::LOG.warn("user_acl: #{@user_acl.inspect}")
|
133
|
+
end
|
134
|
+
|
135
|
+
@user_acl
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
#
|
140
|
+
# Lazy evaluation of hash-like object containing group access control properties of the object.
|
141
|
+
#
|
142
|
+
def group_acl
|
143
|
+
if (@group_acl.nil?)
|
144
|
+
@group_acl = Atmos::ACL.new(self, Atmos::ACL::GROUP)
|
145
|
+
end
|
146
|
+
|
147
|
+
@group_acl
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
#
|
152
|
+
# Lazy evaluation of hash-like object containing non-listable properties of the object.
|
153
|
+
#
|
154
|
+
def metadata
|
155
|
+
if (@metadata.nil?)
|
156
|
+
@metadata = Atmos::Metadata.new(self, Atmos::Metadata::NON_LISTABLE)
|
157
|
+
end
|
158
|
+
|
159
|
+
@metadata
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
#
|
164
|
+
# Lazy evaluation of hash-like object containing listable metadata properties of the object.
|
165
|
+
#
|
166
|
+
def listable_metadata
|
167
|
+
if (@listable_metadata.nil?)
|
168
|
+
@listable_metadata = Atmos::Metadata.new(self, Atmos::Metadata::LISTABLE)
|
169
|
+
end
|
170
|
+
|
171
|
+
@listable_metadata
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
#
|
176
|
+
# Lazy evaluation of Hash-like object containing read-only system metadata associated with the object.
|
177
|
+
#
|
178
|
+
def system_metadata
|
179
|
+
if (@system_metadata.nil?)
|
180
|
+
@system_metadata = Atmos::Metadata.new(self, Atmos::Metadata::SYSTEM)
|
181
|
+
end
|
182
|
+
|
183
|
+
@system_metadata
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
#
|
188
|
+
# Truncates the object to size 0 without changing any of the Metadata or ACLs.
|
189
|
+
#
|
190
|
+
def truncate
|
191
|
+
do_delete_check
|
192
|
+
response = @request.do(:trunc_object, :id => @aoid, :data => nil, :length => 0)
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# Deletes the object from Atmos and invalidates the object.
|
197
|
+
#
|
198
|
+
# obj = store.create
|
199
|
+
# obj.delete
|
200
|
+
#
|
201
|
+
def delete
|
202
|
+
do_delete_check
|
203
|
+
response = @request.do(:delete_object, :id => @aoid)
|
204
|
+
@deleted = true
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
#
|
209
|
+
# Returns all the object data in a single string. Be judicious about
|
210
|
+
# use of this method, since it can load the entire blob into memory.
|
211
|
+
#
|
212
|
+
# Optional:
|
213
|
+
# * <tt>:range</tt> - range of bytes to retrieve (e.g. 0...10000)
|
214
|
+
#
|
215
|
+
def data(range = nil)
|
216
|
+
do_delete_check
|
217
|
+
response = @request.do(:read_object, :id => @aoid, 'Range' => range)
|
218
|
+
response.http_response.body
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
#
|
223
|
+
# Allows progressive download of the object's data. Takes a block:
|
224
|
+
#
|
225
|
+
# obj.data_as_stream do |chunk|
|
226
|
+
# datafile.write(chunk)
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# Optional:
|
230
|
+
# * <tt>:range</tt> - range of bytes to retrieve (e.g. 0...10000)
|
231
|
+
#
|
232
|
+
def data_as_stream(range = nil, &block)
|
233
|
+
do_delete_check
|
234
|
+
@request.do(:read_object, :id => @aoid, 'Range' => range) do |response|
|
235
|
+
response.read_body do |chunk|
|
236
|
+
block.call(chunk)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def update(data, range=nil)
|
243
|
+
response = @request.do(:update_object, :id => @aoid, :data => data, 'Range' => range)
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
#
|
248
|
+
# Checks to see if the represented object exists on Atmos
|
249
|
+
# by requesting it's system metadata.
|
250
|
+
#
|
251
|
+
# Returns boolean +true+ or +false+.
|
252
|
+
#
|
253
|
+
def exists?
|
254
|
+
rv = true
|
255
|
+
begin
|
256
|
+
@request.do(:list_system_metadata, :id => @aoid)
|
257
|
+
rescue Atmos::Exceptions::NoSuchObjectException
|
258
|
+
rv = false
|
259
|
+
end
|
260
|
+
rv
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
def copy #:nodoc:
|
265
|
+
do_delete_check
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
def headers #:nodoc:
|
270
|
+
do_delete_check
|
271
|
+
headers = {}
|
272
|
+
[@user_acl, @group_acl, @tags, @listable_tags, @metadata, @listable_metadata].each do |attr|
|
273
|
+
val = attr.header_value
|
274
|
+
next if (val.nil? || val.empty?)
|
275
|
+
headers[attr.header_name] = attr.header_value
|
276
|
+
end
|
277
|
+
headers
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
private
|
282
|
+
def do_delete_check
|
283
|
+
raise Atmos::Exceptions::InvalidStateException if (@deleted)
|
284
|
+
end
|
285
|
+
|
286
|
+
def validate_options(options)
|
287
|
+
invalid_values = []
|
288
|
+
|
289
|
+
valid_options = [:checksum, :user_acl, :group_acl, :metadata, :listable_metadata, :mimetype, :length, :data, :namespace, :id].freeze
|
290
|
+
invalid_options = options.keys - valid_options
|
291
|
+
raise Atmos::Exceptions::ArgumentException, "Unrecognized options: #{invalid_options.inspect}" if (!invalid_options.empty?)
|
292
|
+
|
293
|
+
options.each do |k,v|
|
294
|
+
case k
|
295
|
+
when :checksum
|
296
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Boolean))
|
297
|
+
when :user_acl
|
298
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
|
299
|
+
when :group_acl
|
300
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
|
301
|
+
when :metadata
|
302
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
|
303
|
+
when :listable_metadata
|
304
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Hash))
|
305
|
+
when :mimetype
|
306
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(String))
|
307
|
+
when :namespace
|
308
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(String))
|
309
|
+
when :length
|
310
|
+
invalid_values.push(k) if (options[k].nil? || !options[k].kind_of?(Integer))
|
311
|
+
when :data
|
312
|
+
invalid_values.push(k) if (options[k].nil? || (!options[k].kind_of?(String) && !options[k].class.ancestors.include?(IO)))
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
raise Atmos::Exceptions::ArgumentException, "Options of invalid type: #{invalid_values.inspect}" if (!invalid_values.empty?)
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
data/lib/atmos/parser.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Atmos
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
NOKOGIRI = "nokogiri"
|
5
|
+
REXML = "rexml"
|
6
|
+
@@parser = nil
|
7
|
+
|
8
|
+
|
9
|
+
def self.parser
|
10
|
+
@@parser
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def self.parser=(which)
|
15
|
+
|
16
|
+
@@parser = which
|
17
|
+
if (@@parser == NOKOGIRI)
|
18
|
+
require 'nokogiri'
|
19
|
+
elsif (@@parser == REXML)
|
20
|
+
require 'rexml/document'
|
21
|
+
else
|
22
|
+
raise Atmos::Exceptions::ArgumentException, "The XML parser has not been set to a known parser."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def self.response_get_array(response, string)
|
28
|
+
rv = nil
|
29
|
+
|
30
|
+
if (parser == NOKOGIRI)
|
31
|
+
|
32
|
+
doc = Nokogiri::XML(response.body)
|
33
|
+
rv = doc.xpath(string).map do |elt|
|
34
|
+
elt.content
|
35
|
+
end
|
36
|
+
|
37
|
+
elsif (parser == REXML)
|
38
|
+
|
39
|
+
doc = ::REXML::Document.new(response.body)
|
40
|
+
rv = doc.elements.to_a(string).map do |elt|
|
41
|
+
elt.text
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
rv
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def self.response_get_string(response, string)
|
51
|
+
response_get_array(response,string)[0]
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
#
|
56
|
+
# Utility method to check the atmos server XML response for errors.
|
57
|
+
# Throws exception on error.
|
58
|
+
#
|
59
|
+
def self.response_check_action_error(action, response)
|
60
|
+
doc = nil
|
61
|
+
code = nil
|
62
|
+
msg = nil
|
63
|
+
exclass = Atmos::Exceptions::AtmosException
|
64
|
+
|
65
|
+
Atmos::LOG.info("body: #{response.body}")
|
66
|
+
if (!response.body.nil? && response.body.match(/<\?xml/))
|
67
|
+
if (parser == NOKOGIRI)
|
68
|
+
|
69
|
+
doc = Nokogiri::XML(response.body)
|
70
|
+
|
71
|
+
code = doc.xpath('//Error/Code')[0]
|
72
|
+
code = code.content if (!code.nil?)
|
73
|
+
|
74
|
+
msg = doc.xpath('//Error/Message')[0]
|
75
|
+
msg = msg.content if (!msg.nil?)
|
76
|
+
|
77
|
+
elsif (parser == REXML)
|
78
|
+
|
79
|
+
code = response_get_string(response, '//Error/Code')
|
80
|
+
msg = response_get_string(response, '//Error/Message')
|
81
|
+
|
82
|
+
else
|
83
|
+
raise Atmos::Exceptions::InvalidStateException, "The XML parser has not been set to a known parser."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
if (!code.nil?)
|
88
|
+
|
89
|
+
Atmos::LOG.info("code: #{code}")
|
90
|
+
Atmos::LOG.info("msg: #{msg}")
|
91
|
+
|
92
|
+
if (!REST[action][:errors].nil? && !REST[action][:errors][code].nil?)
|
93
|
+
tmp = REST[action][:errors][code][:message]
|
94
|
+
msg = tmp if (!tmp.nil?)
|
95
|
+
|
96
|
+
tmp = REST[action][:errors][code][:class]
|
97
|
+
exclass = tmp if (!tmp.nil?)
|
98
|
+
end
|
99
|
+
|
100
|
+
raise exclass, msg
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
true
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|