ruby-manta 1.2.0 → 2.0.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 +4 -4
- data/README.md +25 -11
- data/example.rb +13 -8
- data/lib/ruby-manta.rb +5 -926
- data/lib/ruby-manta/manta_client.rb +989 -0
- data/lib/ruby-manta/version.rb +3 -0
- data/ruby-manta.gemspec +12 -8
- data/{tests/test_ruby-manta.rb → test/unit/manta_client_test.rb} +133 -48
- metadata +36 -7
- data/lib/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e741a0fbcb8ccef97c5efdac3290bd22c9bbcd74
|
4
|
+
data.tar.gz: 01bc9d49ec9fd487314fe5ef3f624b2f4f156f8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2a09a41b30212d25f174e5aa3c0672bcc66ae84870305e6fb7abf64195315314db2fdbdc12c4dfc3c5f07f547a223f1e3eb50e6778bb3ab6b70641d29451d60
|
7
|
+
data.tar.gz: 3e95d53baa10a0f77694e1490322421336714480c260447b867b78db9d85219ee92ab94c355f4891d98702476a5930717c566b9181cf23d2f95b6ecf2de5f0ba
|
data/README.md
CHANGED
@@ -64,19 +64,19 @@ hurried friend, is an example demonstrating some of ruby-manta's usage:
|
|
64
64
|
|
65
65
|
# You'll need to provide these four environment variables to run this
|
66
66
|
# example. E.g.:
|
67
|
-
#
|
68
|
-
#
|
69
|
-
host = ENV['
|
70
|
-
user = ENV['
|
71
|
-
priv_key = ENV['
|
72
|
-
upload_dir = ENV['
|
67
|
+
# MANTA_USER=john MANTA_KEY=~/.ssh/john \
|
68
|
+
# MANTA_URL=https://us-east.manta.joyent.com LOCAL_DIR=. ruby example.rb
|
69
|
+
host = ENV['MANTA_URL']
|
70
|
+
user = ENV['MANTA_USER']
|
71
|
+
priv_key = ENV['MANTA_KEY' ]
|
72
|
+
upload_dir = ENV['LOCAL_DIR' ]
|
73
73
|
|
74
74
|
# Read in private key, create a MantaClient instance. MantaClient is
|
75
75
|
# thread-safe and provides persistent connections with pooling, so you'll
|
76
76
|
# only ever need a single instance of this in a program.
|
77
77
|
priv_key_data = File.read(priv_key)
|
78
|
-
client = MantaClient.new(host, user, priv_key_data,
|
79
|
-
|
78
|
+
client = RubyManta::MantaClient.new(host, user, priv_key_data,
|
79
|
+
:disable_ssl_verification => true)
|
80
80
|
|
81
81
|
# Create an directory in Manta solely for this example run.
|
82
82
|
dir_path = '/' + user + '/stor/ruby-manta-example'
|
@@ -183,7 +183,7 @@ see his image.png? In this case there is also the "public" space:
|
|
183
183
|
Signed URLs
|
184
184
|
-----------
|
185
185
|
|
186
|
-
Objects put in the public space are accessible by everyone. Objects in the
|
186
|
+
Objects put in the public space are accessible by everyone. Objects in the
|
187
187
|
private space are only accessible by individuals authenticated and authorized
|
188
188
|
by Manta. Manta also supports temporary signed URLs that allow unauthenticated
|
189
189
|
individuals to operate on private objects, until the link expires. See
|
@@ -312,8 +312,8 @@ Example:
|
|
312
312
|
````` ruby
|
313
313
|
|
314
314
|
priv_key_data = File.read('/home/john/.ssh/john')
|
315
|
-
client = MantaClient.new('https://manta.joyentcloud.com', 'john',
|
316
|
-
|
315
|
+
client = RubyManta::MantaClient.new('https://manta.joyentcloud.com', 'john',
|
316
|
+
priv_key_data, :disable_ssl_verification => true)
|
317
317
|
`````
|
318
318
|
|
319
319
|
|
@@ -440,6 +440,20 @@ Examples:
|
|
440
440
|
|
441
441
|
|
442
442
|
|
443
|
+
find(dir_path, _options_)
|
444
|
+
-----------------------
|
445
|
+
|
446
|
+
Finds all Manta objects underneath a given path,
|
447
|
+
|
448
|
+
The path must be a valid directory path and point at an actual directory.
|
449
|
+
|
450
|
+
The path must be a valid directory path and point at an actual directory.
|
451
|
+
:limit optionally changes the maximum number of entries; the default is 1000.
|
452
|
+
If given :marker, an object name in the directory, returned directory entries
|
453
|
+
will begin from that point. :regex => can optionally be passed in to filter
|
454
|
+
filenames by a given regular expression.
|
455
|
+
|
456
|
+
|
443
457
|
delete_directory(dir_path, _options_)
|
444
458
|
-------------------------------------
|
445
459
|
|
data/example.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
|
-
|
1
|
+
require_relative 'lib/ruby-manta'
|
2
2
|
|
3
3
|
# You'll need to provide these four environment variables to run this
|
4
4
|
# example. E.g.:
|
5
|
-
#
|
6
|
-
host = ENV['
|
7
|
-
user = ENV['
|
8
|
-
priv_key = ENV['
|
9
|
-
upload_dir = ENV['
|
5
|
+
# MANTA_USER=john KEY=~/.ssh/john MANTA_URL=https://us-east.manta.joyent.com LOCAL_DIR=. ruby example.rb
|
6
|
+
host = ENV['MANTA_URL']
|
7
|
+
user = ENV['MANTA_USER']
|
8
|
+
priv_key = ENV['MANTA_KEY' ]
|
9
|
+
upload_dir = ENV['LOCAL_DIR' ]
|
10
|
+
|
11
|
+
raise 'You must specify MANTA_URL' unless host
|
12
|
+
raise 'You must specify MANTA_USER' unless user
|
13
|
+
raise 'You must specify MANTA_KEY' unless priv_key
|
14
|
+
raise 'You must specify LOCAL_DIR' unless upload_dir
|
10
15
|
|
11
16
|
# Read in private key, create a MantaClient instance. MantaClient is
|
12
17
|
# thread-safe and provides persistent connections with pooling, so you'll
|
13
18
|
# only ever need a single instance of this in a program.
|
14
19
|
priv_key_data = File.read(priv_key)
|
15
|
-
client = MantaClient.new(host, user, priv_key_data,
|
16
|
-
|
20
|
+
client = RubyManta::MantaClient.new(host, user, priv_key_data,
|
21
|
+
:disable_ssl_verification => true)
|
17
22
|
|
18
23
|
# Create an directory in Manta solely for this example run.
|
19
24
|
dir_path = '/' + user + '/stor/ruby-manta-example'
|
data/lib/ruby-manta.rb
CHANGED
@@ -1,928 +1,7 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# ruby-manta is a simple low-abstraction layer which communicates with Joyent's
|
4
|
-
# Manta service.
|
5
|
-
#
|
6
|
-
# Manta is an HTTP-accessible object store supporting UNIX-based map-reduce
|
7
|
-
# jobs. Through ruby-manta a programmer can save/overwrite/delete objects
|
8
|
-
# stored on a Manta service, or run map-reduce jobs over those objects.
|
9
|
-
#
|
10
|
-
# ruby-manta should be thread-safe, and supports pooling of keep-alive
|
11
|
-
# connections to the same server (through HTTPClient). It only relies on the
|
12
|
-
# standard library and two pure Ruby libraries, so it should work anywhere.
|
13
|
-
#
|
14
|
-
# For more information about Manta and general ruby-manta usage, please see
|
15
|
-
# README.md.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
require 'openssl'
|
20
|
-
require 'net/ssh'
|
21
|
-
require 'httpclient'
|
22
|
-
require 'base64'
|
23
|
-
require 'digest'
|
24
|
-
require 'time'
|
25
|
-
require 'json'
|
26
|
-
require 'cgi'
|
27
|
-
require 'uri'
|
28
|
-
|
29
|
-
require File.expand_path('../version', __FILE__)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
class MantaClient
|
34
|
-
DEFAULT_ATTEMPTS = 3
|
35
|
-
DEFAULT_CONNECT_TIMEOUT = 5
|
36
|
-
DEFAULT_SEND_TIMEOUT = 60
|
37
|
-
DEFAULT_RECEIVE_TIMEOUT = 60
|
38
|
-
MAX_LIMIT = 1000
|
39
|
-
HTTP_AGENT = "ruby-manta/#{LIB_VERSION} (#{RUBY_PLATFORM}; #{OpenSSL::OPENSSL_VERSION}) ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
40
|
-
HTTP_SIGNATURE = 'Signature keyId="/%s/keys/%s",algorithm="%s",signature="%s"'
|
41
|
-
OBJ_PATH_REGEX = Regexp.new('^/[^/]+(?:/?$|/stor|/public|/reports|/jobs)(?:/|$)')
|
42
|
-
JOB_PATH_REGEX = Regexp.new('^/[^/]+/jobs(?:/|$)')
|
43
|
-
|
44
|
-
# match one or more protocol and hostnames, with optional port numbers.
|
45
|
-
# E.g. "http://example.com https://example.com:8443"
|
46
|
-
CORS_ORIGIN_REGEX = Regexp.new('^\w+://[^\s\:]+(?:\:\d+)?' +
|
47
|
-
'(?:\s\w+://[^\s\:]+(?:\:\d+)?)*$')
|
48
|
-
CORS_HEADERS_REGEX = Regexp.new('^[\w-]+(?:, [\w-]+)*$')
|
49
|
-
CORS_METHODS = [ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS' ]
|
50
|
-
|
51
|
-
ERROR_CLASSES = [ 'AuthorizationFailed', 'AuthSchemeNotAllowed',
|
52
|
-
'BadRequest', 'Checksum', 'ConcurrentRequest',
|
53
|
-
'ContentLength', 'ContentMD5Mismatch',
|
54
|
-
'DirectoryDoesNotExist', 'DirectoryExists',
|
55
|
-
'DirectoryNotEmpty', 'DirectoryOperation',
|
56
|
-
'EntityExists', 'Internal', 'InvalidArgument',
|
57
|
-
'InvalidAuthToken', 'InvalidCredentials',
|
58
|
-
'InvalidDurabilityLevel', 'InvalidJob', 'InvalidKeyId',
|
59
|
-
'InvalidLink', 'InvalidSignature', 'InvalidJobState',
|
60
|
-
'JobNotFound', 'JobState', 'KeyDoesNotExist',
|
61
|
-
'LinkNotFound', 'LinkNotObject', 'LinkRequired',
|
62
|
-
'NotAcceptable', 'NotEnoughSpace', 'ParentNotDirectory',
|
63
|
-
'PreconditionFailed', 'PreSignedRequest',
|
64
|
-
'RequestEntityTooLarge', 'ResourceNotFound',
|
65
|
-
'RootDirectory', 'SecureTransportRequired',
|
66
|
-
'ServiceUnavailable', 'SourceObjectNotFound',
|
67
|
-
'SSLRequired', 'TaskInit', 'UploadTimeout',
|
68
|
-
'UserDoesNotExist', 'UserTaskError',
|
69
|
-
# and errors that are specific to this class:
|
70
|
-
'CorruptResult', 'UnknownError',
|
71
|
-
'UnsupportedKey' ]
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# Initialize a MantaClient instance.
|
76
|
-
#
|
77
|
-
# priv_key_data is data read directly from an SSH private key (i.e. RFC 4716
|
78
|
-
# format). The method can also accept several optional args: :connect_timeout,
|
79
|
-
# :send_timeout, :receive_timeout, :disable_ssl_verification and :attempts.
|
80
|
-
# The timeouts are in seconds, and :attempts determines the default number of
|
81
|
-
# attempts each method will make upon receiving recoverable errors.
|
82
|
-
#
|
83
|
-
# Will throw an exception if given a key whose format it doesn't understand.
|
84
|
-
def initialize(host, user, priv_key_data, opts = {})
|
85
|
-
raise ArgumentError unless host =~ /^https{0,1}:\/\/.*[^\/]/
|
86
|
-
raise ArgumentError unless user.is_a?(String) && user.size > 0
|
87
|
-
|
88
|
-
@host = host
|
89
|
-
@user = user
|
90
|
-
|
91
|
-
@attempts = opts[:attempts] || DEFAULT_ATTEMPTS
|
92
|
-
raise ArgumentError unless @attempts > 0
|
93
|
-
|
94
|
-
if priv_key_data =~ /BEGIN RSA/
|
95
|
-
@digest = OpenSSL::Digest::SHA1.new
|
96
|
-
@digest_name = 'rsa-sha1'
|
97
|
-
algorithm = OpenSSL::PKey::RSA
|
98
|
-
elsif priv_key_data =~ /BEGIN DSA/
|
99
|
-
@digest = OpenSSL::Digest::DSS1.new
|
100
|
-
@digest_name = 'dsa-sha1'
|
101
|
-
algorithm = OpenSSL::PKey::DSA
|
102
|
-
else
|
103
|
-
raise UnsupportedKey
|
104
|
-
end
|
105
|
-
|
106
|
-
@priv_key = algorithm.new(priv_key_data)
|
107
|
-
@fingerprint = OpenSSL::Digest::MD5.hexdigest(@priv_key.to_blob).
|
108
|
-
scan(/../).join(':')
|
109
|
-
|
110
|
-
@client = HTTPClient.new
|
111
|
-
@client.connect_timeout = opts[:connect_timeout] || DEFAULT_CONNECT_TIMEOUT
|
112
|
-
@client.send_timeout = opts[:send_timeout ] || DEFAULT_SEND_TIMEOUT
|
113
|
-
@client.receive_timeout = opts[:receive_timeout] || DEFAULT_RECEIVE_TIMEOUT
|
114
|
-
@client.ssl_config.verify_mode = nil if opts[:disable_ssl_verification]
|
115
|
-
|
116
|
-
@job_base = '/' + user + '/jobs'
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Uploads object data to Manta to the given path, along with a computed MD5
|
122
|
-
# hash.
|
123
|
-
#
|
124
|
-
# The path must start with /<user>/stor or /<user/public. Data can be any
|
125
|
-
# sequence of octets. The HTTP Content-Type stored on Manta can be set
|
126
|
-
# with an optional :content_type argument; the default is
|
127
|
-
# application/octet-stream. The number of distributed replicates of an object
|
128
|
-
# stored in Manta can be set with an optional :durability_level; the default
|
129
|
-
# is 2.
|
130
|
-
#
|
131
|
-
# Returns true along with received HTTP headers.
|
132
|
-
#
|
133
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
134
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
135
|
-
# be altered by passing in :attempts.
|
136
|
-
def put_object(obj_path, data, opts = {})
|
137
|
-
url = obj_url(obj_path)
|
138
|
-
|
139
|
-
opts[:data] = data
|
140
|
-
headers = gen_headers(opts)
|
141
|
-
|
142
|
-
cors_headers = gen_cors_headers(opts)
|
143
|
-
headers = headers.concat(cors_headers)
|
144
|
-
|
145
|
-
durability_level = opts[:durability_level]
|
146
|
-
if durability_level
|
147
|
-
raise ArgumentError unless durability_level > 0
|
148
|
-
headers.push([ 'Durability-Level', durability_level ])
|
149
|
-
end
|
150
|
-
|
151
|
-
content_type = opts[:content_type]
|
152
|
-
if content_type
|
153
|
-
raise ArgumentError unless content_type.is_a? String
|
154
|
-
headers.push([ 'Content-Type', content_type ])
|
155
|
-
end
|
156
|
-
|
157
|
-
attempt(opts[:attempts]) do
|
158
|
-
result = @client.put(url, data, headers)
|
159
|
-
raise unless result.is_a? HTTP::Message
|
160
|
-
|
161
|
-
return true, result.headers if [204, 304].include? result.status
|
162
|
-
raise_error(result)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
# Get an object from Manta at a given path, and checks it's uncorrupted.
|
169
|
-
#
|
170
|
-
# The path must start with /<user>/stor or /<user/public and point at an
|
171
|
-
# actual object, as well as output objects for jobs. :head => true can
|
172
|
-
# optionally be passed in to do a HEAD instead of a GET.
|
173
|
-
#
|
174
|
-
# Returns the retrieved data along with received HTTP headers.
|
175
|
-
#
|
176
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
177
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
178
|
-
# be altered by passing in :attempts.
|
179
|
-
def get_object(obj_path, opts = {})
|
180
|
-
url = obj_url(obj_path)
|
181
|
-
headers = gen_headers(opts)
|
182
|
-
|
183
|
-
attempt(opts[:attempts]) do
|
184
|
-
method = opts[:head] ? :head : :get
|
185
|
-
result = @client.send(method, url, nil, headers)
|
186
|
-
raise unless result.is_a? HTTP::Message
|
187
|
-
|
188
|
-
if result.status == 200
|
189
|
-
return true, result.headers if method == :head
|
190
|
-
|
191
|
-
sent_md5 = result.headers['Content-MD5']
|
192
|
-
received_md5 = Digest::MD5.base64digest(result.body)
|
193
|
-
raise CorruptResult if sent_md5 != received_md5
|
194
|
-
|
195
|
-
return result.body, result.headers
|
196
|
-
elsif result.status == 304
|
197
|
-
return nil, result.headers
|
198
|
-
end
|
199
|
-
|
200
|
-
raise_error(result)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
# Deletes an object off Manta at a given path.
|
207
|
-
#
|
208
|
-
# The path must start with /<user>/stor or /<user/public and point at an
|
209
|
-
# actual object.
|
210
|
-
#
|
211
|
-
# Returns true along with received HTTP headers.
|
212
|
-
#
|
213
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
214
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
215
|
-
# be altered by passing in :attempts.
|
216
|
-
def delete_object(obj_path, opts = {})
|
217
|
-
url = obj_url(obj_path)
|
218
|
-
headers = gen_headers(opts)
|
219
|
-
|
220
|
-
attempt(opts[:attempts]) do
|
221
|
-
result = @client.delete(url, nil, headers)
|
222
|
-
raise unless result.is_a? HTTP::Message
|
223
|
-
|
224
|
-
return true, result.headers if result.status == 204
|
225
|
-
raise_error(result)
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
# Creates a directory on Manta at a given path.
|
232
|
-
#
|
233
|
-
# The path must start with /<user>/stor or /<user/public.
|
234
|
-
#
|
235
|
-
# Returns true along with received HTTP headers.
|
236
|
-
#
|
237
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
238
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
239
|
-
# be altered by passing in :attempts.
|
240
|
-
def put_directory(dir_path, opts = {})
|
241
|
-
url = obj_url(dir_path)
|
242
|
-
headers = gen_headers(opts)
|
243
|
-
headers.push([ 'Content-Type', 'application/json; type=directory' ])
|
244
|
-
|
245
|
-
cors_headers = gen_cors_headers(opts)
|
246
|
-
headers = headers.concat(cors_headers)
|
247
|
-
|
248
|
-
attempt(opts[:attempts]) do
|
249
|
-
result = @client.put(url, nil, headers)
|
250
|
-
raise unless result.is_a? HTTP::Message
|
251
|
-
|
252
|
-
return true, result.headers if result.status == 204
|
253
|
-
raise_error(result)
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
# Gets a lexicographically sorted directory listing on Manta at a given path,
|
260
|
-
#
|
261
|
-
# The path must start with /<user>/stor or /<user/public and point at an
|
262
|
-
# actual directory. :limit optionally changes the maximum number of entries;
|
263
|
-
# the default is 1000. If given :marker, an object name in the directory,
|
264
|
-
# returned directory entries will begin from that point. :head => true can
|
265
|
-
# optionally be passed in to do a HEAD instead of a GET.
|
266
|
-
#
|
267
|
-
# Returns an array of hash objects, each object representing a directory
|
268
|
-
# entry. Also returns the received HTTP headers.
|
269
|
-
#
|
270
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
271
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
272
|
-
# be altered by passing in :attempts.
|
273
|
-
def list_directory(dir_path, opts = {})
|
274
|
-
url = obj_url(dir_path)
|
275
|
-
headers = gen_headers(opts)
|
276
|
-
query_parameters = {}
|
277
|
-
|
278
|
-
limit = opts[:limit] || MAX_LIMIT
|
279
|
-
raise ArgumentError unless 0 < limit && limit <= MAX_LIMIT
|
280
|
-
query_parameters[:limit] = limit
|
281
|
-
|
282
|
-
marker = opts[:marker]
|
283
|
-
if marker
|
284
|
-
raise ArgumentError unless marker.is_a? String
|
285
|
-
query_parameters[:marker] = marker
|
286
|
-
end
|
287
|
-
|
288
|
-
attempt(opts[:attempts]) do
|
289
|
-
method = opts[:head] ? :head : :get
|
290
|
-
result = @client.send(method, url, query_parameters, headers)
|
291
|
-
raise unless result.is_a? HTTP::Message
|
292
|
-
|
293
|
-
if result.status == 200
|
294
|
-
raise unless result.headers['Content-Type'] ==
|
295
|
-
'application/x-json-stream; type=directory'
|
296
|
-
|
297
|
-
return true, result.headers if method == :head
|
298
|
-
|
299
|
-
json_chunks = result.body.split("\n")
|
300
|
-
|
301
|
-
if json_chunks.size > limit
|
302
|
-
raise CorruptResult
|
303
|
-
end
|
304
|
-
|
305
|
-
dir_entries = json_chunks.map { |i| JSON.parse(i) }
|
306
|
-
|
307
|
-
return dir_entries, result.headers
|
308
|
-
end
|
309
|
-
|
310
|
-
raise_error(result)
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
# Removes a directory from Manta at a given path.
|
317
|
-
#
|
318
|
-
# The path must start with /<user>/stor or /<user/public and point at an
|
319
|
-
# actual object.
|
320
|
-
#
|
321
|
-
# Returns true along with received HTTP headers.
|
322
|
-
#
|
323
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
324
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
325
|
-
# be altered by passing in :attempts.
|
326
|
-
def delete_directory(dir_path, opts = {})
|
327
|
-
url = obj_url(dir_path)
|
328
|
-
headers = gen_headers(opts)
|
329
|
-
|
330
|
-
attempt(opts[:attempts]) do
|
331
|
-
result = @client.delete(url, nil, headers)
|
332
|
-
raise unless result.is_a? HTTP::Message
|
333
|
-
|
334
|
-
return true, result.headers if result.status == 204
|
335
|
-
raise_error(result)
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
# Creates a snaplink from one object in Manta at a given path to a different
|
342
|
-
# path.
|
343
|
-
#
|
344
|
-
# Both paths should start with /<user>/stor or /<user/public.
|
345
|
-
#
|
346
|
-
# Returns true along with received HTTP headers.
|
347
|
-
#
|
348
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
349
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
350
|
-
# be altered by passing in :attempts.
|
351
|
-
def put_snaplink(orig_path, link_path, opts = {})
|
352
|
-
headers = gen_headers(opts)
|
353
|
-
headers.push([ 'Content-Type', 'application/json; type=link' ],
|
354
|
-
[ 'Location', obj_url(orig_path) ])
|
355
|
-
|
356
|
-
attempt(opts[:attempts]) do
|
357
|
-
result = @client.put(obj_url(link_path), nil, headers)
|
358
|
-
raise unless result.is_a? HTTP::Message
|
359
|
-
|
360
|
-
return true, result.headers if result.status == 204
|
361
|
-
raise_error(result)
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
# Creates a job in Manta.
|
368
|
-
#
|
369
|
-
# The job must be a hash, containing at minimum a :phases key. See README.md
|
370
|
-
# or the Manta docs to see the format and options for setting up a job on
|
371
|
-
# Manta; this method effectively just converts the job hash to JSON and sends
|
372
|
-
# to the Manta service.
|
373
|
-
#
|
374
|
-
# Returns the path for the new job, along with received HTTP headers.
|
375
|
-
#
|
376
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
377
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
378
|
-
# be altered by passing in :attempts.
|
379
|
-
def create_job(job, opts = {})
|
380
|
-
raise ArgumentError unless job[:phases] || job['phases']
|
381
|
-
|
382
|
-
headers = gen_headers(opts)
|
383
|
-
headers.push([ 'Content-Type', 'application/json; type=job' ])
|
384
|
-
data = job.to_json
|
385
|
-
|
386
|
-
attempt(opts[:attempts]) do
|
387
|
-
result = @client.post(job_url(), data, headers)
|
388
|
-
raise unless result.is_a? HTTP::Message
|
389
|
-
|
390
|
-
if result.status == 201
|
391
|
-
location = result.headers['Location']
|
392
|
-
raise unless location
|
393
|
-
|
394
|
-
return location, result.headers
|
395
|
-
end
|
396
|
-
|
397
|
-
raise_error(result)
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
# Gets various information about a job in Manta at a given path.
|
404
|
-
#
|
405
|
-
# The path must start with /<user>/jobs/<job UUID> and point at an actual job.
|
406
|
-
# :head => true can optionally be passed in to do a HEAD instead of a GET.
|
407
|
-
#
|
408
|
-
# Returns a hash with job information, along with received HTTP headers.
|
409
|
-
#
|
410
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
411
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
412
|
-
# be altered by passing in :attempts.
|
413
|
-
def get_job(job_path, opts = {})
|
414
|
-
url = job_url(job_path, '/live/status')
|
415
|
-
headers = gen_headers(opts)
|
416
|
-
|
417
|
-
attempt(opts[:attempts]) do
|
418
|
-
method = opts[:head] ? :head : :get
|
419
|
-
result = @client.send(method, url, nil, headers)
|
420
|
-
raise unless result.is_a? HTTP::Message
|
421
|
-
|
422
|
-
if result.status == 200
|
423
|
-
raise unless result.headers['Content-Type'] == 'application/json'
|
424
|
-
|
425
|
-
return true, result.headers if method == :head
|
426
|
-
|
427
|
-
job = JSON.parse(result.body)
|
428
|
-
return job, result.headers
|
429
|
-
end
|
430
|
-
|
431
|
-
raise_error(result)
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
# Gets errors that occured during the execution of a job in Manta at a given
|
438
|
-
# path.
|
439
|
-
#
|
440
|
-
# The path must start with /<user>/jobs/<job UUID> and point at an actual job.
|
441
|
-
# :head => true can optionally be passed in to do a HEAD instead of a GET.
|
442
|
-
#
|
443
|
-
# Returns an array of hashes, each hash containing information about an
|
444
|
-
# error; this information is best-effort by Manta, so it may not be complete.
|
445
|
-
# Also returns received HTTP headers.
|
446
|
-
#
|
447
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
448
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
449
|
-
# be altered by passing in :attempts.
|
450
|
-
def get_job_errors(job_path, opts = {})
|
451
|
-
url = job_url(job_path, '/live/err')
|
452
|
-
headers = gen_headers(opts)
|
453
|
-
|
454
|
-
attempt(opts[:attempts]) do
|
455
|
-
method = opts[:head] ? :head : :get
|
456
|
-
result = @client.send(method, url, nil, headers)
|
457
|
-
raise unless result.is_a? HTTP::Message
|
458
|
-
|
459
|
-
if result.status == 200
|
460
|
-
raise unless result.headers['Content-Type'] ==
|
461
|
-
'application/x-json-stream; type=job-error'
|
462
|
-
|
463
|
-
return true, result.headers if method == :head
|
464
|
-
|
465
|
-
json_chunks = result.body.split("\n")
|
466
|
-
errors = json_chunks.map { |i| JSON.parse(i) }
|
467
|
-
|
468
|
-
return errors, result.headers
|
469
|
-
end
|
470
|
-
|
471
|
-
raise_error(result)
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
# Cancels a running job in Manta at a given path.
|
478
|
-
#
|
479
|
-
# The path must start with /<user>/jobs/<job UUID> and point at an actual job.
|
480
|
-
#
|
481
|
-
# Returns true, along with received HTTP headers.
|
482
|
-
#
|
483
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
484
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
485
|
-
# be altered by passing in :attempts.
|
486
|
-
def cancel_job(job_path, opts = {})
|
487
|
-
url = job_url(job_path, '/live/cancel')
|
488
|
-
headers = gen_headers(opts)
|
489
|
-
|
490
|
-
attempt(opts[:attempts]) do
|
491
|
-
result = @client.post(url, nil, headers)
|
492
|
-
raise unless result.is_a? HTTP::Message
|
493
|
-
|
494
|
-
return true, result.headers if result.status == 202
|
495
|
-
raise_error(result)
|
496
|
-
end
|
497
|
-
end
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
# Adds objects for a running job in Manta to process.
|
502
|
-
#
|
503
|
-
# The job_path must start with /<user>/jobs/<job UUID> and point at an actual
|
504
|
-
# running job. The obj_paths must be an array of paths, starting with
|
505
|
-
# /<user>/stor or /<user>/public, pointing at actual objects.
|
506
|
-
#
|
507
|
-
# Returns true, along with received HTTP headers.
|
508
|
-
#
|
509
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
510
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
511
|
-
# be altered by passing in :attempts.
|
512
|
-
def add_job_keys(job_path, obj_paths, opts = {})
|
513
|
-
url = job_url(job_path, '/live/in')
|
514
|
-
headers = gen_headers(opts)
|
515
|
-
headers.push([ 'Content-Type', 'text/plain' ])
|
516
|
-
|
517
|
-
data = obj_paths.join("\n")
|
518
|
-
|
519
|
-
attempt(opts[:attempts]) do
|
520
|
-
result = @client.post(url, data, headers)
|
521
|
-
raise unless result.is_a? HTTP::Message
|
522
|
-
|
523
|
-
return true, result.headers if result.status == 204
|
524
|
-
raise_error(result)
|
525
|
-
end
|
526
|
-
end
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
# Inform Manta that no more objects will be added for processing by a job,
|
531
|
-
# and that the job should finish all phases and terminate.
|
532
|
-
#
|
533
|
-
# The job_path must start with /<user>/jobs/<job UUID> and point at an actual
|
534
|
-
# running job.
|
535
|
-
#
|
536
|
-
# Returns true, along with received HTTP headers.
|
537
|
-
#
|
538
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
539
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
540
|
-
# be altered by passing in :attempts.
|
541
|
-
def end_job_input(job_path, opts = {})
|
542
|
-
url = job_url(job_path, '/live/in/end')
|
543
|
-
headers = gen_headers(opts)
|
544
|
-
|
545
|
-
attempt(opts[:attempts]) do
|
546
|
-
result = @client.post(url, nil, headers)
|
547
|
-
raise unless result.is_a? HTTP::Message
|
548
|
-
|
549
|
-
return true, result.headers if result.status == 202
|
550
|
-
raise_error(result)
|
551
|
-
end
|
552
|
-
end
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
# Get a list of objects that have been given to a Manta job for processing.
|
557
|
-
#
|
558
|
-
# The job_path must start with /<user>/jobs/<job UUID> and point at an actual
|
559
|
-
# running job.
|
560
|
-
#
|
561
|
-
# Returns an array of object paths, along with received HTTP headers.
|
562
|
-
#
|
563
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
564
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
565
|
-
# be altered by passing in :attempts.
|
566
|
-
def get_job_input(job_path, opts = {})
|
567
|
-
get_job_state_streams(:in, job_path, opts)
|
568
|
-
end
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
# Get a list of objects that contain the intermediate results of a running
|
573
|
-
# Manta job.
|
574
|
-
#
|
575
|
-
# The job_path must start with /<user>/jobs/<job UUID> and point at an actual
|
576
|
-
# running job.
|
577
|
-
#
|
578
|
-
# Returns an array of object paths, along with received HTTP headers.
|
579
|
-
#
|
580
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
581
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
582
|
-
# be altered by passing in :attempts.
|
583
|
-
def get_job_output(job_path, opts = {})
|
584
|
-
get_job_state_streams(:out, job_path, opts)
|
585
|
-
end
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
# Get a list of objects that had failures during processing in a Manta job.
|
590
|
-
#
|
591
|
-
# The job_path must start with /<user>/jobs/<job UUID> and point at an actual
|
592
|
-
# running job.
|
593
|
-
#
|
594
|
-
# Returns an array of object paths, along with received HTTP headers.
|
595
|
-
#
|
596
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
597
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
598
|
-
# be altered by passing in :attempts.
|
599
|
-
def get_job_failures(job_path, opts = {})
|
600
|
-
get_job_state_streams(:fail, job_path, opts)
|
601
|
-
end
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
# Get lists of Manta jobs.
|
606
|
-
#
|
607
|
-
# The state indicates which kind of jobs to return. :running is for jobs
|
608
|
-
# that are currently processing, :done and :all should be obvious. Be careful
|
609
|
-
# of the latter two if you've run a lot of jobs -- the list could be quite
|
610
|
-
# long.
|
611
|
-
#
|
612
|
-
# Returns an array of hashes, each hash containing some information about a
|
613
|
-
# job. Also returns received HTTP headers.
|
614
|
-
#
|
615
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
616
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
617
|
-
# be altered by passing in :attempts.
|
618
|
-
def list_jobs(state, opts = {})
|
619
|
-
raise ArgumentError unless [:all, :running, :done].include? state
|
620
|
-
state = nil if state == :all
|
621
|
-
|
622
|
-
headers = gen_headers(opts)
|
623
|
-
|
624
|
-
attempt(opts[:attempts]) do
|
625
|
-
# method = opts[:head] ? :head : :get
|
626
|
-
method = :get # until added to Manta service
|
627
|
-
result = @client.send(method, job_url(), { :state => state }, headers)
|
628
|
-
raise unless result.is_a? HTTP::Message
|
629
|
-
|
630
|
-
if result.status == 200
|
631
|
-
# return true, result.headers if method == :head
|
632
|
-
return [], result.headers if result.body.size == 0
|
633
|
-
|
634
|
-
raise unless result.headers['Content-Type'] ==
|
635
|
-
'application/x-json-stream; type=job'
|
636
|
-
|
637
|
-
json_chunks = result.body.split("\n")
|
638
|
-
job_entries = json_chunks.map { |i| JSON.parse(i) }
|
639
|
-
|
640
|
-
return job_entries, result.headers
|
641
|
-
end
|
642
|
-
|
643
|
-
raise_error(result)
|
644
|
-
end
|
645
|
-
end
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
# Generates a signed URL which can be used by unauthenticated users to
|
650
|
-
# make a request to Manta at the given path. This is typically used to GET
|
651
|
-
# an object, or to make a CORS preflighted PUT request.
|
652
|
-
#
|
653
|
-
# expires is a Time object or integer representing time after epoch; this
|
654
|
-
# determines how long the signed URL will be valid for. The method is either a
|
655
|
-
# single HTTP method (:get, :put, :post, :delete, :options) or a list of such
|
656
|
-
# methods that the signed URL is allowed to be used for. The path must start
|
657
|
-
# with /<user>/stor. Lastly, the optional args is an array containing pairs of
|
658
|
-
# query args that will be appended at the end of the URL.
|
659
|
-
#
|
660
|
-
# The returned URL is signed, and can be used either over HTTP or HTTPS until
|
661
|
-
# it reaches the expiry date.
|
662
|
-
def gen_signed_url(expires, method, path, args=[])
|
663
|
-
methods = method.is_a?(Array) ? method : [method]
|
664
|
-
raise ArgumentError unless (methods - [:get, :put, :post, :delete, :options]).empty?
|
665
|
-
raise ArgumentError unless path =~ OBJ_PATH_REGEX
|
666
|
-
|
667
|
-
key_id = '/%s/keys/%s' % [@user, @fingerprint]
|
668
|
-
|
669
|
-
args.push([ 'expires', expires.to_i ])
|
670
|
-
args.push([ 'algorithm', @digest_name ])
|
671
|
-
args.push([ 'keyId', key_id ])
|
672
|
-
|
673
|
-
method = methods.map {|m| m.to_s.upcase }.sort.join(",")
|
674
|
-
host = URI.encode(@host.split('/').last)
|
675
|
-
path = URI.encode(path)
|
676
|
-
|
677
|
-
args.push(['method', method]) if methods.count > 1
|
678
|
-
|
679
|
-
encoded_args = args.sort.map do |key, val|
|
680
|
-
# to comply with RFC 3986
|
681
|
-
CGI.escape(key.to_s) + '=' + CGI.escape(val.to_s)
|
682
|
-
end.join('&')
|
683
|
-
|
684
|
-
plaintext = "#{method}\n#{host}\n#{path}\n#{encoded_args}"
|
685
|
-
signature = @priv_key.sign(@digest, plaintext)
|
686
|
-
encoded_signature = CGI.escape(Base64.strict_encode64(signature))
|
687
|
-
|
688
|
-
host + path + '?' + encoded_args + '&signature=' + encoded_signature
|
689
|
-
end
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
# Create some Manta error classes
|
694
|
-
class MantaClientError < StandardError; end
|
695
|
-
for class_name in ERROR_CLASSES
|
696
|
-
MantaClient.const_set(class_name, Class.new(MantaClientError))
|
697
|
-
end
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
# ---------------------------------------------------------------------------
|
702
|
-
protected
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
# Fetch lists of objects that have a given status.
|
707
|
-
#
|
708
|
-
# type takes one of three values (:in, :out, fail), path must start with
|
709
|
-
# /<user>/jobs/<job UUID> and point at an actual job.
|
710
|
-
#
|
711
|
-
# Returns an array of object paths, along with received HTTP headers.
|
712
|
-
#
|
713
|
-
# If there was an unrecoverable error, throws an exception. On connection or
|
714
|
-
# corruption errors, more attempts will be made; the number of attempts can
|
715
|
-
# be altered by passing in :attempts.
|
716
|
-
def get_job_state_streams(type, path, opts)
|
717
|
-
raise ArgumentError unless [:in, :out, :fail].include? type
|
718
|
-
|
719
|
-
url = job_url(path, '/live/' + type.to_s)
|
720
|
-
headers = gen_headers(opts)
|
721
|
-
|
722
|
-
attempt(opts[:attempts]) do
|
723
|
-
#method = opts[:head] ? :head : :get
|
724
|
-
method = :get # until added to Manta service
|
725
|
-
result = @client.send(method, url, nil, headers)
|
726
|
-
raise unless result.is_a? HTTP::Message
|
727
|
-
|
728
|
-
if result.status == 200
|
729
|
-
raise unless result.headers['Content-Type'] == 'text/plain'
|
730
|
-
return true, result.headers if method == :head
|
731
|
-
paths = result.body.split("\n")
|
732
|
-
return paths, result.headers
|
733
|
-
end
|
734
|
-
|
735
|
-
raise_error(result)
|
736
|
-
end
|
737
|
-
end
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
# Returns a full URL for a given path to an object.
|
742
|
-
def obj_url(path)
|
743
|
-
raise ArgumentError unless path =~ OBJ_PATH_REGEX
|
744
|
-
|
745
|
-
URI.encode(@host + path)
|
746
|
-
end
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
# Returns a full URL for a given path to a job.
|
751
|
-
def job_url(*args)
|
752
|
-
path = if args.size == 0
|
753
|
-
@job_base
|
754
|
-
else
|
755
|
-
raise ArgumentError unless args.first =~ JOB_PATH_REGEX
|
756
|
-
args.join('/')
|
757
|
-
end
|
758
|
-
|
759
|
-
URI.encode(@host + path)
|
760
|
-
end
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
# Executes a block. If there is a connection- or corruption-related exception
|
765
|
-
# the block will be reexecuted up to the `tries' argument. It will sleep
|
766
|
-
# for an exponentially-increasing number of seconds between retries.
|
767
|
-
def attempt(tries, &blk)
|
768
|
-
if tries
|
769
|
-
raise ArgumentError unless tries > 0
|
770
|
-
else
|
771
|
-
tries ||= @attempts
|
772
|
-
end
|
773
|
-
|
774
|
-
attempt = 1
|
775
|
-
|
776
|
-
while true
|
777
|
-
begin
|
778
|
-
return yield blk
|
779
|
-
rescue Errno::ECONNREFUSED, HTTPClient::TimeoutError,
|
780
|
-
CorruptResult => e
|
781
|
-
raise e if attempt == tries
|
782
|
-
sleep 2 ** attempt
|
783
|
-
attempt += 1
|
784
|
-
end
|
785
|
-
end
|
786
|
-
end
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
# Creates headers to be given to the HTTP client and sent to the Manta
|
791
|
-
# service. The most important is the Authorization header, without which
|
792
|
-
# none of this class would work.
|
793
|
-
def gen_headers(opts)
|
794
|
-
now = Time.now.httpdate
|
795
|
-
sig = gen_signature('date: ' + now)
|
796
|
-
|
797
|
-
headers = [[ 'Date', now ],
|
798
|
-
[ 'Authorization', sig ],
|
799
|
-
[ 'User-Agent', HTTP_AGENT ],
|
800
|
-
[ 'Accept-Version', '~1.0' ]]
|
801
|
-
|
802
|
-
|
803
|
-
# headers for conditional requests (dates)
|
804
|
-
for arg, conditional in [[:if_modified_since, 'If-Modified-Since' ],
|
805
|
-
[:if_unmodified_since, 'If-Unmodified-Since']]
|
806
|
-
date = opts[arg]
|
807
|
-
next unless date
|
808
|
-
|
809
|
-
date = Time.parse(date.to_s) unless date.kind_of? Time
|
810
|
-
headers.push([conditional, date])
|
811
|
-
end
|
812
|
-
|
813
|
-
# headers for conditional requests (etags)
|
814
|
-
for arg, conditional in [[:if_match, 'If-Match' ],
|
815
|
-
[:if_none_match, 'If-None-Match']]
|
816
|
-
etag = opts[arg]
|
817
|
-
next unless etag
|
818
|
-
|
819
|
-
raise ArgumentError unless etag.kind_of? String
|
820
|
-
headers.push([conditional, etag])
|
821
|
-
end
|
822
|
-
|
823
|
-
origin = opts[:origin]
|
824
|
-
if origin
|
825
|
-
raise ArgumentError unless origin == 'null' || origin =~ CORS_ORIGIN_REGEX
|
826
|
-
headers.push([ 'Origin', origin ])
|
827
|
-
end
|
828
|
-
|
829
|
-
# add md5 hash when sending data
|
830
|
-
data = opts[:data]
|
831
|
-
if data
|
832
|
-
md5 = Digest::MD5.base64digest(data)
|
833
|
-
headers.push([ 'Content-MD5', md5 ])
|
834
|
-
end
|
835
|
-
|
836
|
-
return headers
|
837
|
-
end
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
# Do some sanity checks and create CORS-related headers
|
842
|
-
#
|
843
|
-
# For more details, see http://www.w3.org/TR/cors/ and
|
844
|
-
# https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Access-Control-Expose-Headers
|
845
|
-
def gen_cors_headers(opts)
|
846
|
-
headers = []
|
847
|
-
|
848
|
-
allow_credentials = opts[:access_control_allow_credentials]
|
849
|
-
if allow_credentials
|
850
|
-
allow_credentials = allow_credentials.to_s
|
851
|
-
raise ArgumentError unless allow_credentials == 'true' ||
|
852
|
-
allow_credentials == 'false'
|
853
|
-
headers.push([ 'Access-Control-Allow-Credentials', allow_credentials ])
|
854
|
-
end
|
855
|
-
|
856
|
-
allow_headers = opts[:access_control_allow_headers]
|
857
|
-
if allow_headers
|
858
|
-
raise ArgumentError unless allow_headers =~ CORS_HEADERS_REGEX
|
859
|
-
allow_headers = allow_headers.split(', ').map(&:downcase).sort.join(', ')
|
860
|
-
headers.push([ 'Access-Control-Allow-Headers', allow_headers ])
|
861
|
-
end
|
862
|
-
|
863
|
-
allow_methods = opts[:access_control_allow_methods]
|
864
|
-
if allow_methods
|
865
|
-
raise ArgumentError unless allow_methods.kind_of? String
|
866
|
-
|
867
|
-
unknown_methods = allow_methods.split(', ').reject do |str|
|
868
|
-
CORS_METHODS.include? str
|
869
|
-
end
|
870
|
-
raise ArgumentError unless unknown_methods.size == 0
|
871
|
-
|
872
|
-
headers.push([ 'Access-Control-Allow-Methods', allow_methods ])
|
873
|
-
end
|
874
|
-
|
875
|
-
allow_origin = opts[:access_control_allow_origin]
|
876
|
-
if allow_origin
|
877
|
-
raise ArgumentError unless allow_origin.kind_of? String
|
878
|
-
raise ArgumentError unless allow_origin == '*' ||
|
879
|
-
allow_origin == 'null' ||
|
880
|
-
allow_origin =~ CORS_ORIGIN_REGEX
|
881
|
-
headers.push([ 'Access-Control-Allow-Origin', allow_origin ])
|
882
|
-
end
|
883
|
-
|
884
|
-
expose_headers = opts[:access_control_expose_headers]
|
885
|
-
if expose_headers
|
886
|
-
raise ArgumentError unless expose_headers =~ CORS_HEADERS_REGEX
|
887
|
-
expose_headers = expose_headers.split(', ').map(&:downcase).sort.join(', ')
|
888
|
-
headers.push([ 'Access-Control-Expose-Headers', expose_headers ])
|
889
|
-
end
|
890
|
-
|
891
|
-
max_age = opts[:access_control_max_age]
|
892
|
-
if max_age
|
893
|
-
raise ArgumentError unless max_age.kind_of?(Integer) && max_age >= 0
|
894
|
-
headers.push([ 'Access-Control-Max-Age', max_age.to_s ])
|
895
|
-
end
|
896
|
-
|
897
|
-
headers
|
898
|
-
end
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
# Given a chunk of data, creates an HTTP signature which the Manta service
|
903
|
-
# understands and uses for authentication.
|
904
|
-
def gen_signature(data)
|
905
|
-
raise ArgumentError unless data
|
906
|
-
|
907
|
-
sig = @priv_key.sign(@digest, data)
|
908
|
-
base64sig = Base64.strict_encode64(sig)
|
909
|
-
|
910
|
-
return HTTP_SIGNATURE % [@user, @fingerprint, @digest_name, base64sig]
|
911
|
-
end
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
# Raises an appropriate exception given the HTTP response. If a 40* is
|
916
|
-
# returned, attempts to look up an appropriate error class and raise,
|
917
|
-
# otherwise raises an UnknownError.
|
918
|
-
def raise_error(result)
|
919
|
-
raise unless result.is_a? HTTP::Message
|
920
|
-
|
921
|
-
err = JSON.parse(result.body)
|
922
|
-
klass = MantaClient.const_get err['code']
|
923
|
-
raise klass, err['message']
|
924
|
-
rescue NameError, TypeError, JSON::ParserError
|
925
|
-
raise UnknownError, result.status.to_s + ': ' + result.body
|
926
|
-
end
|
1
|
+
module RubyManta
|
927
2
|
end
|
928
3
|
|
4
|
+
require_relative 'ruby-manta/manta_client'
|
5
|
+
|
6
|
+
# Added API compatibility with 1.xx versions. This may be removed in the future
|
7
|
+
MantaClient = RubyManta::MantaClient
|