ec2_amitools 1.0.2
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 +7 -0
- data/README.md +54 -0
- data/bin/console +14 -0
- data/bin/ec2-ami-tools-version +6 -0
- data/bin/ec2-bundle-image +6 -0
- data/bin/ec2-bundle-vol +6 -0
- data/bin/ec2-delete-bundle +6 -0
- data/bin/ec2-download-bundle +6 -0
- data/bin/ec2-migrate-bundle +6 -0
- data/bin/ec2-migrate-manifest +6 -0
- data/bin/ec2-unbundle +6 -0
- data/bin/ec2-upload-bundle +6 -0
- data/bin/setup +8 -0
- data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
- data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
- data/etc/ec2/amitools/cert-ec2.pem +23 -0
- data/etc/ec2/amitools/mappings.csv +9 -0
- data/lib/ec2/amitools/bundle.rb +251 -0
- data/lib/ec2/amitools/bundle_base.rb +58 -0
- data/lib/ec2/amitools/bundleimage.rb +94 -0
- data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
- data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
- data/lib/ec2/amitools/bundleparameters.rb +120 -0
- data/lib/ec2/amitools/bundlevol.rb +240 -0
- data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
- data/lib/ec2/amitools/crypto.rb +379 -0
- data/lib/ec2/amitools/decryptmanifest.rb +20 -0
- data/lib/ec2/amitools/defaults.rb +12 -0
- data/lib/ec2/amitools/deletebundle.rb +212 -0
- data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
- data/lib/ec2/amitools/downloadbundle.rb +161 -0
- data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
- data/lib/ec2/amitools/exception.rb +86 -0
- data/lib/ec2/amitools/fileutil.rb +219 -0
- data/lib/ec2/amitools/format.rb +127 -0
- data/lib/ec2/amitools/instance-data.rb +97 -0
- data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
- data/lib/ec2/amitools/manifestv20070829.rb +361 -0
- data/lib/ec2/amitools/manifestv20071010.rb +403 -0
- data/lib/ec2/amitools/manifestv3.rb +331 -0
- data/lib/ec2/amitools/mapids.rb +148 -0
- data/lib/ec2/amitools/migratebundle.rb +222 -0
- data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
- data/lib/ec2/amitools/migratemanifest.rb +225 -0
- data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
- data/lib/ec2/amitools/minimalec2.rb +116 -0
- data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
- data/lib/ec2/amitools/parameters_base.rb +168 -0
- data/lib/ec2/amitools/region.rb +93 -0
- data/lib/ec2/amitools/s3toolparameters.rb +183 -0
- data/lib/ec2/amitools/showversion.rb +12 -0
- data/lib/ec2/amitools/syschecks.rb +27 -0
- data/lib/ec2/amitools/tool_base.rb +224 -0
- data/lib/ec2/amitools/unbundle.rb +107 -0
- data/lib/ec2/amitools/unbundleparameters.rb +65 -0
- data/lib/ec2/amitools/uploadbundle.rb +361 -0
- data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
- data/lib/ec2/amitools/util.rb +532 -0
- data/lib/ec2/amitools/version.rb +33 -0
- data/lib/ec2/amitools/xmlbuilder.rb +237 -0
- data/lib/ec2/amitools/xmlutil.rb +55 -0
- data/lib/ec2/common/constants.rb +16 -0
- data/lib/ec2/common/curl.rb +110 -0
- data/lib/ec2/common/headers.rb +95 -0
- data/lib/ec2/common/headersv4.rb +173 -0
- data/lib/ec2/common/http.rb +333 -0
- data/lib/ec2/common/s3support.rb +231 -0
- data/lib/ec2/common/signature.rb +68 -0
- data/lib/ec2/oem/LICENSE.txt +58 -0
- data/lib/ec2/oem/open4.rb +399 -0
- data/lib/ec2/platform/base/architecture.rb +26 -0
- data/lib/ec2/platform/base/constants.rb +54 -0
- data/lib/ec2/platform/base/pipeline.rb +181 -0
- data/lib/ec2/platform/base.rb +57 -0
- data/lib/ec2/platform/current.rb +55 -0
- data/lib/ec2/platform/linux/architecture.rb +35 -0
- data/lib/ec2/platform/linux/constants.rb +23 -0
- data/lib/ec2/platform/linux/fstab.rb +99 -0
- data/lib/ec2/platform/linux/identity.rb +16 -0
- data/lib/ec2/platform/linux/image.rb +811 -0
- data/lib/ec2/platform/linux/mtab.rb +74 -0
- data/lib/ec2/platform/linux/pipeline.rb +40 -0
- data/lib/ec2/platform/linux/rsync.rb +114 -0
- data/lib/ec2/platform/linux/tar.rb +124 -0
- data/lib/ec2/platform/linux/uname.rb +50 -0
- data/lib/ec2/platform/linux.rb +83 -0
- data/lib/ec2/platform/solaris/architecture.rb +28 -0
- data/lib/ec2/platform/solaris/constants.rb +30 -0
- data/lib/ec2/platform/solaris/fstab.rb +43 -0
- data/lib/ec2/platform/solaris/identity.rb +16 -0
- data/lib/ec2/platform/solaris/image.rb +327 -0
- data/lib/ec2/platform/solaris/mtab.rb +29 -0
- data/lib/ec2/platform/solaris/pipeline.rb +40 -0
- data/lib/ec2/platform/solaris/rsync.rb +24 -0
- data/lib/ec2/platform/solaris/tar.rb +36 -0
- data/lib/ec2/platform/solaris/uname.rb +21 -0
- data/lib/ec2/platform/solaris.rb +38 -0
- data/lib/ec2/platform.rb +69 -0
- data/lib/ec2/version.rb +8 -0
- data/lib/ec2_amitools +1 -0
- data/lib/ec2_amitools.rb +7 -0
- metadata +184 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
# Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
|
2
|
+
# Reserved. Licensed under the Amazon Software License (the
|
3
|
+
# "License"). You may not use this file except in compliance with the
|
4
|
+
# License. A copy of the License is located at
|
5
|
+
# http://aws.amazon.com/asl or in the "license" file accompanying this
|
6
|
+
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
|
7
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
8
|
+
# the License for the specific language governing permissions and
|
9
|
+
# limitations under the License.
|
10
|
+
|
11
|
+
module EC2
|
12
|
+
module Common
|
13
|
+
class Headers
|
14
|
+
MANDATORY = ['content-md5', 'content-type', 'date'] # Order matters.
|
15
|
+
X_AMZ_PREFIX = 'x-amz'
|
16
|
+
X_AMZ_SECURITY_TOKEN = 'x-amz-security-token'
|
17
|
+
|
18
|
+
def initialize(verb)
|
19
|
+
raise ArgumentError.new('invalid verb') if verb.to_s.empty?
|
20
|
+
@headers = {}
|
21
|
+
@verb = verb
|
22
|
+
end
|
23
|
+
|
24
|
+
#---------------------------------------------------------------------
|
25
|
+
# Add a Header key-value pair.
|
26
|
+
def add(name, value)
|
27
|
+
raise ArgumentError.new("name '#{name.inspect}' must be a String") unless name.is_a? String
|
28
|
+
raise ArgumentError.new("value '#{value.inspect}' (#{name}) must be a String") unless value.is_a? String
|
29
|
+
@headers[name.downcase.strip] = value.strip
|
30
|
+
end
|
31
|
+
|
32
|
+
#-----------------------------------------------------------------------
|
33
|
+
# Sign the headers using HMAC SHA1.
|
34
|
+
def sign(creds, aws_secret_access_key, url, bucket)
|
35
|
+
aws_access_key_id = creds
|
36
|
+
delegation_token = nil
|
37
|
+
if (creds.is_a?(Hash))
|
38
|
+
aws_access_key_id = creds['aws_access_key_id']
|
39
|
+
aws_secret_access_key = creds['aws_secret_access_key']
|
40
|
+
delegation_token = creds['aws_delegation_token']
|
41
|
+
end
|
42
|
+
|
43
|
+
@headers['date'] = Time.now.httpdate # add HTTP Date header
|
44
|
+
@headers['content-type'] ||= 'application/x-www-form-urlencoded'
|
45
|
+
|
46
|
+
# Build the data to sign.
|
47
|
+
data = @verb + "\n"
|
48
|
+
|
49
|
+
# Deal with mandatory headers first:
|
50
|
+
MANDATORY.each do |name|
|
51
|
+
data += @headers.fetch(name, "") + "\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
unless delegation_token.nil?
|
55
|
+
@headers[X_AMZ_SECURITY_TOKEN] = delegation_token
|
56
|
+
end
|
57
|
+
# Add mandatory headers and those that start with the x-amz prefix.
|
58
|
+
@headers.sort.each do |name, value|
|
59
|
+
# Headers that start with x-amz must have both their name and value
|
60
|
+
# added.
|
61
|
+
if name =~ /^#{X_AMZ_PREFIX}/
|
62
|
+
data += name + ":" + value +"\n"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
uri = URI.parse(url)
|
67
|
+
# Ignore everything in the URL after the question mark unless, by the
|
68
|
+
# S3 protocol, it signifies an acl, torrent, logging or location parameter
|
69
|
+
if uri.host.start_with? bucket
|
70
|
+
data << "/#{bucket}"
|
71
|
+
end
|
72
|
+
data << if uri.path.empty?
|
73
|
+
"/"
|
74
|
+
else
|
75
|
+
uri.path
|
76
|
+
end
|
77
|
+
['acl', 'logging', 'torrent', 'location'].each do |item|
|
78
|
+
regex = Regexp.new("[&?]#{item}($|&|=)")
|
79
|
+
data << '?' + item if regex.match(url)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Sign headers and then put signature back into headers.
|
83
|
+
signature = Base64.encode64(Crypto::hmac_sha1(aws_secret_access_key, data))
|
84
|
+
signature.chomp!
|
85
|
+
@headers['Authorization'] = "AWS #{aws_access_key_id}:#{signature}"
|
86
|
+
end
|
87
|
+
|
88
|
+
#-----------------------------------------------------------------------
|
89
|
+
# Return the headers as a map from header name to header value.
|
90
|
+
def get
|
91
|
+
return @headers.clone
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
|
2
|
+
# Reserved. Licensed under the Amazon Software License (the
|
3
|
+
# "License"). You may not use this file except in compliance with the
|
4
|
+
# License. A copy of the License is located at
|
5
|
+
# http://aws.amazon.com/asl or in the "license" file accompanying this
|
6
|
+
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
|
7
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
8
|
+
# the License for the specific language governing permissions and
|
9
|
+
# limitations under the License.
|
10
|
+
|
11
|
+
#
|
12
|
+
# A class to contain headers and sign them with signature version 4
|
13
|
+
# This code is a shameless copy of our SDK's signature version 4 code base
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'cgi'
|
17
|
+
require 'uri'
|
18
|
+
require 'time'
|
19
|
+
require 'base64'
|
20
|
+
require 'tempfile'
|
21
|
+
require 'digest/md5'
|
22
|
+
require 'openssl'
|
23
|
+
require 'digest'
|
24
|
+
require 'digest/sha2'
|
25
|
+
|
26
|
+
module EC2
|
27
|
+
module Common
|
28
|
+
class HeadersV4
|
29
|
+
# Create an HttpHeaderV4,
|
30
|
+
# values = {
|
31
|
+
# :host -> http host
|
32
|
+
# :hexdigest_body -> hexdigest of the http request's body
|
33
|
+
# :region -> region of the endpoint
|
34
|
+
# :service -> the service that should recieve this request
|
35
|
+
# :http_method -> http method
|
36
|
+
# :path -> URI
|
37
|
+
# :querystring -> Everything after ? in URI
|
38
|
+
# :access_key_id -> access key
|
39
|
+
# :secret_access_key -> secret access kry
|
40
|
+
# [optional] :fixed_datetime -> Fix the datetime using DateTime object, do not use Time.now
|
41
|
+
# }
|
42
|
+
# For more info see:
|
43
|
+
# http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
44
|
+
def initialize values, headers={}
|
45
|
+
@headers = headers
|
46
|
+
@host = values[:host]
|
47
|
+
@hexdigest_body = values[:hexdigest_body]
|
48
|
+
@region = values[:region]
|
49
|
+
@service = values[:service]
|
50
|
+
@http_method = values[:http_method]
|
51
|
+
@path = values[:path]
|
52
|
+
@querystring = values[:querystring]
|
53
|
+
@access_key_id = values[:access_key_id]
|
54
|
+
@secret_access_key = values[:secret_access_key]
|
55
|
+
@fixed_datetime = values[:fixed_datetime]
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_authorization!
|
59
|
+
datetime = get_datetime
|
60
|
+
@headers['host'] = @host
|
61
|
+
@headers['x-amz-date'] = datetime
|
62
|
+
@headers['x-amz-content-sha256'] ||= @hexdigest_body || EC2::Common::HeadersV4::hexdigest('')
|
63
|
+
@headers['authorization'] = authorization(datetime)
|
64
|
+
@headers
|
65
|
+
end
|
66
|
+
|
67
|
+
def authorization datetime
|
68
|
+
parts = []
|
69
|
+
parts << "AWS4-HMAC-SHA256 Credential=#{credential(datetime)}"
|
70
|
+
parts << "SignedHeaders=#{signed_headers}"
|
71
|
+
parts << "Signature=#{signature(datetime)}"
|
72
|
+
parts.join(', ')
|
73
|
+
end
|
74
|
+
|
75
|
+
def signature datetime
|
76
|
+
k_secret = @secret_access_key
|
77
|
+
k_date = hmac("AWS4" + k_secret, datetime[0,8])
|
78
|
+
k_region = hmac(k_date, @region)
|
79
|
+
k_service = hmac(k_region, @service)
|
80
|
+
k_credentials = hmac(k_service, 'aws4_request')
|
81
|
+
hexhmac(k_credentials, string_to_sign(datetime))
|
82
|
+
end
|
83
|
+
|
84
|
+
def string_to_sign datetime
|
85
|
+
parts = []
|
86
|
+
parts << 'AWS4-HMAC-SHA256'
|
87
|
+
parts << datetime
|
88
|
+
parts << credential_string(datetime)
|
89
|
+
parts << EC2::Common::HeadersV4::hexdigest(canonical_request)
|
90
|
+
parts.join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
def credential datetime
|
94
|
+
"#{@access_key_id}/#{credential_string(datetime)}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def credential_string datetime
|
98
|
+
parts = []
|
99
|
+
parts << datetime[0,8]
|
100
|
+
parts << @region
|
101
|
+
parts << @service
|
102
|
+
parts << 'aws4_request'
|
103
|
+
parts.join("/")
|
104
|
+
end
|
105
|
+
|
106
|
+
def canonical_request
|
107
|
+
parts = []
|
108
|
+
parts << @http_method
|
109
|
+
parts << CGI::unescape(@path)
|
110
|
+
parts << canonical_querystring
|
111
|
+
parts << canonical_headers + "\n"
|
112
|
+
parts << signed_headers
|
113
|
+
parts << @headers['x-amz-content-sha256']
|
114
|
+
parts.join("\n")
|
115
|
+
end
|
116
|
+
|
117
|
+
def signed_headers
|
118
|
+
to_sign = @headers.keys.map{|k| k.to_s.downcase}
|
119
|
+
to_sign.delete('authorization')
|
120
|
+
to_sign.sort.join(";")
|
121
|
+
end
|
122
|
+
|
123
|
+
def canonical_querystring
|
124
|
+
CGI::parse(@querystring).sort_by{|k,v| CGI::escape(k)}.map do |v|
|
125
|
+
value = v[1][0] || ""
|
126
|
+
"#{CGI::escape(v[0])}=#{CGI::escape(value)}"
|
127
|
+
end.join('&')
|
128
|
+
end
|
129
|
+
|
130
|
+
def canonical_headers
|
131
|
+
headers = []
|
132
|
+
@headers.each_pair do |k,v|
|
133
|
+
k_lower = k.downcase
|
134
|
+
headers << [k_lower,v] unless k_lower == 'authorization'
|
135
|
+
end
|
136
|
+
headers = headers.sort_by{|k,v| k}
|
137
|
+
headers.map{|k,v| "#{k}:#{canonical_header_values(v)}"}.join("\n")
|
138
|
+
end
|
139
|
+
|
140
|
+
def canonical_header_values values
|
141
|
+
values = [values] unless values.is_a?(Array)
|
142
|
+
values.map{|v|v.to_s}.join(',').gsub(/\s+/, ' ').strip
|
143
|
+
end
|
144
|
+
|
145
|
+
def hmac key, value
|
146
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, value)
|
147
|
+
end
|
148
|
+
|
149
|
+
def hexhmac key, value
|
150
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha256'), key, value)
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_datetime
|
154
|
+
return @fixed_datetime.strftime("%Y%m%dT%H%M%SZ") if @fixed_datetime != nil
|
155
|
+
Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns a SHA256 hexdigest of value.
|
159
|
+
# Should be used to hexdigest body of http request.
|
160
|
+
def self.hexdigest value, chunk_size = 1024 * 1024
|
161
|
+
digest = Digest::SHA256.new
|
162
|
+
if value.respond_to?(:read)
|
163
|
+
chunk = nil
|
164
|
+
digest.update(chunk) while chunk = value.read(chunk_size)
|
165
|
+
value.rewind
|
166
|
+
else
|
167
|
+
digest.update(value)
|
168
|
+
end
|
169
|
+
digest.hexdigest
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
# Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
|
2
|
+
# Reserved. Licensed under the Amazon Software License (the
|
3
|
+
# "License"). You may not use this file except in compliance with the
|
4
|
+
# License. A copy of the License is located at
|
5
|
+
# http://aws.amazon.com/asl or in the "license" file accompanying this
|
6
|
+
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
|
7
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
8
|
+
# the License for the specific language governing permissions and
|
9
|
+
# limitations under the License.
|
10
|
+
|
11
|
+
# ---------------------------------------------------------------------------
|
12
|
+
# Module that provides http functionality.
|
13
|
+
# ---------------------------------------------------------------------------
|
14
|
+
require 'uri'
|
15
|
+
require 'set'
|
16
|
+
require 'time'
|
17
|
+
require 'base64'
|
18
|
+
require 'tmpdir'
|
19
|
+
require 'tempfile'
|
20
|
+
require 'fileutils'
|
21
|
+
|
22
|
+
require 'ec2/common/constants'
|
23
|
+
require 'ec2/common/curl'
|
24
|
+
require 'ec2/common/signature'
|
25
|
+
require 'ec2/common/headers'
|
26
|
+
require 'ec2/amitools/crypto'
|
27
|
+
|
28
|
+
module EC2
|
29
|
+
module Common
|
30
|
+
module HTTP
|
31
|
+
DEFAULT_SIGV = EC2::Common::SIGV4
|
32
|
+
DEFAULT_REGION = 'us-east-1'
|
33
|
+
|
34
|
+
class Response < EC2::Common::Curl::Response
|
35
|
+
attr_reader :body
|
36
|
+
def initialize(code, type, body=nil)
|
37
|
+
super code, type
|
38
|
+
@body = body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#-----------------------------------------------------------------------
|
43
|
+
# Errors.
|
44
|
+
class Error < RuntimeError
|
45
|
+
attr_reader :code
|
46
|
+
def initialize(msg, code = nil)
|
47
|
+
super(msg)
|
48
|
+
@code = code || 1
|
49
|
+
end
|
50
|
+
class PathInvalid < Error; end
|
51
|
+
class Write < Error; end
|
52
|
+
class BadDigest < Error
|
53
|
+
def initialize(file, expected, obtained)
|
54
|
+
super("Digest for file '#{file}' #{obtained} differs from expected digest #{digest}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
class Transfer < Error; end
|
58
|
+
class Retrieve < Transfer; end
|
59
|
+
class Redirect < Error
|
60
|
+
attr_accessor :code, :endpoint
|
61
|
+
def initialize(code, endpoint)
|
62
|
+
super("Redirected (#{code}) to new endpoint: #{endpoint}")
|
63
|
+
@code = code
|
64
|
+
@endpoint = endpoint
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
#-----------------------------------------------------------------------
|
71
|
+
# Invoke curl with arguments given and process results of output file
|
72
|
+
def HTTP::invoke(url, arguments, outfile, debug=false)
|
73
|
+
begin
|
74
|
+
raise ArgumentError.new(outfile) unless File.exists? outfile
|
75
|
+
result = EC2::Common::Curl.execute("#{arguments.join(' ')} '#{url}'", debug)
|
76
|
+
if result.success?
|
77
|
+
if result.response.success?
|
78
|
+
return result.response
|
79
|
+
else
|
80
|
+
synopsis= 'Server.Error(' + result.response.code.to_s + '): '
|
81
|
+
message = result.stderr + ' '
|
82
|
+
if result.response.type == 'application/xml'
|
83
|
+
require 'rexml/document'
|
84
|
+
doc = REXML::Document.new(IO.read(outfile))
|
85
|
+
if doc.root
|
86
|
+
if result.response.redirect?
|
87
|
+
endpoint = REXML::XPath.first(doc, '/Error/Endpoint').text
|
88
|
+
raise Error::Redirect.new(result.response.code, endpoint)
|
89
|
+
end
|
90
|
+
content = REXML::XPath.first(doc, '/Error/Code')
|
91
|
+
unless content.nil? or content.text.empty?
|
92
|
+
synopsis= 'Server.'+ content.text + '(' +
|
93
|
+
result.response.code.to_s + '): '
|
94
|
+
end
|
95
|
+
content = REXML::XPath.first(doc, '/Error/Message')
|
96
|
+
message = content.text unless content.nil?
|
97
|
+
end
|
98
|
+
else
|
99
|
+
if result.response.type =~ /text/
|
100
|
+
message << IO.read(outfile)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
raise Error::Transfer.new(synopsis + message, result.response.code)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
synopsis= 'Curl.Error(' + result.status.to_s + '): '
|
107
|
+
message = result.stderr.split("\n").map { |line|
|
108
|
+
if (m = /^curl:\s+(?:\(\d{1,2}\)\s+)*(.*)$/.match(line))
|
109
|
+
(m.captures[0] == "try 'curl --help' for more information") ?
|
110
|
+
'' : m.captures[0]
|
111
|
+
else
|
112
|
+
line.strip
|
113
|
+
end
|
114
|
+
}.join("\n")
|
115
|
+
output = result.stdout.chomp
|
116
|
+
if debug and not output.empty?
|
117
|
+
message << "\nCurl.Output: " + output.gsub("\n", "\nCurl.Output: ")
|
118
|
+
end
|
119
|
+
raise Error::Transfer.new(synopsis + message.strip + '.', result.status)
|
120
|
+
end
|
121
|
+
rescue EC2::Common::Curl::Error => e
|
122
|
+
raise Error::Transfer.new(e.message, e.code)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
#-----------------------------------------------------------------------
|
127
|
+
# Delete the file at the specified url.
|
128
|
+
def HTTP::delete(url, bucket, options={}, user=nil, pass=nil, debug=false, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
|
129
|
+
raise ArgumentError.new('Bad options in HTTP::delete') unless options.is_a? Hash
|
130
|
+
begin
|
131
|
+
output = Tempfile.new('ec2-delete-response')
|
132
|
+
output.close
|
133
|
+
|
134
|
+
arguments = ['-X DELETE']
|
135
|
+
arguments << get_arguments(url, bucket, 'DELETE', options, user, pass, sigv, region)
|
136
|
+
arguments << '-o ' + output.path
|
137
|
+
|
138
|
+
response = HTTP::invoke(url, arguments, output.path, debug)
|
139
|
+
return EC2::Common::HTTP::Response.new(response.code, response.type)
|
140
|
+
ensure
|
141
|
+
output.close(true)
|
142
|
+
GC.start
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
#-----------------------------------------------------------------------
|
148
|
+
# Put the file at the specified path to the specified url. The content of
|
149
|
+
# the options hash will be passed as HTTP headers. If the username and
|
150
|
+
# password options are specified, then the headers will be signed.
|
151
|
+
def HTTP::put(url, bucket, path, options={}, user=nil, pass=nil, debug=false, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
|
152
|
+
path ||= "/dev/null"
|
153
|
+
raise Error::PathInvalid.new(path) unless path and File::exist?(path)
|
154
|
+
raise ArgumentError.new('Bad options in HTTP::put') unless options.is_a? Hash
|
155
|
+
|
156
|
+
begin
|
157
|
+
output = Tempfile.new('ec2-put-response')
|
158
|
+
output.close
|
159
|
+
|
160
|
+
arguments = [get_arguments(url, bucket, 'PUT', options, user, pass, sigv, region, open(path))]
|
161
|
+
arguments << '-T ' + path
|
162
|
+
arguments << '-o ' + output.path
|
163
|
+
|
164
|
+
response = HTTP::invoke(url, arguments, output.path, debug)
|
165
|
+
return EC2::Common::HTTP::Response.new(response.code, response.type)
|
166
|
+
ensure
|
167
|
+
output.close(true)
|
168
|
+
GC.start
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
#-----------------------------------------------------------------------
|
173
|
+
# Create a bucket using the specified url and constraints (either a file
|
174
|
+
# with location string, or the string itself). The content of the
|
175
|
+
# options hash will be passed as HTTP headers. If the username and
|
176
|
+
# password options are specified, then the headers will be signed.
|
177
|
+
def HTTP::putdir(url, bucket, constraint, options={}, user=nil, pass=nil, debug=false, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
|
178
|
+
if constraint and File::exists?(constraint)
|
179
|
+
putdir_file(url, bucket, constraint, options, user, pass, debug)
|
180
|
+
else
|
181
|
+
putdir_binary_data(url, bucket, constraint, options, user, pass, debug, sigv, region)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def HTTP::putdir_file(url, bucket, path, options={}, user=nil, pass=nil, debug=false)
|
186
|
+
raise Error::PathInvalid.new(path) unless path and File::exist?(path)
|
187
|
+
raise ArgumentError.new('Bad options in HTTP::putdir_file') unless options.is_a? Hash
|
188
|
+
|
189
|
+
begin
|
190
|
+
output = Tempfile.new('ec2-put-response')
|
191
|
+
output.close
|
192
|
+
arguments = []
|
193
|
+
|
194
|
+
headers = EC2::Common::Headers.new('PUT')
|
195
|
+
options.each do |name, value| headers.add(name, value) end
|
196
|
+
headers.sign(user, pass, url, bucket) if user and pass
|
197
|
+
|
198
|
+
arguments << headers.get.map { |name, value| "-H \"#{name}:#{value}\""}.join(' ')
|
199
|
+
arguments << '-T - '
|
200
|
+
arguments << '-o ' + output.path
|
201
|
+
arguments << " < #{path}"
|
202
|
+
|
203
|
+
response = HTTP::invoke(url, arguments, output.path, debug)
|
204
|
+
return EC2::Common::HTTP::Response.new(response.code, response.type)
|
205
|
+
ensure
|
206
|
+
output.close(true)
|
207
|
+
GC.start
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def HTTP::putdir_binary_data(url, bucket, binary_data, options={}, user=nil, pass=nil, debug=false, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
|
212
|
+
raise ArgumentError.new('Bad options in HTTP::putdir_binary_data') unless options.is_a? Hash
|
213
|
+
|
214
|
+
begin
|
215
|
+
output = Tempfile.new('ec2-put-response')
|
216
|
+
output.close
|
217
|
+
|
218
|
+
arguments = ["-X PUT"]
|
219
|
+
arguments << "--data-binary \"#{binary_data}\""
|
220
|
+
arguments << get_arguments(url, bucket, 'PUT', options, user, pass, sigv, region, binary_data)
|
221
|
+
arguments << '-o ' + output.path
|
222
|
+
|
223
|
+
response = HTTP::invoke(url, arguments, output.path, debug)
|
224
|
+
return EC2::Common::HTTP::Response.new(response.code, response.type)
|
225
|
+
ensure
|
226
|
+
output.close(true)
|
227
|
+
GC.start
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
#-----------------------------------------------------------------------
|
232
|
+
# Save the file at specified url, to the local file at specified path.
|
233
|
+
# The local file will be created if necessary, or overwritten already
|
234
|
+
# existing. If specified, the expected digest is compare to that of the
|
235
|
+
# retrieved file which gets deleted if the calculated digest does not meet
|
236
|
+
# expectations. If no path is specified, and the response is a 200 OK, the
|
237
|
+
# content of the response will be returned as a String
|
238
|
+
def HTTP::get(url, bucket, path=nil, options={}, user=nil, pass=nil,
|
239
|
+
size=nil, digest=nil, debug=false, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
|
240
|
+
raise ArgumentError.new('Bad options in HTTP::get') unless options.is_a? Hash
|
241
|
+
buffer = nil
|
242
|
+
if path.nil?
|
243
|
+
buffer = Tempfile.new('ec2-get-response')
|
244
|
+
buffer.close
|
245
|
+
path = buffer.path
|
246
|
+
else
|
247
|
+
directory = File.dirname(path)
|
248
|
+
FileUtils.mkdir_p(directory) unless File.exist?(directory)
|
249
|
+
end
|
250
|
+
|
251
|
+
arguments = [get_arguments(url, bucket, 'GET', options, user, pass, sigv, region)]
|
252
|
+
arguments << "--max-filesize #{size}" if size
|
253
|
+
arguments << '-o ' + path
|
254
|
+
|
255
|
+
begin
|
256
|
+
FileUtils.touch path
|
257
|
+
response = HTTP::invoke(url, arguments, path, debug)
|
258
|
+
body = nil
|
259
|
+
if response.success?
|
260
|
+
if digest
|
261
|
+
obtained = IO.popen("openssl sha1 #{path}") { |io| io.readline.split(/\s+/).last.strip }
|
262
|
+
unless digest == obtained
|
263
|
+
File.delete(path) if File.exists?(path) and not buffer.is_a? Tempfile
|
264
|
+
raise Error::BadDigest.new(path, digest, obtained)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
if buffer.is_a? Tempfile
|
268
|
+
buffer.open;
|
269
|
+
body = buffer.read
|
270
|
+
end
|
271
|
+
else
|
272
|
+
File.delete(path) if File.exist?(path) and not buffer.is_a? Tempfile
|
273
|
+
end
|
274
|
+
return EC2::Common::HTTP::Response.new(response.code, response.type, body)
|
275
|
+
rescue Error::Transfer => e
|
276
|
+
File::delete(path) if File::exist?(path) and not buffer.is_a? Tempfile
|
277
|
+
raise Error::Retrieve.new(e.message, e.code)
|
278
|
+
ensure
|
279
|
+
if buffer.is_a? Tempfile
|
280
|
+
buffer.close
|
281
|
+
buffer.unlink if File.exists? path
|
282
|
+
GC.start
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
#-----------------------------------------------------------------------
|
289
|
+
# Get the HEAD response for the specified url.
|
290
|
+
def HTTP::head(url, bucket, options={}, user=nil, pass=nil, debug=false, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
|
291
|
+
raise ArgumentError.new('Bad options in HTTP::head') unless options.is_a? Hash
|
292
|
+
begin
|
293
|
+
output = Tempfile.new('ec2-head-response')
|
294
|
+
output.close
|
295
|
+
|
296
|
+
arguments = ['--head']
|
297
|
+
arguments << get_arguments(url, bucket, 'HEAD', options, user, pass, sigv, region)
|
298
|
+
arguments << '-o ' + output.path
|
299
|
+
|
300
|
+
response = HTTP::invoke(url, arguments, output.path, debug)
|
301
|
+
return EC2::Common::HTTP::Response.new(response.code, response.type)
|
302
|
+
|
303
|
+
rescue Error::Transfer => e
|
304
|
+
raise Error::Retrieve.new(e.message, e.code)
|
305
|
+
ensure
|
306
|
+
output.close(true)
|
307
|
+
GC.start
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
def HTTP::get_arguments(url, bucket, http_method, options, user, pass, sigv, region, file_path=nil)
|
313
|
+
headers = if user and pass
|
314
|
+
if sigv == EC2::Common::SIGV2
|
315
|
+
EC2::Common::Signature::curl_args_sigv2(url, bucket,
|
316
|
+
http_method,
|
317
|
+
options,
|
318
|
+
user, pass)
|
319
|
+
elsif sigv == EC2::Common::SIGV4
|
320
|
+
EC2::Common::Signature::curl_args_sigv4(url, region, bucket,
|
321
|
+
http_method,
|
322
|
+
options,
|
323
|
+
user, pass,
|
324
|
+
file_path)
|
325
|
+
end
|
326
|
+
else
|
327
|
+
options
|
328
|
+
end
|
329
|
+
headers.map { |name, value| "-H \"#{name}:#{value}\""}.join(' ')
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|