rsanheim-amazon-ec2 0.3.6.2

Sign up to get free protection for your applications and to get access to all the features.
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