rsanheim-amazon-ec2 0.3.6.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.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ # read the contents of the gemspec, eval it, and assign it to 'spec'
7
+ # this lets us maintain all gemspec info in one place. Nice and DRY.
8
+ spec = eval(IO.read("amazon-ec2.gemspec"))
9
+
10
+ Rake::GemPackageTask.new(spec) do |pkg|
11
+ pkg.gem_spec = spec
12
+ end
13
+
14
+ desc "Package and then install the gem locally"
15
+ task :install => [:package] do
16
+ sh %{sudo gem install pkg/#{GEM}-#{VER}}
17
+ end
18
+
19
+ desc "Package and then install the gem locally omitting documentation"
20
+ task :install_nodoc => [:package] do
21
+ sh %{sudo gem install --no-ri --no-rdoc pkg/#{GEM}-#{VER}}
22
+ end
23
+
24
+ Rake::TestTask.new do |t|
25
+ t.libs << "test"
26
+ t.test_files = FileList['test/test*.rb']
27
+ t.verbose = true
28
+ end
29
+
30
+ Rake::RDocTask.new do |rd|
31
+ rd.main = "README.rdoc"
32
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
33
+ rd.rdoc_dir = 'doc'
34
+ rd.options = spec.rdoc_options
35
+ end
36
+
37
+ task :default => :test
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Amazon Web Services EC2 Query API Ruby library
4
+ #
5
+ # Ruby Gem Name:: amazon-ec2
6
+ # Author:: Glenn Rempe (mailto:glenn@rempe.us)
7
+ # Copyright:: Copyright (c) 2007-2008 Glenn Rempe
8
+ # License:: Distributes under the same terms as Ruby
9
+ # Home:: http://github.com/grempe/amazon-ec2/tree/master
10
+ #++
11
+
12
+ require 'rubygems'
13
+ require File.dirname(__FILE__) + '/../lib/EC2'
14
+ require 'pp'
15
+
16
+ # pull these from the local shell environment variables set in ~/.bash_login
17
+ # or using appropriate methods specific to your login shell.
18
+ #
19
+ # e.g. in ~/.bash_login
20
+ #
21
+ # # For amazon-ec2 and amazon s3 ruby gems
22
+ # export AMAZON_ACCESS_KEY_ID="FOO"
23
+ # export AMAZON_SECRET_ACCESS_KEY="BAR"
24
+
25
+ ACCESS_KEY_ID = ENV['AMAZON_ACCESS_KEY_ID']
26
+ SECRET_ACCESS_KEY = ENV['AMAZON_SECRET_ACCESS_KEY']
27
+
28
+ if ACCESS_KEY_ID.nil? || ACCESS_KEY_ID.empty?
29
+ puts "Error : You must add the shell environment variables AMAZON_ACCESS_KEY_ID and AMAZON_SECRET_ACCESS_KEY before calling #{$0}!"
30
+ exit
31
+ end
32
+
33
+ # us-east-1.ec2.amazonaws.com == ec2.amazonaws.com
34
+ # eu-west-1.ec2.amazonaws.com for the european region
35
+ # test different servers by running something like:
36
+ # export EC2_URL='https://ec2.amazonaws.com';./bin/ec2-gem-example.rb
37
+ if ENV['EC2_URL']
38
+ ec2 = EC2::Base.new( :access_key_id => ACCESS_KEY_ID, :secret_access_key => SECRET_ACCESS_KEY, :server => URI.parse(ENV['EC2_URL']).host )
39
+ else
40
+ # default server is US ec2.amazonaws.com
41
+ ec2 = EC2::Base.new( :access_key_id => ACCESS_KEY_ID, :secret_access_key => SECRET_ACCESS_KEY )
42
+ end
43
+
44
+ puts "----- ec2.methods.sort -----"
45
+ p ec2.methods.sort
46
+
47
+ puts "----- listing images owned by 'amazon' -----"
48
+ ec2.describe_images(:owner_id => "amazon").imagesSet.item.each do |image|
49
+ image.keys.each do |key|
50
+ puts "#{key} => #{image[key]}"
51
+ end
52
+ end
53
+
54
+ puts "----- listing all running instances -----"
55
+ pp ec2.describe_instances()
56
+
57
+ puts "----- creating a security group -----"
58
+ pp ec2.create_security_group(:group_name => "ec2-example-rb-test-group", :group_description => "ec-example.rb test group description.")
59
+
60
+ puts "----- listing security groups -----"
61
+ pp ec2.describe_security_groups()
62
+
63
+ puts "----- deleting a security group -----"
64
+ pp ec2.delete_security_group(:group_name => "ec2-example-rb-test-group")
65
+
66
+ puts "----- listing my keypairs (verbose mode) -----"
67
+ pp ec2.describe_keypairs()
data/bin/ec2sh ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Amazon Web Services EC2 Query API Ruby library
4
+ #
5
+ # Ruby Gem Name:: amazon-ec2
6
+ # Author:: Glenn Rempe (mailto:glenn@rempe.us)
7
+ # Copyright:: Copyright (c) 2007-2008 Glenn Rempe
8
+ # License:: Distributes under the same terms as Ruby
9
+ # Home:: http://github.com/grempe/amazon-ec2/tree/master
10
+ #++
11
+
12
+ # CREDITS : Credit for this bit of shameful ripoff coolness
13
+ # goes to Marcel Molina and his AWS::S3 gem. Thanks!
14
+
15
+ # Usage : running this starts up an irb session and
16
+ # sets up the connection to EC2 as a class variable called
17
+ # '@ec2'. So just do something like the following on the
18
+ # shell command line:
19
+
20
+ # macbook-pro:~ glenn$ ec2sh
21
+ # >> @ec2.describe_images
22
+ # => [#<EC2::Item image_location...
23
+
24
+ ec2_lib = File.dirname(__FILE__) + '/../lib/EC2'
25
+ setup = File.dirname(__FILE__) + '/setup'
26
+ irb_name = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
27
+
28
+ if ( ENV['AMAZON_ACCESS_KEY_ID'] && ENV['AMAZON_SECRET_ACCESS_KEY'] )
29
+
30
+ welcome_message = <<-MESSAGE
31
+
32
+ 'ec2sh' usage :
33
+ This is an interactive 'irb' command shell that allows you to use all
34
+ commands available to the amazon-ec2 gem. You'll find this to be a
35
+ great tool to help you debug issues and practice running commands
36
+ against the live EC2 servers prior to putting them in your code.
37
+
38
+ The EC2 connection is wired to the class instance '@ec2'. Make method calls
39
+ on this to execute commands on EC2. Adding a #to_s
40
+ at the end of any command should give you a full String representation of the
41
+ response.
42
+
43
+ Examples to try:
44
+
45
+ returns : all ec2 public methods
46
+ >> @ec2.methods.sort
47
+
48
+ returns : a string representation of ALL images
49
+ >> @ec2.describe_images.to_s
50
+
51
+ returns : an Array of EC2::Response objects, each an EC2 image and its data
52
+ >> @ec2.describe_images.imagesSet.item
53
+ >> @ec2.describe_images.imagesSet.item[0] (an OpenStruct of a single item in that array)
54
+ >> @ec2.describe_images.imagesSet.item[0].to_s (a String representation of that OpenStruct item)
55
+
56
+ MESSAGE
57
+
58
+ puts welcome_message
59
+ exec "#{irb_name} -r #{ec2_lib} -r #{setup} --simple-prompt"
60
+ else
61
+ puts "You must define AMAZON_ACCESS_KEY_ID and AMAZON_SECRET_ACCESS_KEY as shell environment variables before running #{$0}!"
62
+ end
data/bin/setup.rb ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Amazon Web Services EC2 Query API Ruby library
4
+ #
5
+ # Ruby Gem Name:: amazon-ec2
6
+ # Author:: Glenn Rempe (mailto:glenn@rempe.us)
7
+ # Copyright:: Copyright (c) 2007-2008 Glenn Rempe
8
+ # License:: Distributes under the same terms as Ruby
9
+ # Home:: http://github.com/grempe/amazon-ec2/tree/master
10
+ #++
11
+
12
+ if ENV['AMAZON_ACCESS_KEY_ID'] && ENV['AMAZON_SECRET_ACCESS_KEY']
13
+ opts = {
14
+ :access_key_id => ENV['AMAZON_ACCESS_KEY_ID'],
15
+ :secret_access_key => ENV['AMAZON_SECRET_ACCESS_KEY']
16
+ }
17
+ if ENV['EC2_URL']
18
+ opts[:server] = URI.parse(ENV['EC2_URL']).host
19
+ end
20
+ @ec2 = EC2::Base.new(opts)
21
+ end
22
+
23
+ puts "EC2 Server: #{opts[:server]}"
24
+
25
+ include EC2
data/lib/EC2.rb ADDED
@@ -0,0 +1,270 @@
1
+ #--
2
+ # Amazon Web Services EC2 Query API Ruby library
3
+ #
4
+ # Ruby Gem Name:: amazon-ec2
5
+ # Author:: Glenn Rempe (mailto:glenn@rempe.us)
6
+ # Copyright:: Copyright (c) 2007-2008 Glenn Rempe
7
+ # License:: Distributes under the same terms as Ruby
8
+ # Home:: http://github.com/grempe/amazon-ec2/tree/master
9
+ #++
10
+
11
+ %w[ base64 cgi openssl digest/sha1 net/https rexml/document time ostruct ].each { |f| require f }
12
+
13
+ # Require any lib files that we have bundled with this Ruby Gem in the lib/EC2 directory.
14
+ # Parts of the EC2 module and Base class are broken out into separate
15
+ # files for maintainability and are organized by the functional groupings defined
16
+ # in the EC2 API developers guide.
17
+ Dir[File.join(File.dirname(__FILE__), 'EC2/**/*.rb')].sort.each { |lib| require lib }
18
+
19
+ module EC2
20
+
21
+ # Which host FQDN will we connect to for all API calls to AWS?
22
+ # If EC2_URL is defined in the users ENV we can use that. It is
23
+ # expected that this var is set with something like:
24
+ # export EC2_URL='https://ec2.amazonaws.com'
25
+ #
26
+ if ENV['EC2_URL']
27
+ EC2_URL = ENV['EC2_URL']
28
+ VALID_HOSTS = ['https://ec2.amazonaws.com', 'https://us-east-1.ec2.amazonaws.com', 'https://eu-west-1.ec2.amazonaws.com']
29
+ raise ArgumentError, "Invalid EC2_URL environment variable : #{EC2_URL}" unless VALID_HOSTS.include?(EC2_URL)
30
+ DEFAULT_HOST = URI.parse(EC2_URL).host
31
+ else
32
+ # default US host
33
+ DEFAULT_HOST = 'ec2.amazonaws.com'
34
+ end
35
+
36
+ # This is the version of the API as defined by Amazon Web Services
37
+ API_VERSION = '2008-12-01'
38
+
39
+ # Builds the canonical string for signing. This strips out all '&', '?', and '='
40
+ # from the query string to be signed.
41
+ # Note: The parameters in the path passed in must already be sorted in
42
+ # case-insensitive alphabetical order and must not be url encoded.
43
+ def EC2.canonical_string(params, host = DEFAULT_HOST, method="POST", base="/")
44
+ # Sort, and encode parameters into a canonical string.
45
+ sorted_params = params.sort {|x,y| x[0] <=> y[0]}
46
+ encoded_params = sorted_params.collect do |p|
47
+ encoded = (CGI::escape(p[0].to_s) +
48
+ "=" + CGI::escape(p[1].to_s))
49
+ # Ensure spaces are encoded as '%20', not '+'
50
+ encoded.gsub('+', '%20')
51
+ end
52
+ sigquery = encoded_params.join("&")
53
+
54
+ # Generate the request description string
55
+ req_desc =
56
+ method + "\n" +
57
+ host + "\n" +
58
+ base + "\n" +
59
+ sigquery
60
+
61
+ end
62
+
63
+ # Encodes the given string with the secret_access_key, by taking the
64
+ # hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
65
+ # url encode the result of that to protect the string if it's going to
66
+ # be used as a query string parameter.
67
+ def EC2.encode(secret_access_key, str, urlencode=true)
68
+ digest = OpenSSL::Digest::Digest.new('sha1')
69
+ b64_hmac =
70
+ Base64.encode64(
71
+ OpenSSL::HMAC.digest(digest, secret_access_key, str)).gsub("\n","")
72
+
73
+ if urlencode
74
+ return CGI::escape(b64_hmac)
75
+ else
76
+ return b64_hmac
77
+ end
78
+ end
79
+
80
+
81
+ #Introduction:
82
+ #
83
+ # The library exposes one main interface class, 'EC2::Base'.
84
+ # This class provides all the methods for using the EC2 service
85
+ # including the handling of header signing and other security issues .
86
+ # This class uses Net::HTTP to interface with the EC2 Query API interface.
87
+ #
88
+ #Required Arguments:
89
+ #
90
+ # :access_key_id => String (default : "")
91
+ # :secret_access_key => String (default : "")
92
+ #
93
+ #Optional Arguments:
94
+ #
95
+ # :use_ssl => Boolean (default : true)
96
+ # :server => String (default : 'ec2.amazonaws.com')
97
+ # :proxy_server => String (default : nil)
98
+ #
99
+ class Base
100
+
101
+ attr_reader :use_ssl, :server, :proxy_server, :port
102
+
103
+ def initialize( options = {} )
104
+
105
+ options = { :access_key_id => "",
106
+ :secret_access_key => "",
107
+ :use_ssl => true,
108
+ :server => DEFAULT_HOST,
109
+ :proxy_server => nil
110
+ }.merge(options)
111
+
112
+ @server = options[:server]
113
+ @proxy_server = options[:proxy_server]
114
+ @use_ssl = options[:use_ssl]
115
+
116
+ raise ArgumentError, "No :access_key_id provided" if options[:access_key_id].nil? || options[:access_key_id].empty?
117
+ raise ArgumentError, "No :secret_access_key provided" if options[:secret_access_key].nil? || options[:secret_access_key].empty?
118
+ raise ArgumentError, "No :use_ssl value provided" if options[:use_ssl].nil?
119
+ raise ArgumentError, "Invalid :use_ssl value provided, only 'true' or 'false' allowed" unless options[:use_ssl] == true || options[:use_ssl] == false
120
+ raise ArgumentError, "No :server provided" if options[:server].nil? || options[:server].empty?
121
+
122
+ if options[:port]
123
+ # user-specified port
124
+ @port = options[:port]
125
+ elsif @use_ssl
126
+ # https
127
+ @port = 443
128
+ else
129
+ # http
130
+ @port = 80
131
+ end
132
+
133
+ @access_key_id = options[:access_key_id]
134
+ @secret_access_key = options[:secret_access_key]
135
+
136
+ # Use proxy server if defined
137
+ # Based on patch by Mathias Dalheimer. 20070217
138
+ proxy = @proxy_server ? URI.parse(@proxy_server) : OpenStruct.new
139
+ @http = Net::HTTP::Proxy( proxy.host,
140
+ proxy.port,
141
+ proxy.user,
142
+ proxy.password).new(options[:server], @port)
143
+
144
+ @http.use_ssl = @use_ssl
145
+
146
+ # Don't verify the SSL certificates. Avoids SSL Cert warning in log on every GET.
147
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
148
+
149
+ end
150
+
151
+
152
+ private
153
+
154
+ # pathlist is a utility method which takes a key string and and array as input.
155
+ # It converts the array into a Hash with the hash key being 'Key.n' where
156
+ # 'n' increments by 1 for each iteration. So if you pass in args
157
+ # ("ImageId", ["123", "456"]) you should get
158
+ # {"ImageId.1"=>"123", "ImageId.2"=>"456"} returned.
159
+ def pathlist(key, arr)
160
+ params = {}
161
+ arr.each_with_index do |value, i|
162
+ params["#{key}.#{i+1}"] = value
163
+ end
164
+ params
165
+ end
166
+
167
+
168
+ # Make the connection to AWS EC2 passing in our request. This is generally called from
169
+ # within a 'Response' class object or one of its sub-classes so the response is interpreted
170
+ # in its proper context. See lib/EC2/responses.rb
171
+ def make_request(action, params, data='')
172
+
173
+ @http.start do
174
+
175
+ # remove any keys that have nil or empty values
176
+ params.reject! { |key, value| value.nil? or value.empty?}
177
+
178
+ params.merge!( {"Action" => action,
179
+ "SignatureVersion" => "2",
180
+ "SignatureMethod" => 'HmacSHA1',
181
+ "AWSAccessKeyId" => @access_key_id,
182
+ "Version" => API_VERSION,
183
+ "Timestamp"=>Time.now.getutc.iso8601} )
184
+
185
+ sig = get_aws_auth_param(params, @secret_access_key)
186
+
187
+ query = params.sort.collect do |param|
188
+ CGI::escape(param[0]) + "=" + CGI::escape(param[1])
189
+ end.join("&") + "&Signature=" + sig
190
+
191
+ req = Net::HTTP::Post.new("/")
192
+ req.content_type = 'application/x-www-form-urlencoded'
193
+ req['User-Agent'] = "github-amazon-ec2-ruby-gem"
194
+
195
+ response = @http.request(req, query)
196
+
197
+ # Make a call to see if we need to throw an error based on the response given by EC2
198
+ # All error classes are defined in EC2/exceptions.rb
199
+ ec2_error?(response)
200
+
201
+ return response
202
+
203
+ end
204
+
205
+ end
206
+
207
+ # Set the Authorization header using AWS signed header authentication
208
+ def get_aws_auth_param(params, secret_access_key)
209
+ canonical_string = EC2.canonical_string(params)
210
+ encoded_canonical = EC2.encode(secret_access_key, canonical_string)
211
+ end
212
+
213
+ # allow us to have a one line call in each method which will do all of the work
214
+ # in making the actual request to AWS.
215
+ def response_generator( options = {} )
216
+
217
+ options = {
218
+ :action => "",
219
+ :params => {}
220
+ }.merge(options)
221
+
222
+ raise ArgumentError, ":action must be provided to response_generator" if options[:action].nil? || options[:action].empty?
223
+
224
+ http_response = make_request(options[:action], options[:params])
225
+ http_xml = http_response.body
226
+ return Response.parse(:xml => http_xml)
227
+
228
+ end
229
+
230
+ # Raises the appropriate error if the specified Net::HTTPResponse object
231
+ # contains an Amazon EC2 error; returns +false+ otherwise.
232
+ def ec2_error?(response)
233
+
234
+ # return false if we got a HTTP 200 code,
235
+ # otherwise there is some type of error (40x,50x) and
236
+ # we should try to raise an appropriate exception
237
+ # from one of our exception classes defined in
238
+ # exceptions.rb
239
+ return false if response.is_a?(Net::HTTPSuccess)
240
+
241
+ # parse the XML document so we can walk through it
242
+ doc = REXML::Document.new(response.body)
243
+
244
+ # Check that the Error element is in the place we would expect.
245
+ # and if not raise a generic error exception
246
+ unless doc.root.elements['Errors'].elements['Error'].name == 'Error'
247
+ raise Error, "Unexpected error format. response.body is: #{response.body}"
248
+ end
249
+
250
+ # An valid error response looks like this:
251
+ # <?xml version="1.0"?><Response><Errors><Error><Code>InvalidParameterCombination</Code><Message>Unknown parameter: foo</Message></Error></Errors><RequestID>291cef62-3e86-414b-900e-17246eccfae8</RequestID></Response>
252
+ # AWS EC2 throws some exception codes that look like Error.SubError. Since we can't name classes this way
253
+ # we need to strip out the '.' in the error 'Code' and we name the error exceptions with this
254
+ # non '.' name as well.
255
+ error_code = doc.root.elements['Errors'].elements['Error'].elements['Code'].text.gsub('.', '')
256
+ error_message = doc.root.elements['Errors'].elements['Error'].elements['Message'].text
257
+
258
+ # Raise one of our specific error classes if it exists.
259
+ # otherwise, throw a generic EC2 Error with a few details.
260
+ if EC2.const_defined?(error_code)
261
+ raise EC2.const_get(error_code), error_message
262
+ else
263
+ raise EC2::Error, error_message
264
+ end
265
+
266
+ end
267
+
268
+ end
269
+
270
+ end