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 @@
|
|
1
|
+
# This does nothing except signal that the chosen XML parser to use is REXML
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module Atmos
|
2
|
+
class Request # :nodoc:
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
valid = [:store, :default_tag].freeze
|
6
|
+
invalid = options.keys - valid
|
7
|
+
raise Atmos::Exceptions::ArgumentException,
|
8
|
+
"Unrecognized options: #{invalid.inspect}" if (!invalid.empty?)
|
9
|
+
Atmos::LOG.debug("Request.initialize: options: #{options.inspect}")
|
10
|
+
|
11
|
+
@baseurl = options[:store].uri
|
12
|
+
@uid = options[:store].uid
|
13
|
+
@secret = options[:store].secret
|
14
|
+
@http = options[:store].http
|
15
|
+
@tag = options[:default_tag]
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
#
|
20
|
+
# If there is no namespace option provided, we're using the object interface,
|
21
|
+
# otherwise we're using the namespace interface. Determine which it is, so we know
|
22
|
+
# which URI to use.
|
23
|
+
#
|
24
|
+
# If aoid is nil, we're creating a new object regardless.
|
25
|
+
#
|
26
|
+
def get_uri(action, options)
|
27
|
+
|
28
|
+
if (options.has_key?(:namespace) && action.has_key?(:namespace_uri))
|
29
|
+
|
30
|
+
# only two actions need namespace, so if the action doesn't have the
|
31
|
+
# :namespace_url key, we use the regular :object_url.
|
32
|
+
ns = (options[:namespace].start_with?('/')) ? options[:namespace][1..-1] : options[:namespace]
|
33
|
+
uri = action[:namespace_uri].sub(/:namespace/, ns)
|
34
|
+
|
35
|
+
else
|
36
|
+
|
37
|
+
uri = action[:object_uri]
|
38
|
+
|
39
|
+
# only need the following check/substution if the uri requires an id
|
40
|
+
if (!uri.index(':id').nil?)
|
41
|
+
|
42
|
+
if (options.has_key?(:id) && !options[:id].nil?)
|
43
|
+
uri = uri.sub(/:id/, options[:id])
|
44
|
+
else
|
45
|
+
raise Atmos::Exceptions::ArgumentException, "An id is required for this action. This is an internal error."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Atmos::LOG.info("uri: #{uri}")
|
51
|
+
uri
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
def do(actionname, options = {}, &block)
|
57
|
+
verbs = [:head, :get, :put, :post, :delete].freeze
|
58
|
+
Atmos::LOG.info("do #{actionname} options: #{options.inspect}")
|
59
|
+
raise Atmos::Exceptions::InternalLibraryException,
|
60
|
+
"Invalid REST action: #{actionname}" if (REST[actionname].nil?)
|
61
|
+
|
62
|
+
action = REST[actionname]
|
63
|
+
uri = get_uri(action, options)
|
64
|
+
url = URI::join(@baseurl.to_s, uri.to_s)
|
65
|
+
verb = action[:verb]
|
66
|
+
|
67
|
+
request = (verbs.include?(verb)) ? Net::HTTP.const_get(verb.to_s.capitalize).new(uri) : nil
|
68
|
+
raise Atmos::Exceptions::AtmosException,
|
69
|
+
"Couldn't create Net::HTTP request object for #{verb}" if (request.nil?)
|
70
|
+
|
71
|
+
headers = {}
|
72
|
+
headers['Date'] = Time.now().httpdate()
|
73
|
+
headers['x-emc-date'] = Time.now().httpdate()
|
74
|
+
headers['x-emc-uid'] = @uid
|
75
|
+
|
76
|
+
if (!action[:required_headers].nil?)
|
77
|
+
action[:required_headers].each do |header|
|
78
|
+
case header
|
79
|
+
when 'Content-Type' then
|
80
|
+
headers[header] = (!options[:mimetype].nil?) ? options[:mimetype] : 'binary/octet-stream'
|
81
|
+
when 'Content-Length' then
|
82
|
+
headers[header] = (!options[:length].nil?) ? options[:length] : 0
|
83
|
+
when 'x-emc-tags' then
|
84
|
+
headers[header] = (!options[header].nil?) ? options[header] : ""
|
85
|
+
else
|
86
|
+
if (options[header])
|
87
|
+
headers[header] = options[header]
|
88
|
+
else
|
89
|
+
raise "Value not supplied for required header: '#{header}'"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if (!action[:optional_headers].nil?)
|
96
|
+
action[:optional_headers].each do |header|
|
97
|
+
if (header.eql?('Range') && options.has_key?('Range') && !options['Range'].nil?)
|
98
|
+
r = options['Range']
|
99
|
+
options.delete('Range')
|
100
|
+
#headers[header] = (r.kind_of?(Range)) ? "Bytes=#{r.first}-#{r.last}" : "Bytes=#{r.to_s}"
|
101
|
+
if (r.kind_of?(Range))
|
102
|
+
headers[header] = "Bytes=#{r.first}-#{r.last}"
|
103
|
+
Atmos::LOG.info("Request.initialize: given Range object: #{headers[header]}")
|
104
|
+
else
|
105
|
+
Atmos::LOG.info("Request.initialize: given range string: #{headers[header]}")
|
106
|
+
headers[header] = "Bytes=#{r.to_s}"
|
107
|
+
end
|
108
|
+
if ((!options[:data].nil?) and ((r.end-r.begin+1) != options[:data].length))
|
109
|
+
raise Atmos::Exceptions::ArgumentException, "The range length (#{r.end - r.begin + 1}) doesn't match the data length (#{options[:data].length})."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
key = (action[:header2sym] && action[:header2sym][header]) ? action[:header2sym][header] : header
|
113
|
+
|
114
|
+
if (!options[key].nil?)
|
115
|
+
if (options[key].kind_of?(Hash))
|
116
|
+
headers[header] = ""
|
117
|
+
options[key].each do |key,val|
|
118
|
+
headers[header] += "#{key}=#{val}, "
|
119
|
+
end
|
120
|
+
headers[header].chop!
|
121
|
+
headers[header].chop!
|
122
|
+
elsif (options[key].kind_of?(Array))
|
123
|
+
headers[header] = ""
|
124
|
+
options[key].each do |val|
|
125
|
+
headers[header] += "#{val}, "
|
126
|
+
end
|
127
|
+
headers[header].chop!
|
128
|
+
headers[header].chop!
|
129
|
+
elsif (options[key].respond_to?(:to_s))
|
130
|
+
headers[header] = options[key]
|
131
|
+
else
|
132
|
+
raise "value for optional header '#{header}' id bad type: #{options[header].class}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
if (!@tag.nil? && !action[:optional_headers].nil? && action[:optional_headers].include?('x-emc-listable-meta'))
|
139
|
+
if (headers.has_key?('x-emc-listable-meta'))
|
140
|
+
headers['x-emc-listable-meta'] += ", #{@tag}=atmos-ruby-default"
|
141
|
+
else
|
142
|
+
headers['x-emc-listable-meta'] = "#{@tag}=atmos-ruby-default"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
headers['x-emc-signature'] = calculate_signature(action[:verb], @secret, url, headers)
|
147
|
+
|
148
|
+
if (options[:data])
|
149
|
+
Atmos::LOG.info("there is data: [#{options[:data]}]")
|
150
|
+
if (options[:data].respond_to?(:read))
|
151
|
+
request.body_stream = options[:data]
|
152
|
+
else
|
153
|
+
request.body = options[:data]
|
154
|
+
end
|
155
|
+
headers['Content-Length'] =
|
156
|
+
options[:data].respond_to?(:lstat) ? options[:data].stat.size : options[:data].size
|
157
|
+
Atmos::LOG.info("set Content-Length to: #{headers['Content-Length']}\n")
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
headers.each do |key,val|
|
162
|
+
request[key] = val
|
163
|
+
end
|
164
|
+
|
165
|
+
@http.set_debug_output($stdout) if (Atmos::LOG.level == Log4r::DEBUG)
|
166
|
+
|
167
|
+
|
168
|
+
#
|
169
|
+
# so this is weird. because some of these atmos request may be
|
170
|
+
# insanely long, we need to allow a mechanism for our API layer
|
171
|
+
# to read progressively, not only all at once
|
172
|
+
#
|
173
|
+
if (block)
|
174
|
+
@http.request(request) do |response|
|
175
|
+
block.call(response)
|
176
|
+
end
|
177
|
+
return
|
178
|
+
end
|
179
|
+
|
180
|
+
response = @http.request(request)
|
181
|
+
Atmos::Parser::response_check_action_error(actionname, response)
|
182
|
+
|
183
|
+
Atmos::Response.new(response, actionname)
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
def calculate_signature(verb, secret, url, headers)
|
188
|
+
headers_copy = headers.clone
|
189
|
+
|
190
|
+
# normalize emc headers
|
191
|
+
headers_copy.each do |header, value|
|
192
|
+
if (header.start_with?('x-emc-'))
|
193
|
+
headers.delete(header)
|
194
|
+
headers[header.to_s.strip.downcase] = value.to_s.strip.sub(/\s+/, ' ').sub(/\n/, '')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# string together all emc headers, no newline at end
|
199
|
+
emc = ""
|
200
|
+
headers.keys.sort.each do |header|
|
201
|
+
if (header.start_with?('x-emc-'))
|
202
|
+
emc += "#{header}:#{headers[header]}\n"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
emc.chomp!
|
206
|
+
|
207
|
+
hashstring = verb.to_s.upcase+"\n"
|
208
|
+
['Content-Type', 'Range', 'Date'].each do |key|
|
209
|
+
hashstring += headers[key] if (!headers[key].nil?)
|
210
|
+
hashstring += "\n"
|
211
|
+
end
|
212
|
+
|
213
|
+
hashstring += url.path
|
214
|
+
hashstring += '?'+url.query if (!url.query.nil?)
|
215
|
+
hashstring += "\n"+ emc
|
216
|
+
|
217
|
+
Atmos::LOG.debug("calculate_signature: hashstring: #{hashstring}")
|
218
|
+
Base64.encode64(HMAC::SHA1.digest(Base64.decode64(secret), hashstring)).chomp()
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Atmos
|
2
|
+
class Response # :nodoc:
|
3
|
+
attr_reader :http_response
|
4
|
+
|
5
|
+
def initialize(response, action)
|
6
|
+
@http_response = response
|
7
|
+
@action = action
|
8
|
+
|
9
|
+
if (response.kind_of?(Net::HTTPServerError))
|
10
|
+
raise Atmos::Exceptions::ServerException, response.message
|
11
|
+
elsif (response.kind_of?(Net::HTTPClientError))
|
12
|
+
|
13
|
+
raise Atmos::Exceptions::AtmosException, "Atmos got a bad request. This is probably a problem in this library." if (response.kind_of?(Net::HTTPBadRequest))
|
14
|
+
|
15
|
+
Atmos::LOG.debug("#{response.class}")
|
16
|
+
Atmos::LOG.debug("#{response.body}")
|
17
|
+
|
18
|
+
Atmos::Parser::response_check_action_error(@action, response)
|
19
|
+
|
20
|
+
elsif (!response.kind_of?(Net::HTTPSuccess))
|
21
|
+
raise Atmos::Exceptions::NotImplementedException, "This library doesn't handle these kinds of responses: #{response}"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(header)
|
27
|
+
if (!self[header].nil?)
|
28
|
+
self[header].split(',').each do |elt|
|
29
|
+
if (!elt.index('=').nil?)
|
30
|
+
key,val = elt.split('=')
|
31
|
+
yield key.strip, val.strip
|
32
|
+
else
|
33
|
+
yield elt.strip
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def body
|
40
|
+
@http_response.body
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(sym, *args)
|
44
|
+
Atmos::LOG.info("method missing: #{sym}")
|
45
|
+
header = "x-emc-#{sym.id2name.sub(/_/, '-')}"
|
46
|
+
if (REST[@action][:return_headers].include?(header))
|
47
|
+
Atmos::LOG.info("header: #{header}")
|
48
|
+
rv = Atmos::Util.header2hash(header, @http_response[header])
|
49
|
+
Atmos::LOG.debug("rv: #{rv.inspect}")
|
50
|
+
return rv
|
51
|
+
else
|
52
|
+
return super.method_missing(sym, *args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def delta
|
57
|
+
if (REST[@action][:return_headers].include?('x-emc-delta'))
|
58
|
+
@http_response['x-emc-delta']
|
59
|
+
else
|
60
|
+
raise "A #{@action} request doesn't return a delta."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def policy
|
65
|
+
if (REST[@action][:return_headers].include?('x-emc-policy'))
|
66
|
+
@http_response['x-emc-policy']
|
67
|
+
else
|
68
|
+
raise "A #{@action} request doesn't return a policy description."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def id
|
73
|
+
if (REST[@action][:return_headers].include?('location'))
|
74
|
+
@http_response['location'][@http_response['location'].rindex('/')+1..-1]
|
75
|
+
else
|
76
|
+
raise "A #{@action} request doesn't return an id."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def headers
|
81
|
+
headers = {}
|
82
|
+
@http_response.each do |header, value|
|
83
|
+
headers[header] = value
|
84
|
+
end
|
85
|
+
headers
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](header)
|
89
|
+
@http_response[header]
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
# while (response.kind_of?(Net::HTTPRedirection))
|
100
|
+
|
101
|
+
#From http://railstips.org/blog/archives/2009/03/04/following-redirects-with-nethttp/
|
102
|
+
# rurl = (response['location'].nil?) ? response.body.match(/<a href=\"([^>]+)\">/i)[1] : response['location']
|
103
|
+
|
104
|
+
# puts("Got a redirect \nfrom: #{options[:url]}\n to: #{rurl}")
|
105
|
+
|
106
|
+
# if rurl.start_with?('/')
|
107
|
+
# puts("Got a relative redirect url: #{options[:url]}")
|
108
|
+
# options[:url] = URI.parse("#{url.scheme}://#{url.host}#{redirect_url}")
|
109
|
+
# end
|
110
|
+
|
111
|
+
# options[:redirects] = (options[:redirects].nil?) ? 0 : options[:redirects] += 1
|
112
|
+
# response = self.generic_request(options)
|
113
|
+
# end
|
114
|
+
# raise "Too many redirects (#{options[:redirects]}): #{url}" if (!options[:redirects].nil? && (options[:redirects] > 3))
|
115
|
+
|
116
|
+
end
|
data/lib/atmos/rest.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
module Atmos
|
2
|
+
REST = {
|
3
|
+
:listable_tags => { :verb => :get,
|
4
|
+
:object_uri => '/rest/objects?listabletags',
|
5
|
+
:required_headers => ['x-emc-tags'],
|
6
|
+
:optional_headers => ['x-emc-token'],
|
7
|
+
:http_response => 200,
|
8
|
+
:return_headers => ['x-emc-listable-tags'],
|
9
|
+
},
|
10
|
+
:set_group_acl => { :verb => :post,
|
11
|
+
:object_uri => '/rest/objects/:id?acl',
|
12
|
+
:required_headers => ['x-emc-groupacl'],
|
13
|
+
:http_response => 200,
|
14
|
+
},
|
15
|
+
:set_user_acl => { :verb => :post,
|
16
|
+
:object_uri => '/rest/objects/:id?acl',
|
17
|
+
:required_headers => ['x-emc-useracl'],
|
18
|
+
:http_response => 200,
|
19
|
+
},
|
20
|
+
:set_metadata => { :verb => :post,
|
21
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
22
|
+
:http_response => 200,
|
23
|
+
:required_headers => ['x-emc-meta'],
|
24
|
+
},
|
25
|
+
:set_listable_metadata => { :verb => :post,
|
26
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
27
|
+
:http_response => 200,
|
28
|
+
:required_headers => ['x-emc-listable-meta'],
|
29
|
+
},
|
30
|
+
:delete_metadata => { :verb => :delete,
|
31
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
32
|
+
:http_response => 204,
|
33
|
+
:required_headers => ['x-emc-tags'],
|
34
|
+
},
|
35
|
+
:read_object => { :verb => :get,
|
36
|
+
:object_uri => '/rest/objects/:id',
|
37
|
+
:http_response => 200,
|
38
|
+
:optional_headers => ['Range'],
|
39
|
+
},
|
40
|
+
:update_object => { :verb => :put,
|
41
|
+
:object_uri => '/rest/objects/:id',
|
42
|
+
:http_response => 200,
|
43
|
+
:required_headers => ['Content-Type', 'Content-Length'],
|
44
|
+
:optional_headers => ['x-emc-useracl', 'x-emc-groupacl', 'Range'],
|
45
|
+
},
|
46
|
+
:trunc_object => { :verb => :put,
|
47
|
+
:object_uri => '/rest/objects/:id',
|
48
|
+
:http_response => 200,
|
49
|
+
:required_headers => ['Content-Length'],
|
50
|
+
:optional_headers => ['x-emc-useracl', 'x-emc-groupacl', 'Range'],
|
51
|
+
},
|
52
|
+
:get_service_info => { :verb => :get,
|
53
|
+
:object_uri => '/rest/service',
|
54
|
+
:response => :xml,
|
55
|
+
:http_response => 200,
|
56
|
+
},
|
57
|
+
|
58
|
+
:list_objects => { :verb => :get,
|
59
|
+
:object_uri => '/rest/objects',
|
60
|
+
:response => :xml,
|
61
|
+
:http_response => 200,
|
62
|
+
:required_headers => ['x-emc-tags', 'Content-Type'],
|
63
|
+
:optional_headers => ['x-emc-include-meta', 'x-emc-tags'],
|
64
|
+
:errors => { '1003' => { :class => Atmos::Exceptions::ArgumentException ,
|
65
|
+
:message => "No such listable tag found." } },
|
66
|
+
},
|
67
|
+
|
68
|
+
:create_object => { :verb => :post,
|
69
|
+
:namespace_uri => '/rest/namespace/:namespace',
|
70
|
+
:object_uri => '/rest/objects',
|
71
|
+
:http_response => 201,
|
72
|
+
:optional_headers => ['x-emc-wschecksum', 'x-emc-useracl', 'x-emc-groupacl', 'x-emc-meta', 'x-emc-listable-meta'],
|
73
|
+
:required_headers => ['Content-Type', 'Content-Length'],
|
74
|
+
:return_headers => ['x-emc-delta', 'x-emc-policy', 'location'],
|
75
|
+
:header2sym => {'x-emc-wschecksum' => :checksum,
|
76
|
+
'x-emc-useracl' => :user_acl,
|
77
|
+
'x-emc-groupacl' => :group_acl,
|
78
|
+
'x-emc-meta' => :metadata,
|
79
|
+
'x-emc-listable-meta' => :listable_metadata},
|
80
|
+
},
|
81
|
+
|
82
|
+
:get_object_info => { :verb => :get,
|
83
|
+
:namespace_uri => '/rest/namespace/:namespace?info',
|
84
|
+
:object_uri => '/rest/objects/:id?info',
|
85
|
+
:http_response => 200,
|
86
|
+
:return_headers => ['x-emc-policy'],
|
87
|
+
},
|
88
|
+
:delete_object => { :verb => :delete,
|
89
|
+
:object_uri => '/rest/objects/:id',
|
90
|
+
:http_response => 204,
|
91
|
+
:required_headers => ['Content-Type'],
|
92
|
+
:return_headers => ['x-emc-delta', 'x-emc-policy'],
|
93
|
+
:errors => { '1003' => { :class => Atmos::Exceptions::NoSuchObjectException ,
|
94
|
+
:message => "Object not found to delete." } },
|
95
|
+
},
|
96
|
+
|
97
|
+
:list_system_metadata=> { :verb => :get,
|
98
|
+
:object_uri => '/rest/objects/:id?metadata/system',
|
99
|
+
:http_response => 200,
|
100
|
+
:return_headers => ['x-emc-meta'],
|
101
|
+
:errors => { '1003' => { :class => Atmos::Exceptions::NoSuchObjectException ,
|
102
|
+
:message => "Object not found to delete." } },
|
103
|
+
},
|
104
|
+
:list_metadata => { :verb => :get,
|
105
|
+
:object_uri => '/rest/objects/:id?metadata/user',
|
106
|
+
:http_response => 200,
|
107
|
+
:required_headers => ['Content-Type'],
|
108
|
+
:return_headers => ['x-emc-meta', 'x-emc-listable-meta'],
|
109
|
+
},
|
110
|
+
|
111
|
+
:list_tags => { :verb => :get,
|
112
|
+
:object_uri => '/rest/objects/:id?metadata/tags',
|
113
|
+
:http_response => 200,
|
114
|
+
:return_headers => ['x-emc-tags', 'x-emc-listable-tags'],
|
115
|
+
},
|
116
|
+
|
117
|
+
:list_acl => { :verb => :get,
|
118
|
+
:object_uri => '/rest/objects/:id?acl',
|
119
|
+
:http_response => 200,
|
120
|
+
:return_headers => ['x-emc-useracl', 'x-emc-groupacl'],
|
121
|
+
},
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
:delete_version => { :verb => :delete,
|
130
|
+
:object_uri => '/rest/objects/:id?versions',
|
131
|
+
:http_response => 204,
|
132
|
+
},
|
133
|
+
:create_version => { :verb => :post,
|
134
|
+
:object_uri => '/rest/objects/:id?versions' ,
|
135
|
+
:http_response => 201,
|
136
|
+
},
|
137
|
+
:list_versions => { :verb => :get,
|
138
|
+
:object_uri => '/rest/objects/:id?versions',
|
139
|
+
:http_response => 200},
|
140
|
+
:restore_version => { :verb => :put,
|
141
|
+
:object_uri => '/rest/objects/:id?versions',
|
142
|
+
:http_response => 200},
|
143
|
+
} # :nodoc:
|
144
|
+
|
145
|
+
end
|