amazon-ec2 0.1.0 → 0.2.0

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 CHANGED
@@ -12,8 +12,8 @@ require 'hoe'
12
12
  include FileUtils
13
13
  require File.join(File.dirname(__FILE__), 'lib', 'EC2', 'version')
14
14
 
15
- AUTHOR = ["Amazon Web Services LLC", "Glenn Rempe"] # can also be an array of Authors
16
- EMAIL = "grempe@rubyforge.org"
15
+ AUTHOR = ["Glenn Rempe"] # can also be an array of Authors
16
+ EMAIL = "glenn@elasticworkbench.com"
17
17
  DESCRIPTION = "An interface library that allows Ruby or Ruby on Rails applications to easily connect to the HTTP 'Query API' for the Amazon Web Services Elastic Compute Cloud (EC2) and manipulate server instances."
18
18
  GEM_NAME = "amazon-ec2" # what ppl will type to install your gem
19
19
 
@@ -74,10 +74,13 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
74
74
 
75
75
  # == Optional
76
76
  p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
77
- #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
78
- #p.spec_extras = {} # A hash of extra values to set in the gemspec.
77
+
78
+ # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
79
+ p.extra_deps = [['xml-simple', '>= 1.0.11'], ['mocha', '>= 0.4.0'], ['test-spec', '>= 0.3.0'], ['rcov', '>= 0.8.0.2'], ['syntax', '>= 1.0.0'], ['RedCloth', '>= 3.0.4']]
80
+
81
+ # A hash of extra values to set in the gemspec.
79
82
  p.spec_extras = {
80
- :extra_rdoc_files => ["README.txt", "History.txt"],
83
+ :extra_rdoc_files => ["README.txt", "History.txt", "License.txt"],
81
84
  :rdoc_options => RDOC_OPTS,
82
85
  :autorequire => "EC2"
83
86
  }
@@ -106,6 +109,9 @@ end
106
109
  desc 'Generate and upload website files'
107
110
  task :website => [:website_generate, :website_upload]
108
111
 
112
+ desc 'Run all types of tests that we have defined'
113
+ task :test_all => [:test, :test_deps, :test_coverage]
114
+
109
115
  desc 'Release the website and new gem version'
110
116
  task :deploy => [:check_version, :website, :release] do
111
117
  puts "Remember to create SVN tag:"
@@ -129,4 +135,8 @@ task :check_version do
129
135
  end
130
136
  end
131
137
 
138
+ desc 'Runs RCOV and generates test coverage reports which are stored in coverage/ dir'
139
+ task :test_coverage do
140
+ sh %{rcov -T test/*.rb}
141
+ end
132
142
 
@@ -0,0 +1,61 @@
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@elasticworkbench.com)
7
+ # Copyright:: Copyright (c) 2007 Glenn Rempe
8
+ # License:: Distributes under the same terms as Ruby
9
+ # Home:: http://amazon-ec2.rubyforge.org
10
+ #++
11
+
12
+ require 'rubygems'
13
+ require File.dirname(__FILE__) + '/../lib/EC2'
14
+
15
+ # pull these from the local shell environment variables set in ~/.bash_login
16
+ # or using appropriate methods specific to your login shell.
17
+ #
18
+ # e.g. in ~/.bash_login
19
+ #
20
+ # # For amazon-ec2 and amazon s3 ruby gems
21
+ # export AMAZON_ACCESS_KEY_ID="FOO"
22
+ # export AMAZON_SECRET_ACCESS_KEY="BAR"
23
+
24
+ ACCESS_KEY_ID = ENV['AMAZON_ACCESS_KEY_ID']
25
+ SECRET_ACCESS_KEY = ENV['AMAZON_SECRET_ACCESS_KEY']
26
+
27
+ if ACCESS_KEY_ID.nil? || ACCESS_KEY_ID.empty?
28
+ puts "Error : You must add the shell environment variables AMAZON_ACCESS_KEY_ID and AMAZON_SECRET_ACCESS_KEY before calling #{$0}!"
29
+ exit
30
+ end
31
+
32
+ ec2 = EC2::Base.new( :access_key_id => ACCESS_KEY_ID, :secret_access_key => SECRET_ACCESS_KEY )
33
+
34
+ puts "----- GEM Version -----"
35
+ puts EC2::VERSION::STRING
36
+
37
+ puts "----- ec2.methods.sort -----"
38
+ p ec2.methods.sort
39
+
40
+ puts "----- listing images owned by 'amazon' -----"
41
+ ec2.describe_images(:owner_id => "amazon").imagesSet.item.each do |image|
42
+ image.members.each do |member|
43
+ puts "#{member} => #{image[member]}"
44
+ end
45
+ end
46
+
47
+ puts "----- listing all running instances -----"
48
+ puts ec2.describe_instances()
49
+
50
+ puts "----- creating a security group -----"
51
+ puts ec2.create_security_group(:group_name => "ec2-example-rb-test-group", :group_description => "ec-example.rb test group description.")
52
+
53
+ puts "----- listing security groups -----"
54
+ puts ec2.describe_security_groups()
55
+
56
+ puts "----- deleting a security group -----"
57
+ puts ec2.delete_security_group(:group_name => "ec2-example-rb-test-group")
58
+
59
+ puts "----- listing my keypairs (verbose mode) -----"
60
+ puts ec2.describe_keypairs()
61
+
data/bin/ec2sh ADDED
@@ -0,0 +1,73 @@
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@elasticworkbench.com)
7
+ # Copyright:: Copyright (c) 2007 Glenn Rempe
8
+ # License:: Distributes under the same terms as Ruby
9
+ # Home:: http://amazon-ec2.rubyforge.org
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. The #xml data is available for each response
42
+ which allows you to view the full and complete XML response returned by
43
+ EC2 without any parsing applied. This is useful for viewing the
44
+ hierarchy of an entire response in a friendly way (if XML is friendly
45
+ to you!). Understanding the hierarchy of the XML response is critical
46
+ to making effective use of this library.
47
+
48
+ Examples to try:
49
+
50
+ returns : all ec2 public methods
51
+ >> @ec2.methods.sort
52
+
53
+ returns : a string representation of ALL images
54
+ >> @ec2.describe_images.to_s
55
+
56
+ returns : an Array of EC2::Response objects, each an EC2 image and its data
57
+ >> @ec2.describe_images.imagesSet.item
58
+ >> @ec2.describe_images.imagesSet.item[0] (an OpenStruct of a single item in that array)
59
+ >> @ec2.describe_images.imagesSet.item[0].to_s (a String representation of that OpenStruct item)
60
+
61
+ returns : an XML representation of all images
62
+ >> puts @ec2.describe_images.xml
63
+
64
+ returns : an XML representation of all images owned by Amazon
65
+ >> puts @ec2.describe_images(:owner_id => 'amazon').xml
66
+
67
+ MESSAGE
68
+
69
+ puts welcome_message
70
+ exec "#{irb_name} -r #{ec2_lib} -r #{setup} --simple-prompt"
71
+ else
72
+ puts "You must define AMAZON_ACCESS_KEY_ID and AMAZON_SECRET_ACCESS_KEY as shell environment variables before running #{$0}!"
73
+ end
data/bin/setup.rb ADDED
@@ -0,0 +1,19 @@
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@elasticworkbench.com)
7
+ # Copyright:: Copyright (c) 2007 Glenn Rempe
8
+ # License:: Distributes under the same terms as Ruby
9
+ # Home:: http://amazon-ec2.rubyforge.org
10
+ #++
11
+
12
+ if ENV['AMAZON_ACCESS_KEY_ID'] && ENV['AMAZON_SECRET_ACCESS_KEY']
13
+ @ec2 = EC2::Base.new(
14
+ :access_key_id => ENV['AMAZON_ACCESS_KEY_ID'],
15
+ :secret_access_key => ENV['AMAZON_SECRET_ACCESS_KEY']
16
+ )
17
+ end
18
+
19
+ include EC2
data/lib/EC2.rb CHANGED
@@ -1,31 +1,17 @@
1
- # Amazon Web Services EC2 Query API Ruby Library
2
- # This library has been packaged as a Ruby Gem
3
- # by Glenn Rempe ( grempe @nospam@ rubyforge.org ).
4
- #
5
- # Source code and gem hosted on RubyForge
6
- # under the Ruby License as of 12/14/2006:
7
- # http://amazon-ec2.rubyforge.org
1
+ #--
2
+ # Amazon Web Services EC2 Query API Ruby library
3
+ #
4
+ # Ruby Gem Name:: amazon-ec2
5
+ # Author:: Glenn Rempe (mailto:glenn@elasticworkbench.com)
6
+ # Copyright:: Copyright (c) 2007 Glenn Rempe
7
+ # License:: Distributes under the same terms as Ruby
8
+ # Home:: http://amazon-ec2.rubyforge.org
9
+ #++
8
10
 
9
- # Original Amazon Web Services Notice
10
- # This software code is made available "AS IS" without warranties of any
11
- # kind. You may copy, display, modify and redistribute the software
12
- # code either by itself or as incorporated into your code; provided that
13
- # you do not remove any proprietary notices. Your use of this software
14
- # code is at your own risk and you waive any claim against Amazon Web
15
- # Services LLC or its affiliates with respect to your use of this software
16
- # code. (c) 2006 Amazon Web Services LLC or its affiliates. All rights
17
- # reserved.
18
-
19
- require 'base64'
20
- require 'cgi'
21
- require 'openssl'
22
- require 'digest/sha1'
23
- require 'net/https'
24
- require 'rexml/document'
25
- require 'time'
11
+ %w[ base64 cgi openssl digest/sha1 net/https rexml/document time ostruct ].each { |f| require f }
26
12
 
27
13
  # Require any lib files that we have bundled with this Ruby Gem in the lib/EC2 directory.
28
- # Parts of the EC2 module and AWSAuthConnection class are broken out into separate
14
+ # Parts of the EC2 module and Base class are broken out into separate
29
15
  # files for maintainability and are organized by the functional groupings defined
30
16
  # in the EC2 API developers guide.
31
17
  Dir[File.join(File.dirname(__FILE__), 'EC2/**/*.rb')].sort.each { |lib| require lib }
@@ -35,11 +21,8 @@ module EC2
35
21
  # Which host FQDN will we connect to for all API calls to AWS?
36
22
  DEFAULT_HOST = 'ec2.amazonaws.com'
37
23
 
38
- # Define the ports to use for SSL(true) or Non-SSL(false) connections.
39
- PORTS_BY_SECURITY = { true => 443, false => 80 }
40
-
41
24
  # This is the version of the API as defined by Amazon Web Services
42
- API_VERSION = '2007-01-03'
25
+ API_VERSION = '2007-01-19'
43
26
 
44
27
  # This release version is passed in with each request as part
45
28
  # of the HTTP 'User-Agent' header. Set this be the same value
@@ -60,15 +43,15 @@ module EC2
60
43
  return buf
61
44
  end
62
45
 
63
- # Encodes the given string with the aws_secret_access_key, by taking the
46
+ # Encodes the given string with the secret_access_key, by taking the
64
47
  # hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
65
48
  # url encode the result of that to protect the string if it's going to
66
49
  # be used as a query string parameter.
67
- def EC2.encode(aws_secret_access_key, str, urlencode=true)
50
+ def EC2.encode(secret_access_key, str, urlencode=true)
68
51
  digest = OpenSSL::Digest::Digest.new('sha1')
69
52
  b64_hmac =
70
53
  Base64.encode64(
71
- OpenSSL::HMAC.digest(digest, aws_secret_access_key, str)).strip
54
+ OpenSSL::HMAC.digest(digest, secret_access_key, str)).strip
72
55
 
73
56
  if urlencode
74
57
  return CGI::escape(b64_hmac)
@@ -78,27 +61,62 @@ module EC2
78
61
  end
79
62
 
80
63
 
81
- # The library exposes one main interface class, 'AWSAuthConnection'.
82
- # This class performs all the operations for using the EC2 service
83
- # including header signing. This class uses Net::HTTP to interface
84
- # with EC2 Query API interface.
85
- class AWSAuthConnection
64
+ #Introduction:
65
+ #
66
+ # The library exposes one main interface class, 'EC2::Base'.
67
+ # This class provides all the methods for using the EC2 service
68
+ # including the handling of header signing and other security issues .
69
+ # This class uses Net::HTTP to interface with the EC2 Query API interface.
70
+ #
71
+ #Required Arguments:
72
+ #
73
+ # :access_key_id => String (default : "")
74
+ # :secret_access_key => String (default : "")
75
+ #
76
+ #Optional Arguments:
77
+ #
78
+ # :use_ssl => Boolean (default : true)
79
+ # :server => String (default : 'ec2.amazonaws.com')
80
+ #
81
+ class Base
86
82
 
87
- # Allow viewing, or turning on and off, the verbose mode of the connection class.
88
- # If 'true' some 'puts' are done to view variable contents.
89
- attr_accessor :verbose
83
+ attr_reader :use_ssl, :server, :port
90
84
 
91
- def initialize(aws_access_key_id, aws_secret_access_key, is_secure=true,
92
- server=DEFAULT_HOST, port=PORTS_BY_SECURITY[is_secure])
85
+ def initialize( options = {} )
93
86
 
94
- @aws_access_key_id = aws_access_key_id
95
- @aws_secret_access_key = aws_secret_access_key
96
- @http = Net::HTTP.new(server, port)
97
- @http.use_ssl = is_secure
98
- # Don't verify the SSL certificates. Avoids SSL Cert warning on every GET.
99
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
87
+ options = { :access_key_id => "",
88
+ :secret_access_key => "",
89
+ :use_ssl => true,
90
+ :server => DEFAULT_HOST
91
+ }.merge(options)
92
+
93
+ @server = options[:server]
94
+ @use_ssl = options[:use_ssl]
95
+
96
+ raise ArgumentError, "No :access_key_id provided" if options[:access_key_id].nil? || options[:access_key_id].empty?
97
+ raise ArgumentError, "No :secret_access_key provided" if options[:secret_access_key].nil? || options[:secret_access_key].empty?
98
+ raise ArgumentError, "No :use_ssl value provided" if options[:use_ssl].nil?
99
+ raise ArgumentError, "Invalid :use_ssl value provided, only 'true' or 'false' allowed" unless options[:use_ssl] == true || options[:use_ssl] == false
100
+ raise ArgumentError, "No :server provided" if options[:server].nil? || options[:server].empty?
101
+
102
+
103
+ # based on the :use_ssl boolean, determine which port we should connect to
104
+ case @use_ssl
105
+ when true
106
+ # https
107
+ @port = 443
108
+ when false
109
+ # http
110
+ @port = 80
111
+ end
112
+
113
+ @access_key_id = options[:access_key_id]
114
+ @secret_access_key = options[:secret_access_key]
115
+ @http = Net::HTTP.new(options[:server], @port)
116
+ @http.use_ssl = @use_ssl
100
117
 
101
- @verbose = false
118
+ # Don't verify the SSL certificates. Avoids SSL Cert warning in log on every GET.
119
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
102
120
 
103
121
  end
104
122
 
@@ -126,20 +144,23 @@ module EC2
126
144
 
127
145
  @http.start do
128
146
 
129
- params.merge!( {"Action"=>action, "SignatureVersion"=>"1", "AWSAccessKeyId"=>@aws_access_key_id,
130
- "Version"=>API_VERSION, "Timestamp"=>Time.now.getutc.iso8601,} )
131
- p params if @verbose
147
+ # remove any keys that have nil or empty values
148
+ params.reject! { |key, value| value.nil? or value.empty?}
149
+
150
+ params.merge!( {"Action" => action,
151
+ "SignatureVersion" => "1",
152
+ "AWSAccessKeyId" => @access_key_id,
153
+ "Version" => API_VERSION,
154
+ "Timestamp"=>Time.now.getutc.iso8601} )
132
155
 
133
156
  sigpath = "?" + params.sort_by { |param| param[0].downcase }.collect { |param| param.join("=") }.join("&")
134
157
 
135
- sig = get_aws_auth_param(sigpath, @aws_secret_access_key)
158
+ sig = get_aws_auth_param(sigpath, @secret_access_key)
136
159
 
137
160
  path = "?" + params.sort.collect do |param|
138
161
  CGI::escape(param[0]) + "=" + CGI::escape(param[1])
139
162
  end.join("&") + "&Signature=" + sig
140
163
 
141
- puts path if @verbose
142
-
143
164
  req = Net::HTTP::Get.new("/#{path}")
144
165
 
145
166
  # Ruby will automatically add a random content-type on some verbs, so
@@ -149,20 +170,80 @@ module EC2
149
170
  req['Content-Type'] ||= ''
150
171
  req['User-Agent'] = "rubyforge-amazon-ec2-ruby-gem-query-api v-#{RELEASE_VERSION}"
151
172
 
152
- data = nil unless req.request_body_permitted?
153
- @http.request(req, data)
173
+ #data = nil unless req.request_body_permitted?
174
+ response = @http.request(req, nil)
175
+
176
+ # Make a call to see if we need to throw an error based on the response given by EC2
177
+ # All error classes are defined in EC2/exceptions.rb
178
+ ec2_error?(response)
179
+
180
+ return response
154
181
 
155
182
  end
156
183
 
157
184
  end
158
185
 
159
-
160
186
  # Set the Authorization header using AWS signed header authentication
161
- def get_aws_auth_param(path, aws_secret_access_key)
187
+ def get_aws_auth_param(path, secret_access_key)
162
188
  canonical_string = EC2.canonical_string(path)
163
- encoded_canonical = EC2.encode(aws_secret_access_key, canonical_string)
189
+ encoded_canonical = EC2.encode(secret_access_key, canonical_string)
164
190
  end
165
-
191
+
192
+ # allow us to have a one line call in each method which will do all of the work
193
+ # in making the actual request to AWS.
194
+ def response_generator( options = {} )
195
+
196
+ options = {
197
+ :action => "",
198
+ :params => {}
199
+ }.merge(options)
200
+
201
+ raise ArgumentError, ":action must be provided to response_generator" if options[:action].nil? || options[:action].empty?
202
+
203
+ http_response = make_request(options[:action], options[:params])
204
+ http_xml = http_response.body
205
+ return Response.parse(:xml => http_xml)
206
+
207
+ end
208
+
209
+ # Raises the appropriate error if the specified Net::HTTPResponse object
210
+ # contains an Amazon EC2 error; returns +false+ otherwise.
211
+ def ec2_error?(response)
212
+
213
+ # return false if we got a HTTP 200 code,
214
+ # otherwise there is some type of error (40x,50x) and
215
+ # we should try to raise an appropriate exception
216
+ # from one of our exception classes defined in
217
+ # exceptions.rb
218
+ return false if response.is_a?(Net::HTTPSuccess)
219
+
220
+ # parse the XML document so we can walk through it
221
+ doc = REXML::Document.new(response.body)
222
+
223
+ # Check that the Error element is in the place we would expect.
224
+ # and if not raise a generic error exception
225
+ unless doc.root.elements['Errors'].elements['Error'].name == 'Error'
226
+ raise Error, "Unexpected error format. response.body is: #{response.body}"
227
+ end
228
+
229
+ # An valid error response looks like this:
230
+ # <?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>
231
+ # AWS EC2 throws some exception codes that look like Error.SubError. Since we can't name classes this way
232
+ # we need to strip out the '.' in the error 'Code' and we name the error exceptions with this
233
+ # non '.' name as well.
234
+ error_code = doc.root.elements['Errors'].elements['Error'].elements['Code'].text.gsub('.', '')
235
+ error_message = doc.root.elements['Errors'].elements['Error'].elements['Message'].text
236
+
237
+ # Raise one of our specific error classes if it exists.
238
+ # otherwise, throw a generic EC2 Error with a few details.
239
+ if EC2.const_defined?(error_code)
240
+ raise EC2.const_get(error_code), error_message
241
+ else
242
+ raise Error, "This is an undefined error code which needs to be added to exceptions.rb : error_code => #{error_code} : error_message => #{error_message}"
243
+ end
244
+
245
+ end
246
+
166
247
  end
167
248
 
168
249
  end