ruby-atmos-pure 1.0.3

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.
@@ -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