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