adamwiggins-sumo 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.
Files changed (8) hide show
  1. data/README.rdoc +122 -0
  2. data/Rakefile +23 -0
  3. data/VERSION +1 -0
  4. data/bin/sumo +173 -0
  5. data/lib/sumo.rb +268 -0
  6. data/spec/base.rb +21 -0
  7. data/spec/sumo_spec.rb +31 -0
  8. metadata +60 -0
data/README.rdoc ADDED
@@ -0,0 +1,122 @@
1
+ = Tired of wrestling with server provisioning? Sumo!
2
+
3
+ Want to fire up a one-off EC2 instance, pronto? ec2-run-instances got you down? Try Sumo.
4
+
5
+ $ sumo launch
6
+ ---> Launching instance... i-4f809c26 (1.5s)
7
+ ---> Acquiring hostname... ec2-67-202-17-178.compute-1.amazonaws.com (26.7s)
8
+
9
+ Logging you in via ssh. Type 'exit' or Ctrl-D to return to your local system.
10
+ ------------------------------------------------------------------------------
11
+ Linux domU-12-31-39-04-31-37 2.6.21.7-2.fc8xen #1 SMP Fri Feb 15 12:39:36 EST 2008 i686
12
+ ...
13
+ root@domU-12-31-39-04-31-37:~#
14
+
15
+ Later...
16
+
17
+ $ sumo terminate
18
+ ec2-67-202-17-178.compute-1.amazonaws.com scheduled for termination
19
+
20
+ You can manage multiple instances via "sumo list" and specifying hostname or instance id as arguments to the ssh or terminate commands.
21
+
22
+ == Service installation with Chef
23
+
24
+ The launch command takes an argument, which is a server role (from roles/#{role}.json inside your cookbooks repo):
25
+
26
+ $ sumo launch redis
27
+ ---> Launch instance... i-b96c73d0 (1.3s)
28
+ ---> Acquire hostname... ec2-75-101-191-220.compute-1.amazonaws.com (36.1s)
29
+ ---> Wait for ssh... done (9.0s)
30
+ ---> Bootstrap chef... done (61.3s)
31
+ ---> Setup redis... done (11.9s)
32
+ ---> Opening firewall... ports 6379 (5.2s)
33
+
34
+ Your instance is exporting the following resources:
35
+ Redis: redis://:8452cdd98f428c972f08@ec2-75-101-191-220.compute-1.amazonaws.com:6379/0
36
+
37
+ The instance can assume multiple roles if you like:
38
+
39
+ $ sumo launch redis,solr,couchdb
40
+
41
+ == Setup
42
+
43
+ Dependencies:
44
+
45
+ $ sudo gem install amazon-ec2 thor
46
+
47
+ Then create ~/.sumo/config.yml containing:
48
+
49
+ ---
50
+ access_id: <your amazon access key id>
51
+ access_secret: <your amazon secret access key>
52
+
53
+ Optional config you can include any of the following in your config.yml:
54
+
55
+ user: root
56
+ ami: ami-ed46a784
57
+ availability_zone: us-east-1b
58
+ cookbooks_url: git://github.com/adamwiggins/chef-cookbooks.git
59
+
60
+ You'll need Bacon and Mocha if you want to run the specs, and Jewler if you want to create gems.
61
+
62
+ == Managing volumes
63
+
64
+ Create and attach a volume to your running instance:
65
+
66
+ $ sumo create_volume
67
+ ---> Create 5MB volume... vol-8a9c6ae3 (1.1s)
68
+ $ sumo volumes
69
+ vol-8a9c6ae3 5MB available
70
+ $ sumo attach
71
+ ---> Attach vol-8a9c6ae3 to i-bc32cbd4 as /dev/sdc1... done (0.6s)
72
+
73
+ Log in to format and mount the volume:
74
+
75
+ $ sumo ssh
76
+ root@ip-10-251-122-175:~# mkfs.ext3 /dev/sdc1
77
+ mke2fs 1.41.4 (27-Jan-2009)
78
+ Filesystem label=
79
+ OS type: Linux
80
+ Block size=4096 (log=2)
81
+ ...
82
+ $ mkdir /myvol
83
+ $ mount /dev/sdc1 /myvol
84
+ $ echo "I'm going to persist to a volume" > /myvol/hello.txt
85
+
86
+ To detach from a running instance (perhaps so you can attach elsewhere):
87
+
88
+ $ sumo detatch
89
+ ---> Detach vol-8a9c6ae3... done (0.6s)
90
+
91
+ Destroy it if you no longer want the data stored on it:
92
+
93
+ $ sumo destroy_volume
94
+ ---> Destroy volume... done (0.8s)
95
+
96
+ == Some details you might want to know
97
+
98
+ Sumo creates its own keypair named sumo, which is stored in ~/.ssh/keypair.pem. Amazon doesn't let you upload your own ssh public key, which is lame, so this is the best option for making the launch-and-connect process a single step.
99
+
100
+ It will also create an Amazon security group called sumo, so that it can lower the firewall for services you configure via cookbook roles.
101
+
102
+ If you run any production machines from your EC2 account, I recommend setting up a separate account for use with Sumo. It does not prompt for confirmation when terminating an instance or differentiate between instances started by it vs. instances started by other tools.
103
+
104
+ == Anti-features
105
+
106
+ Sumo is not a cloud management tool, a monitor tool, or anything more than a way to get an instance up right quick. If you're looking for a way to manage a cluster of production instances, try one of these fine tools.
107
+
108
+ * Pool Party
109
+ * RightScale
110
+ * Engine Yard Cloud
111
+ * Cloudkick
112
+
113
+ == Meta
114
+
115
+ Created by Adam Wiggins
116
+
117
+ Patches contributed by Orion Henry, Blake Mizerany, and Jesse Newland
118
+
119
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
120
+
121
+ http://github.com/adamwiggins/sumo
122
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'jeweler'
2
+
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "sumo"
5
+ s.description = "A no-hassle way to launch one-off EC2 instances from the command line"
6
+ s.summary = s.description
7
+ s.author = "Adam Wiggins"
8
+ s.email = "adam@heroku.com"
9
+ s.homepage = "http://github.com/adamwiggins/sumo"
10
+ s.rubyforge_project = "sumo"
11
+ s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
12
+ s.executables = %w(sumo)
13
+ end
14
+
15
+ Jeweler::RubyforgeTasks.new
16
+
17
+ desc 'Run specs'
18
+ task :spec do
19
+ sh 'bacon -s spec/*_spec.rb'
20
+ end
21
+
22
+ task :default => :spec
23
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/bin/sumo ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/sumo'
4
+
5
+ require 'thor'
6
+
7
+ class CLI < Thor
8
+ desc "launch [<role>]", "launch an instance as role, or omit to ssh to vanilla instance"
9
+ def launch(role=nil)
10
+ id = task("Launch instance") { sumo.launch }
11
+ host = task("Acquire hostname") { sumo.wait_for_hostname(id) }
12
+ task("Wait for ssh") { sumo.wait_for_ssh(host) }
13
+
14
+ if role
15
+ task("Bootstrap chef") { sumo.bootstrap_chef(host) }
16
+ role.split(',').each do |role|
17
+ task("Setup #{role}") { sumo.setup_role(host, role) }
18
+ end
19
+
20
+ resources = sumo.resources(host)
21
+ unless resources.empty?
22
+ task("Open firewall") do
23
+ ports = resources.map { |r| r.match(/:(\d+)\//)[1] }
24
+ ports.each { |port| sumo.open_firewall(port) }
25
+ "ports " + ports.join(", ")
26
+ end
27
+ end
28
+
29
+ puts
30
+ display_resources(host)
31
+ else
32
+ puts "\nLogging you in via ssh. Type 'exit' or Ctrl-D to return to your local system."
33
+ puts '-' * 78
34
+ connect_ssh(host)
35
+ end
36
+ end
37
+
38
+ desc "ssh [<instance_id or hostname>]", "ssh to a specified instance or first available"
39
+ def ssh(id=nil)
40
+ inst = sumo.find(id) || sumo.running.first || abort("No running instances")
41
+ hostname = inst[:hostname] || wait_for_hostname(inst[:instance_id])
42
+ connect_ssh hostname
43
+ end
44
+
45
+ desc "resources [<instance_id or hostname>]", "show resources exported by an instance"
46
+ def resources(id=nil)
47
+ inst = sumo.find(id) || sumo.running.first || abort("No running instances")
48
+ hostname = inst[:hostname] || wait_for_hostname(inst[:instance_id])
49
+ display_resources(inst[:hostname])
50
+ end
51
+
52
+ desc "bootstrap", "bootstrap chef and cookbooks"
53
+ def bootstrap(id=nil)
54
+ inst = sumo.find(id) || sumo.running.first || abort("No running instances")
55
+ task "Bootstrap chef" do
56
+ sumo.bootstrap_chef(inst[:hostname])
57
+ end
58
+ end
59
+
60
+ desc "role", "setup instance as a role"
61
+ def role(role, id=nil)
62
+ inst = sumo.find(id) || sumo.running.first || abort("No running instances")
63
+ task "Setup #{role}" do
64
+ sumo.setup_role(inst[:hostname], role)
65
+ end
66
+ end
67
+
68
+ desc "list", "list running instances"
69
+ def list
70
+ sumo.list.each do |inst|
71
+ printf "%-50s %-12s %s\n", inst[:hostname], inst[:instance_id], inst[:status]
72
+ end
73
+ end
74
+
75
+ desc "console [<instance_id or hostname>]", "get console output for instance or first available"
76
+ def console(id=nil)
77
+ inst = sumo.find(id) || (sumo.running | sumo.pending).first || abort("No running or pending instances")
78
+
79
+ puts sumo.console_output(inst[:instance_id]).inspect
80
+ end
81
+
82
+ desc "terminate [<instance_id or hostname>]", "terminate specified instance or first available"
83
+ def terminate(id=nil)
84
+ inst = sumo.find(id) || (sumo.running | sumo.pending).first || abort("No running or pending instances")
85
+
86
+ sumo.terminate(inst[:instance_id])
87
+ puts "#{inst[:hostname] || inst[:instance_id]} scheduled for termination"
88
+ end
89
+
90
+ desc "terminate_all", "terminate all instances"
91
+ def terminate_all
92
+ instances = (sumo.running | sumo.pending)
93
+ abort("No running or pending instances") if instances.empty?
94
+ instances.each do |inst|
95
+ sumo.terminate(inst[:instance_id])
96
+ puts "#{inst[:hostname] || inst[:instance_id]} scheduled for termination"
97
+ end
98
+ end
99
+
100
+ desc "volumes", "list all volumes"
101
+ def volumes
102
+ sumo.volumes.each do |v|
103
+ printf "%-10s %4sMB %10s %15s %15s\n", v[:volume_id], v[:size], v[:status], v[:instance], v[:device]
104
+ end
105
+ end
106
+
107
+ desc "create_volume [<megabytes>]", "create a volume"
108
+ def create_volume(size=5)
109
+ task("Create #{size}MB volume") { sumo.create_volume(size) }
110
+ end
111
+
112
+ desc "destroy_volume [<volume_id>]", "destroy a volume"
113
+ def destroy_volume(volume=nil)
114
+ vol_id = (sumo.find_volume(volume) || sumo.nondestroyed_volumes.first || abort("No volumes"))[:volume_id]
115
+ task("Destroy volume") { sumo.destroy_volume(vol_id) }
116
+ end
117
+
118
+ desc "attach [<volume_id>] [<instance_id or hostname>] [<device>]", "attach volume to running instance"
119
+ def attach(volume=nil, inst_id=nil, device=nil)
120
+ vol_id = (sumo.find_volume(volume) || sumo.available_volumes.first || abort("No available volumes"))[:volume_id]
121
+ inst_id = (sumo.find(inst_id) || sumo.running.first || abort("No running instances"))[:instance_id]
122
+ device ||= '/dev/sdc1'
123
+ task("Attach #{vol_id} to #{inst_id} as #{device}") do
124
+ sumo.attach(vol_id, inst_id, device)
125
+ end
126
+ end
127
+
128
+ desc "detach [<volume_id>]", "detach volume from instance"
129
+ def detach(volume=nil)
130
+ vol_id = (sumo.find_volume(volume) || sumo.attached_volumes.first || abort("No attached volumes"))[:volume_id]
131
+ task("Detach #{vol_id}") { sumo.detach(vol_id) }
132
+ end
133
+
134
+ no_tasks do
135
+ def sumo
136
+ @sumo ||= Sumo.new
137
+ end
138
+
139
+ def config
140
+ sumo.config
141
+ end
142
+
143
+ def task(msg, &block)
144
+ printf "---> %-24s ", "#{msg}..."
145
+ start = Time.now
146
+ result = block.call || 'done'
147
+ finish = Time.now
148
+ time = sprintf("%0.1f", finish - start)
149
+ puts "#{result} (#{time}s)"
150
+ result
151
+ end
152
+
153
+ def connect_ssh(hostname)
154
+ sumo.wait_for_ssh(hostname)
155
+ system "ssh -i #{sumo.keypair_file} #{config['user']}@#{hostname}"
156
+ if $?.success?
157
+ puts "\nType 'sumo terminate' if you're done with this instance."
158
+ end
159
+ end
160
+
161
+ def display_resources(host)
162
+ resources = sumo.resources(host)
163
+ unless resources.empty?
164
+ puts "Your instance is exporting the following resources:"
165
+ resources.each do |resource|
166
+ puts " #{resource}"
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ CLI.start
data/lib/sumo.rb ADDED
@@ -0,0 +1,268 @@
1
+ require 'AWS'
2
+ require 'yaml'
3
+ require 'socket'
4
+
5
+ class Sumo
6
+ def launch
7
+ ami = config['ami']
8
+ raise "No AMI selected" unless ami
9
+
10
+ create_keypair unless File.exists? keypair_file
11
+
12
+ create_security_group
13
+ open_firewall(22)
14
+
15
+ result = ec2.run_instances(
16
+ :image_id => ami,
17
+ :instance_type => config['instance_size'] || 'm1.small',
18
+ :key_name => 'sumo',
19
+ :group_id => [ 'sumo' ],
20
+ :availability_zone => config['availability_zone'],
21
+ )
22
+ result.instancesSet.item[0].instanceId
23
+ end
24
+
25
+ def list
26
+ @list ||= fetch_list
27
+ end
28
+
29
+ def volumes
30
+ result = ec2.describe_volumes
31
+ return [] unless result.volumeSet
32
+
33
+ result.volumeSet.item.map do |row|
34
+ {
35
+ :volume_id => row["volumeId"],
36
+ :size => row["size"],
37
+ :status => row["status"],
38
+ :device => (row["attachmentSet"]["item"].first["device"] rescue ""),
39
+ :instance_id => (row["attachmentSet"]["item"].first["instanceId"] rescue ""),
40
+ }
41
+ end
42
+ end
43
+
44
+ def available_volumes
45
+ volumes.select { |vol| vol[:status] == 'available' }
46
+ end
47
+
48
+ def attached_volumes
49
+ volumes.select { |vol| vol[:status] == 'in-use' }
50
+ end
51
+
52
+ def nondestroyed_volumes
53
+ volumes.select { |vol| vol[:status] != 'deleting' }
54
+ end
55
+
56
+ def attach(volume, instance, device)
57
+ result = ec2.attach_volume(
58
+ :volume_id => volume,
59
+ :instance_id => instance,
60
+ :device => device
61
+ )
62
+ "done"
63
+ end
64
+
65
+ def detach(volume)
66
+ result = ec2.detach_volume(:volume_id => volume, :force => "true")
67
+ "done"
68
+ end
69
+
70
+ def create_volume(size)
71
+ result = ec2.create_volume(
72
+ :availability_zone => config['availability_zone'],
73
+ :size => size.to_s
74
+ )
75
+ result["volumeId"]
76
+ end
77
+
78
+ def destroy_volume(volume)
79
+ ec2.delete_volume(:volume_id => volume)
80
+ "done"
81
+ end
82
+
83
+ def fetch_list
84
+ result = ec2.describe_instances
85
+ return [] unless result.reservationSet
86
+
87
+ instances = []
88
+ result.reservationSet.item.each do |r|
89
+ r.instancesSet.item.each do |item|
90
+ instances << {
91
+ :instance_id => item.instanceId,
92
+ :status => item.instanceState.name,
93
+ :hostname => item.dnsName
94
+ }
95
+ end
96
+ end
97
+ instances
98
+ end
99
+
100
+ def find(id_or_hostname)
101
+ return unless id_or_hostname
102
+ id_or_hostname = id_or_hostname.strip.downcase
103
+ list.detect do |inst|
104
+ inst[:hostname] == id_or_hostname or
105
+ inst[:instance_id] == id_or_hostname or
106
+ inst[:instance_id].gsub(/^i-/, '') == id_or_hostname
107
+ end
108
+ end
109
+
110
+ def find_volume(volume_id)
111
+ return unless volume_id
112
+ volume_id = volume_id.strip.downcase
113
+ volumes.detect do |volume|
114
+ inst[:volume_id] == volume_id or
115
+ inst[:volume_id].gsub(/^vol-/, '') == volume_id
116
+ end
117
+ end
118
+
119
+ def running
120
+ list_by_status('running')
121
+ end
122
+
123
+ def pending
124
+ list_by_status('pending')
125
+ end
126
+
127
+ def list_by_status(status)
128
+ list.select { |i| i[:status] == status }
129
+ end
130
+
131
+ def instance_info(instance_id)
132
+ fetch_list.detect do |inst|
133
+ inst[:instance_id] == instance_id
134
+ end
135
+ end
136
+
137
+ def wait_for_hostname(instance_id)
138
+ raise ArgumentError unless instance_id and instance_id.match(/^i-/)
139
+ loop do
140
+ if inst = instance_info(instance_id)
141
+ if hostname = inst[:hostname]
142
+ return hostname
143
+ end
144
+ end
145
+ sleep 1
146
+ end
147
+ end
148
+
149
+ def wait_for_ssh(hostname)
150
+ raise ArgumentError unless hostname
151
+ loop do
152
+ begin
153
+ Timeout::timeout(4) do
154
+ TCPSocket.new(hostname, 22)
155
+ return
156
+ end
157
+ rescue SocketError, Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
158
+ end
159
+ end
160
+ end
161
+
162
+ def bootstrap_chef(hostname)
163
+ commands = [
164
+ 'apt-get update',
165
+ 'apt-get autoremove -y',
166
+ 'apt-get install -y ruby ruby-dev rubygems git-core',
167
+ 'gem sources -a http://gems.opscode.com',
168
+ 'gem install chef ohai --no-rdoc --no-ri',
169
+ "git clone #{config['cookbooks_url']}",
170
+ ]
171
+ ssh(hostname, commands)
172
+ end
173
+
174
+ def setup_role(hostname, role)
175
+ commands = [
176
+ "cd chef-cookbooks",
177
+ "/var/lib/gems/1.8/bin/chef-solo -c config.json -j roles/#{role}.json"
178
+ ]
179
+ ssh(hostname, commands)
180
+ end
181
+
182
+ def ssh(hostname, cmds)
183
+ IO.popen("ssh -i #{keypair_file} #{config['user']}@#{hostname} > ~/.sumo/ssh.log 2>&1", "w") do |pipe|
184
+ pipe.puts cmds.join(' && ')
185
+ end
186
+ unless $?.success?
187
+ abort "failed\nCheck ~/.sumo/ssh.log for the output"
188
+ end
189
+ end
190
+
191
+ def resources(hostname)
192
+ @resources ||= {}
193
+ @resources[hostname] ||= fetch_resources(hostname)
194
+ end
195
+
196
+ def fetch_resources(hostname)
197
+ cmd = "ssh -i #{keypair_file} #{config['user']}@#{hostname} 'cat /root/resources' 2>&1"
198
+ out = IO.popen(cmd, 'r') { |pipe| pipe.read }
199
+ abort "failed to read resources, output:\n#{out}" unless $?.success?
200
+ parse_resources(out, hostname)
201
+ end
202
+
203
+ def parse_resources(raw, hostname)
204
+ raw.split("\n").map do |line|
205
+ line.gsub(/localhost/, hostname)
206
+ end
207
+ end
208
+
209
+ def terminate(instance_id)
210
+ ec2.terminate_instances(:instance_id => [ instance_id ])
211
+ end
212
+
213
+ def console_output(instance_id)
214
+ ec2.get_console_output(:instance_id => instance_id)["output"]
215
+ end
216
+
217
+ def config
218
+ @config ||= default_config.merge read_config
219
+ end
220
+
221
+ def default_config
222
+ {
223
+ 'user' => 'root',
224
+ 'ami' => 'ami-ed46a784',
225
+ 'availability_zone' => 'us-east-1b',
226
+ }
227
+ end
228
+
229
+ def sumo_dir
230
+ "#{ENV['HOME']}/.sumo"
231
+ end
232
+
233
+ def read_config
234
+ YAML.load File.read("#{sumo_dir}/config.yml")
235
+ rescue Errno::ENOENT
236
+ raise "Sumo is not configured, please fill in ~/.sumo/config.yml"
237
+ end
238
+
239
+ def keypair_file
240
+ "#{sumo_dir}/keypair.pem"
241
+ end
242
+
243
+ def create_keypair
244
+ keypair = ec2.create_keypair(:key_name => "sumo").keyMaterial
245
+ File.open(keypair_file, 'w') { |f| f.write keypair }
246
+ File.chmod 0600, keypair_file
247
+ end
248
+
249
+ def create_security_group
250
+ ec2.create_security_group(:group_name => 'sumo', :group_description => 'Sumo')
251
+ rescue AWS::InvalidGroupDuplicate
252
+ end
253
+
254
+ def open_firewall(port)
255
+ ec2.authorize_security_group_ingress(
256
+ :group_name => 'sumo',
257
+ :ip_protocol => 'tcp',
258
+ :from_port => port,
259
+ :to_port => port,
260
+ :cidr_ip => '0.0.0.0/0'
261
+ )
262
+ rescue AWS::InvalidPermissionDuplicate
263
+ end
264
+
265
+ def ec2
266
+ @ec2 ||= AWS::EC2::Base.new(:access_key_id => config['access_id'], :secret_access_key => config['access_secret'])
267
+ end
268
+ end
data/spec/base.rb ADDED
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/../lib/sumo'
2
+
3
+ require 'bacon'
4
+ require 'mocha/standalone'
5
+ require 'mocha/object'
6
+
7
+ class Bacon::Context
8
+ include Mocha::API
9
+
10
+ def initialize(name, &block)
11
+ @name = name
12
+ @before, @after = [
13
+ [lambda { mocha_setup }],
14
+ [lambda { mocha_verify ; mocha_teardown }]
15
+ ]
16
+ @block = block
17
+ end
18
+
19
+ def xit(desc, &bk)
20
+ end
21
+ end
data/spec/sumo_spec.rb ADDED
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ require 'fileutils'
4
+
5
+ describe Sumo do
6
+ before do
7
+ @work_path = "/tmp/spec_#{Process.pid}/"
8
+ FileUtils.mkdir_p(@work_path)
9
+ File.open("#{@work_path}/config.yml", "w") do |f|
10
+ f.write YAML.dump({})
11
+ end
12
+
13
+ @sumo = Sumo.new
14
+ @sumo.stubs(:sumo_dir).returns(@work_path)
15
+ end
16
+
17
+ after do
18
+ FileUtils.rm_rf(@work_path)
19
+ end
20
+
21
+ it "defaults to user root if none is specified in the config" do
22
+ @sumo.config['user'].should == 'root'
23
+ end
24
+
25
+ it "uses specified user if one is in the config" do
26
+ File.open("#{@work_path}/config.yml", "w") do |f|
27
+ f.write YAML.dump('user' => 'joe')
28
+ end
29
+ @sumo.config['user'].should == 'joe'
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: adamwiggins-sumo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Wiggins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-07 00:00:00 -07:00
13
+ default_executable: sumo
14
+ dependencies: []
15
+
16
+ description: A no-hassle way to launch one-off EC2 instances from the command line
17
+ email: adam@heroku.com
18
+ executables:
19
+ - sumo
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - README.rdoc
26
+ - Rakefile
27
+ - VERSION
28
+ - bin/sumo
29
+ - lib/sumo.rb
30
+ - spec/base.rb
31
+ - spec/sumo_spec.rb
32
+ has_rdoc: true
33
+ homepage: http://github.com/adamwiggins/sumo
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project: sumo
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: A no-hassle way to launch one-off EC2 instances from the command line
58
+ test_files:
59
+ - spec/base.rb
60
+ - spec/sumo_spec.rb