ec2_amitools 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +54 -0
  3. data/bin/console +14 -0
  4. data/bin/ec2-ami-tools-version +6 -0
  5. data/bin/ec2-bundle-image +6 -0
  6. data/bin/ec2-bundle-vol +6 -0
  7. data/bin/ec2-delete-bundle +6 -0
  8. data/bin/ec2-download-bundle +6 -0
  9. data/bin/ec2-migrate-bundle +6 -0
  10. data/bin/ec2-migrate-manifest +6 -0
  11. data/bin/ec2-unbundle +6 -0
  12. data/bin/ec2-upload-bundle +6 -0
  13. data/bin/setup +8 -0
  14. data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
  15. data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
  16. data/etc/ec2/amitools/cert-ec2.pem +23 -0
  17. data/etc/ec2/amitools/mappings.csv +9 -0
  18. data/lib/ec2/amitools/bundle.rb +251 -0
  19. data/lib/ec2/amitools/bundle_base.rb +58 -0
  20. data/lib/ec2/amitools/bundleimage.rb +94 -0
  21. data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
  22. data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
  23. data/lib/ec2/amitools/bundleparameters.rb +120 -0
  24. data/lib/ec2/amitools/bundlevol.rb +240 -0
  25. data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
  26. data/lib/ec2/amitools/crypto.rb +379 -0
  27. data/lib/ec2/amitools/decryptmanifest.rb +20 -0
  28. data/lib/ec2/amitools/defaults.rb +12 -0
  29. data/lib/ec2/amitools/deletebundle.rb +212 -0
  30. data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
  31. data/lib/ec2/amitools/downloadbundle.rb +161 -0
  32. data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
  33. data/lib/ec2/amitools/exception.rb +86 -0
  34. data/lib/ec2/amitools/fileutil.rb +219 -0
  35. data/lib/ec2/amitools/format.rb +127 -0
  36. data/lib/ec2/amitools/instance-data.rb +97 -0
  37. data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
  38. data/lib/ec2/amitools/manifestv20070829.rb +361 -0
  39. data/lib/ec2/amitools/manifestv20071010.rb +403 -0
  40. data/lib/ec2/amitools/manifestv3.rb +331 -0
  41. data/lib/ec2/amitools/mapids.rb +148 -0
  42. data/lib/ec2/amitools/migratebundle.rb +222 -0
  43. data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
  44. data/lib/ec2/amitools/migratemanifest.rb +225 -0
  45. data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
  46. data/lib/ec2/amitools/minimalec2.rb +116 -0
  47. data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
  48. data/lib/ec2/amitools/parameters_base.rb +168 -0
  49. data/lib/ec2/amitools/region.rb +93 -0
  50. data/lib/ec2/amitools/s3toolparameters.rb +183 -0
  51. data/lib/ec2/amitools/showversion.rb +12 -0
  52. data/lib/ec2/amitools/syschecks.rb +27 -0
  53. data/lib/ec2/amitools/tool_base.rb +224 -0
  54. data/lib/ec2/amitools/unbundle.rb +107 -0
  55. data/lib/ec2/amitools/unbundleparameters.rb +65 -0
  56. data/lib/ec2/amitools/uploadbundle.rb +361 -0
  57. data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
  58. data/lib/ec2/amitools/util.rb +532 -0
  59. data/lib/ec2/amitools/version.rb +33 -0
  60. data/lib/ec2/amitools/xmlbuilder.rb +237 -0
  61. data/lib/ec2/amitools/xmlutil.rb +55 -0
  62. data/lib/ec2/common/constants.rb +16 -0
  63. data/lib/ec2/common/curl.rb +110 -0
  64. data/lib/ec2/common/headers.rb +95 -0
  65. data/lib/ec2/common/headersv4.rb +173 -0
  66. data/lib/ec2/common/http.rb +333 -0
  67. data/lib/ec2/common/s3support.rb +231 -0
  68. data/lib/ec2/common/signature.rb +68 -0
  69. data/lib/ec2/oem/LICENSE.txt +58 -0
  70. data/lib/ec2/oem/open4.rb +399 -0
  71. data/lib/ec2/platform/base/architecture.rb +26 -0
  72. data/lib/ec2/platform/base/constants.rb +54 -0
  73. data/lib/ec2/platform/base/pipeline.rb +181 -0
  74. data/lib/ec2/platform/base.rb +57 -0
  75. data/lib/ec2/platform/current.rb +55 -0
  76. data/lib/ec2/platform/linux/architecture.rb +35 -0
  77. data/lib/ec2/platform/linux/constants.rb +23 -0
  78. data/lib/ec2/platform/linux/fstab.rb +99 -0
  79. data/lib/ec2/platform/linux/identity.rb +16 -0
  80. data/lib/ec2/platform/linux/image.rb +811 -0
  81. data/lib/ec2/platform/linux/mtab.rb +74 -0
  82. data/lib/ec2/platform/linux/pipeline.rb +40 -0
  83. data/lib/ec2/platform/linux/rsync.rb +114 -0
  84. data/lib/ec2/platform/linux/tar.rb +124 -0
  85. data/lib/ec2/platform/linux/uname.rb +50 -0
  86. data/lib/ec2/platform/linux.rb +83 -0
  87. data/lib/ec2/platform/solaris/architecture.rb +28 -0
  88. data/lib/ec2/platform/solaris/constants.rb +30 -0
  89. data/lib/ec2/platform/solaris/fstab.rb +43 -0
  90. data/lib/ec2/platform/solaris/identity.rb +16 -0
  91. data/lib/ec2/platform/solaris/image.rb +327 -0
  92. data/lib/ec2/platform/solaris/mtab.rb +29 -0
  93. data/lib/ec2/platform/solaris/pipeline.rb +40 -0
  94. data/lib/ec2/platform/solaris/rsync.rb +24 -0
  95. data/lib/ec2/platform/solaris/tar.rb +36 -0
  96. data/lib/ec2/platform/solaris/uname.rb +21 -0
  97. data/lib/ec2/platform/solaris.rb +38 -0
  98. data/lib/ec2/platform.rb +69 -0
  99. data/lib/ec2/version.rb +8 -0
  100. data/lib/ec2_amitools +1 -0
  101. data/lib/ec2_amitools.rb +7 -0
  102. 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