GoEC2 1.0.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/.gitignore +6 -0
- data/GoEC2.gemspec +51 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/bin/goec2 +4 -0
- data/lib/installer/goec2 +216 -0
- data/lib/installer/install-goec2 +70 -0
- data/lib/installer/uninstall_goec2 +17 -0
- data/lib/options.rb +34 -0
- data/lib/runner.rb +83 -0
- data/lib/utils.rb +152 -0
- metadata +86 -0
data/.gitignore
ADDED
data/GoEC2.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{GoEC2}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Within3", "Joe Fiorini", "Jonathan Penn"]
|
12
|
+
s.date = %q{2009-10-09}
|
13
|
+
s.default_executable = %q{goec2}
|
14
|
+
s.description = %q{Simple and opinionated helper for creating and managing Rubygem projects on GitHub}
|
15
|
+
s.email = %q{jfiorini@within3.com}
|
16
|
+
s.executables = ["goec2"]
|
17
|
+
s.files = [
|
18
|
+
".gitignore",
|
19
|
+
"GoEC2.gemspec",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"bin/goec2",
|
23
|
+
"lib/installer/goec2",
|
24
|
+
"lib/installer/install-goec2",
|
25
|
+
"lib/installer/uninstall_goec2",
|
26
|
+
"lib/options.rb",
|
27
|
+
"lib/runner.rb",
|
28
|
+
"lib/utils.rb"
|
29
|
+
]
|
30
|
+
s.homepage = %q{http://github.com/faithfulgeek/goec2}
|
31
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
32
|
+
s.require_paths = ["lib"]
|
33
|
+
s.rubygems_version = %q{1.3.5}
|
34
|
+
s.summary = %q{Simple and opinionated helper for creating and managing Rubygem projects on GitHub}
|
35
|
+
|
36
|
+
if s.respond_to? :specification_version then
|
37
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
38
|
+
s.specification_version = 3
|
39
|
+
|
40
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
41
|
+
s.add_runtime_dependency(%q<sevenwire-forgery>, [">= 0.2.2"])
|
42
|
+
s.add_development_dependency(%q<jeweler>, [">= 1.2.1"])
|
43
|
+
else
|
44
|
+
s.add_dependency(%q<sevenwire-forgery>, [">= 0.2.2"])
|
45
|
+
s.add_dependency(%q<jeweler>, [">= 1.2.1"])
|
46
|
+
end
|
47
|
+
else
|
48
|
+
s.add_dependency(%q<sevenwire-forgery>, [">= 0.2.2"])
|
49
|
+
s.add_dependency(%q<jeweler>, [">= 1.2.1"])
|
50
|
+
end
|
51
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "GoEC2"
|
5
|
+
gemspec.summary = "Simple and opinionated helper for creating and managing Rubygem projects on GitHub"
|
6
|
+
gemspec.description = "Simple and opinionated helper for creating and managing Rubygem projects on GitHub"
|
7
|
+
gemspec.email = "jfiorini@within3.com"
|
8
|
+
gemspec.homepage = "http://github.com/faithfulgeek/goec2"
|
9
|
+
gemspec.authors = ["Within3", "Joe Fiorini", "Jonathan Penn"]
|
10
|
+
gemspec.add_dependency('sevenwire-forgery', '>=0.2.2')
|
11
|
+
gemspec.add_development_dependency('jeweler', '>=1.2.1')
|
12
|
+
end
|
13
|
+
Jeweler::GemcutterTasks.new
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
16
|
+
end
|
17
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/goec2
ADDED
data/lib/installer/goec2
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'yaml'
|
4
|
+
require 'forgery'
|
5
|
+
require 'dictionaries'
|
6
|
+
require 'file_reader'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
IDREGEX = /i-[0-9a-f]{8}/
|
10
|
+
|
11
|
+
EC2_SSH_KEY_NAME = ENV['EC2_KEYPAIR_NAME'] || 'default'
|
12
|
+
EC2_SSH_KEY_PATH = ENV['EC2_KEYPAIR_PATH'] || '~/.ec2/default-key-pair.pem'
|
13
|
+
|
14
|
+
#AMI_ID = "ami-5647a33f" # Amazon Base Fedora 8
|
15
|
+
#AMI_ID = "ami-c8ac48a1" # RightImage CentOS 5 Base
|
16
|
+
#AMI_ID = "ami-d510f1bc" # Within3 App Image
|
17
|
+
#AMI_ID = "ami-155cba7c" # RightImage CentOS 5.2 Base
|
18
|
+
#AMI_ID = "ami-4f1dfc26" # Within3 App Base Image
|
19
|
+
#AMI_ID = "ami-af6283c6" # Within3 App Base Image
|
20
|
+
AMI_ID = "ami-3f688956" # Within3 App Base Image
|
21
|
+
|
22
|
+
Instance = Struct.new(:line, :instance_id, :image, :status, :url, :name)
|
23
|
+
|
24
|
+
def main
|
25
|
+
case ARGV[0]
|
26
|
+
when /spinup/
|
27
|
+
options = {}
|
28
|
+
opts = OptionParser.new
|
29
|
+
opts.on('-n', '--name NAME') { |name| options[:name] = name }
|
30
|
+
opts.on('-s', '--size SIZE') { |size| options[:instance_size] = size }
|
31
|
+
opts.on('-f', '--user-data-file PATH') { |path| options[:user_data_file] = path }
|
32
|
+
opts.on('-a', '--ami-id AMI_ID') { |ami_id| options[:ami_id] = ami_id }
|
33
|
+
opts.on('-z', '--zone ZONE') { |zone| options[:zone] = zone }
|
34
|
+
opts.parse!(ARGV)
|
35
|
+
options[:name] ||= get_name_for_instance
|
36
|
+
options[:ami_id] ||= AMI_ID
|
37
|
+
options[:instance_size] ||= 'm1.large'
|
38
|
+
options[:zone] ||= 'us-east-1c'
|
39
|
+
puts "Spining up image with options: #{options.inspect}"
|
40
|
+
spinup(options)
|
41
|
+
when /spindown/
|
42
|
+
raise "Need an instance name" unless ARGV[1]
|
43
|
+
spindown ARGV[1]
|
44
|
+
when /status/
|
45
|
+
puts "Getting the status"
|
46
|
+
instances = if ARGV[1] then [get_instance(ARGV[1])].compact else get_instances end
|
47
|
+
instances.each do |instance|
|
48
|
+
puts "%s\t%s\t%s\t%s" % [instance.name, instance.instance_id, instance.status, instance.url]
|
49
|
+
end
|
50
|
+
when /ssh/
|
51
|
+
raise "Need an instance name" unless ARGV[1]
|
52
|
+
instance = get_instance(ARGV[1])
|
53
|
+
raise "Unknown instance name #{ARGV[1]}" unless instance
|
54
|
+
ssh instance.url
|
55
|
+
when /scp_up/
|
56
|
+
raise "Need an istance name" unless ARGV[1]
|
57
|
+
raise "Need a source path" unless ARGV[2]
|
58
|
+
raise "Need a destination path" unless ARGV[3]
|
59
|
+
instance = get_instance(ARGV[1])
|
60
|
+
scp_up instance, ARGV[2], ARGV[3]
|
61
|
+
when /webopen/
|
62
|
+
raise "Need an instance name" unless ARGV[1]
|
63
|
+
instance = get_instance(ARGV[1])
|
64
|
+
raise "Unknown instance name #{ARGV[1]}" unless instance
|
65
|
+
`open http://#{instance.url}`
|
66
|
+
when /output/
|
67
|
+
raise "Need an instance name" unless ARGV[1]
|
68
|
+
console_output get_instance(ARGV[1])
|
69
|
+
when /address/
|
70
|
+
raise "Need an instance name" unless ARGV[1]
|
71
|
+
instance = get_instance(ARGV[1])
|
72
|
+
puts instance.url
|
73
|
+
else
|
74
|
+
puts "Fail."
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def run_instance(options={})
|
80
|
+
instance_size = options.delete(:instance_size)
|
81
|
+
image_id = options.delete(:ami_id)
|
82
|
+
name = options.delete(:name)
|
83
|
+
zone = options.delete(:zone)
|
84
|
+
user_data_file = options.delete(:user_data_file)
|
85
|
+
instance_options = "-t #{instance_size}"
|
86
|
+
instance_options += " -f #{user_data_file}" if user_data_file
|
87
|
+
instance_options += " -z #{zone}" if zone
|
88
|
+
instance = parse_aws_status_line_into_instance `ec2-run-instances #{image_id} -k #{EC2_SSH_KEY_NAME} #{instance_options}`.split("\n")[-1], name
|
89
|
+
write_instance_to_datastore(instance)
|
90
|
+
instance
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_name_for_instance
|
94
|
+
Dictionaries.new[:company_names].random.downcase
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse_aws_status_line_into_instance line, name=nil
|
98
|
+
columns = line.split("\t")
|
99
|
+
instance = Instance.new line, columns[1], columns[2], columns[5], columns[3], name
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_instance(name)
|
103
|
+
instances = load_instances_from_datastore
|
104
|
+
instance_id = instances[name]
|
105
|
+
`ec2-describe-instances #{instance_id}`.split("\n").select{ |l| l =~ /^INSTANCE/ }.inject([]) do |result, line|
|
106
|
+
instance = parse_aws_status_line_into_instance(line, name)
|
107
|
+
|
108
|
+
if instance.status != 'terminated'
|
109
|
+
instance.name = name
|
110
|
+
result << instance
|
111
|
+
else
|
112
|
+
result
|
113
|
+
end
|
114
|
+
end.compact.first
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_instances
|
118
|
+
instance_mappings = load_instances_from_datastore
|
119
|
+
`ec2-describe-instances`.split("\n").select{|l| l =~ /^INSTANCE/}.inject([]) do |result, line|
|
120
|
+
instance = parse_aws_status_line_into_instance(line)
|
121
|
+
|
122
|
+
if instance.status != "terminated"
|
123
|
+
instance.name = instance_mappings.find_name_by_instance_id(instance.instance_id)
|
124
|
+
result << instance
|
125
|
+
else
|
126
|
+
result
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def terminate_instances *instances
|
132
|
+
`ec2-terminate-instances #{instances.map{|i| i.instance_id}.join(' ')}`
|
133
|
+
end
|
134
|
+
|
135
|
+
def ssh url
|
136
|
+
puts url
|
137
|
+
exec "ssh -i #{EC2_SSH_KEY_PATH} root@#{url}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def scp_up instance, source, destination
|
141
|
+
exec "scp -i #{EC2_SSH_KEY_PATH} #{source} root@#{instance.url}:#{destination}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def console_output instance
|
145
|
+
exec "ec2-get-console-output #{instance.instance_id}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def spinup(options={})
|
149
|
+
puts "Spinning up image"
|
150
|
+
name = options[:name]
|
151
|
+
current_instance = instance = run_instance(options)
|
152
|
+
puts "Instance #{instance.instance_id} spinning up"
|
153
|
+
|
154
|
+
still_loading = true
|
155
|
+
while still_loading
|
156
|
+
puts "waiting...#{current_instance.line}"
|
157
|
+
sleep 2
|
158
|
+
current_instance = get_instance(name)
|
159
|
+
still_loading = current_instance.status == "pending"
|
160
|
+
end
|
161
|
+
|
162
|
+
puts "Instance running!"
|
163
|
+
puts "name: #{current_instance.name}, id: #{current_instance.instance_id}, url: #{current_instance.url}"
|
164
|
+
end
|
165
|
+
|
166
|
+
INSTANCE_MAPPINGS_FILENAME = File.expand_path("~/.ec2/instance_mappings.yml")
|
167
|
+
def write_instance_to_datastore(instance)
|
168
|
+
instances = load_instances_from_datastore || {}
|
169
|
+
instances.merge!({instance.name => instance.instance_id})
|
170
|
+
write_instances_to_datastore(instances)
|
171
|
+
end
|
172
|
+
|
173
|
+
def write_instances_to_datastore(instances)
|
174
|
+
File.open(INSTANCE_MAPPINGS_FILENAME, "w+") do |f|
|
175
|
+
f << instances.to_yaml
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def load_instances_from_datastore
|
180
|
+
data = File.read(INSTANCE_MAPPINGS_FILENAME)
|
181
|
+
instances = YAML.load(data)
|
182
|
+
|
183
|
+
class << instances
|
184
|
+
def find_name_by_instance_id(instance_id)
|
185
|
+
select { |k,v| v == instance_id }[0]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
instances
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_instance_from_datastore(name)
|
193
|
+
instances = load_instances_from_datastore
|
194
|
+
instances.delete(name)
|
195
|
+
write_instances_to_datastore(instances)
|
196
|
+
end
|
197
|
+
|
198
|
+
def spindown name
|
199
|
+
puts "Spinning down #{name}"
|
200
|
+
instance = get_instance(name)
|
201
|
+
raise "Unknown instance name #{name}" unless instance
|
202
|
+
|
203
|
+
terminate_instances instance
|
204
|
+
|
205
|
+
while instance
|
206
|
+
puts "waiting...#{instance.line}"
|
207
|
+
sleep 2
|
208
|
+
instance = get_instance(name)
|
209
|
+
end
|
210
|
+
|
211
|
+
delete_instance_from_datastore(name)
|
212
|
+
|
213
|
+
puts "Terminated #{name}"
|
214
|
+
end
|
215
|
+
|
216
|
+
main
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# Must be run with root permissions
|
4
|
+
|
5
|
+
set -e
|
6
|
+
|
7
|
+
export EC2_HOME=/usr/local/ec2
|
8
|
+
export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
|
9
|
+
|
10
|
+
ec2_dir=".ec2"
|
11
|
+
|
12
|
+
if [ ! -e /usr/local/ec2 ]; then
|
13
|
+
sudo mkdir -p $EC2_HOME
|
14
|
+
sudo tar -xf ec2-api-tools.tar.gz -C $EC2_HOME
|
15
|
+
fi
|
16
|
+
|
17
|
+
while getopts "v:" flag
|
18
|
+
do
|
19
|
+
case $flag in
|
20
|
+
v)
|
21
|
+
conf_dir=$OPTARG ;;
|
22
|
+
esac
|
23
|
+
done
|
24
|
+
|
25
|
+
if [ -z $conf_dir ]; then
|
26
|
+
mkdir ~/.ec2
|
27
|
+
else
|
28
|
+
|
29
|
+
case $conf_dir in
|
30
|
+
*/) ;;
|
31
|
+
*) conf_dir=$conf_dir/
|
32
|
+
esac
|
33
|
+
|
34
|
+
if [ ! -e $conf_dir$ec2_dir ]; then
|
35
|
+
mkdir $conf_dir$ec2_dir
|
36
|
+
fi
|
37
|
+
|
38
|
+
ln -s $conf_dir$ec2_dir ~/
|
39
|
+
fi
|
40
|
+
|
41
|
+
sudo gem install sevenwire-forgery
|
42
|
+
|
43
|
+
cp pk-* cert-* default-key-pair.pem ~/$ec2_dir
|
44
|
+
|
45
|
+
sudo cp goec2 /usr/local/bin
|
46
|
+
|
47
|
+
touch ~/$ec2_dir/instance_mappings.yml
|
48
|
+
|
49
|
+
export EC2_PRIVATE_KEY=$(ls ~/$ec2_dir/pk-*)
|
50
|
+
export EC2_CERT=$(ls ~/$ec2_dir/cert-*)
|
51
|
+
|
52
|
+
echo "Please enter the name you'd like to use for your EC2 key pair:"
|
53
|
+
read keypair_name
|
54
|
+
|
55
|
+
$EC2_HOME/bin/ec2-add-keypair $keypair_name | grep -v KEYPAIR > ~/$ec2_dir/$keypair_name
|
56
|
+
chmod 400 ~/$ec2_dir/$keypair_name
|
57
|
+
chmod 400 ~/$ec2_dir/default-key-pair.pem
|
58
|
+
|
59
|
+
echo "
|
60
|
+
EC2_HOME=$EC2_HOME
|
61
|
+
EC2_PRIVATE_KEY=$(ls ~/$ec2_dir/pk-*)
|
62
|
+
EC2_CERT=$(ls ~/$ec2_dir/cert-*)
|
63
|
+
PATH=\"$PATH:$EC2_HOME/bin\"
|
64
|
+
JAVA_HOME=$JAVA_HOME
|
65
|
+
EC2_KEYPAIR_PATH=~/$ec2_dir/$keypair_name
|
66
|
+
EC2_KEYPAIR_NAME=$keypair_name
|
67
|
+
export EC2_HOME EC2_PRIVATE_KEY EC2_CERT PATH JAVA_HOME EC2_KEYPAIR_NAME EC2_KEYPAIR_PATH
|
68
|
+
" >> ~/$ec2_dir/use-ec2
|
69
|
+
|
70
|
+
echo "source ~/$ec2_dir/use-ec2" >> ~/.bash_profile
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
ec2_dir=".ec2"
|
4
|
+
|
5
|
+
echo "Remove keypair? (yes/no)"
|
6
|
+
read should_remove_keypair
|
7
|
+
|
8
|
+
if [ $should_remove_keypair = "yes" ]; then
|
9
|
+
echo "What is the name of the keypair to remove?"
|
10
|
+
read keypair_name
|
11
|
+
source use-ec2
|
12
|
+
$EC2_HOME/bin/ec2-delete-keypair $keypair_name
|
13
|
+
fi
|
14
|
+
|
15
|
+
sudo rm /usr/local/bin/goec2
|
16
|
+
rm -rf ~/$ec2_dir
|
17
|
+
|
data/lib/options.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Options
|
4
|
+
|
5
|
+
def opts
|
6
|
+
@opts ||= OptionParser.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse_spinup_options(args, defaults={})
|
10
|
+
opts.on('-n', '--name NAME', "The name to use for the instance (note: this is local to this machine. It does not affect Amazon EC2 AT ALL") { |name| defaults[:name] = name }
|
11
|
+
opts.on('-s', '--size SIZE', "The size of the instance per EC2 documentation") { |size| defaults[:instance_size] = size }
|
12
|
+
opts.on('-f', '--user-data-file PATH', "Path to a user data file to pass into the instance") { |path| defaults[:user_data_file] = path }
|
13
|
+
opts.on('-a', '--ami-id AMI_ID', "The AMI ID of the image with which to boot this machine") { |ami_id| defaults[:ami_id] = ami_id }
|
14
|
+
opts.on('-z', '--zone ZONE', "The zone in which to boot this machine per EC2 documentation") { |zone| defaults[:zone] = zone }
|
15
|
+
opts.banner = "Usage: goec2 spinup [options]"
|
16
|
+
opts.parse!(args)
|
17
|
+
defaults
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_ssh_options(args, defaults={})
|
21
|
+
opts.on("-u", "--user [NAME]", "The user to pass to ssh") { |name| defaults[:user] = name }
|
22
|
+
opts.banner = "Usage: goec2 ssh INSTANCE_NAME [options]"
|
23
|
+
opts.parse!(args)
|
24
|
+
defaults
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_attach_volume_options(args, defaults={})
|
28
|
+
opts.on("-v", "--volume-id VOLUME_ID") { |volume_id| defaults[:volume_id] = volume_id }
|
29
|
+
opts.on("-d", "--device [DEVICE]") { |device| defaults[:device] = device }
|
30
|
+
opts.banner = "Usage: goec2 attach_volume INSTANCE_NAME [options]"
|
31
|
+
opts.parse!(args)
|
32
|
+
defaults
|
33
|
+
end
|
34
|
+
end
|
data/lib/runner.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'utils'
|
3
|
+
require 'options'
|
4
|
+
|
5
|
+
class Runner
|
6
|
+
include Utils
|
7
|
+
include Options
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
case args[0]
|
11
|
+
when /spinup/
|
12
|
+
options = parse_spinup_options(args,
|
13
|
+
:name => get_name_for_instance,
|
14
|
+
:ami_id => AMI_ID,
|
15
|
+
:instance_size => 'm1.large',
|
16
|
+
:zone => 'us-east-1c')
|
17
|
+
puts "Spining up image with options: #{options.inspect}"
|
18
|
+
spinup(options)
|
19
|
+
when /spindown/
|
20
|
+
raise "Need an instance name" unless args[1]
|
21
|
+
spindown args[1]
|
22
|
+
when /status/
|
23
|
+
puts "Getting the status"
|
24
|
+
instances = if args[1] then [get_instance(args[1])].compact else get_instances end
|
25
|
+
instances.each do |instance|
|
26
|
+
puts "%s\t%s\t%s\t%s" % [instance.name, instance.instance_id, instance.status, instance.url]
|
27
|
+
end
|
28
|
+
when /ssh/
|
29
|
+
instance_name = args.delete_at(1)
|
30
|
+
raise "Need an instance name" unless instance_name
|
31
|
+
instance = get_instance(instance_name)
|
32
|
+
raise "Unknown instance name #{instance_name}" unless instance
|
33
|
+
options = parse_ssh_options(args, :user => 'w3dev')
|
34
|
+
ssh instance.url, options
|
35
|
+
when /scp_up/
|
36
|
+
raise "Need an istance name" unless args[1]
|
37
|
+
raise "Need a source path" unless args[2]
|
38
|
+
raise "Need a destination path" unless args[3]
|
39
|
+
instance = get_instance(args[1])
|
40
|
+
scp_up instance, args[2], args[3]
|
41
|
+
when /webopen/
|
42
|
+
raise "Need an instance name" unless args[1]
|
43
|
+
instance = get_instance(args[1])
|
44
|
+
raise "Unknown instance name #{args[1]}" unless instance
|
45
|
+
`open http://#{instance.url}`
|
46
|
+
when /output/
|
47
|
+
raise "Need an instance name" unless args[1]
|
48
|
+
console_output get_instance(args[1])
|
49
|
+
when /address/
|
50
|
+
raise "Need an instance name" unless args[1]
|
51
|
+
instance = get_instance(args[1])
|
52
|
+
puts instance.url
|
53
|
+
when /attach_volume/
|
54
|
+
raise "Need an instance name" unless args[1]
|
55
|
+
instance_name = args.delete_at(1)
|
56
|
+
options = parse_attach_volume_options(args, :device => '/dev/sdf')
|
57
|
+
instance = get_instance(instance_name)
|
58
|
+
`ec2-attach-volume #{options[:volume_id]} -i #{instance.instance_id} -d #{options[:device]}`
|
59
|
+
else
|
60
|
+
puts banner
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def banner
|
65
|
+
<<-EOS
|
66
|
+
Script enabling easier interaction with Amazon EC2 instances.
|
67
|
+
|
68
|
+
Usage: goec2 COMMAND [INSTANCE_NAME] [ARGS]
|
69
|
+
|
70
|
+
Commands:
|
71
|
+
|
72
|
+
spinup Tells EC2 service to launch a new instance. By default generates a random name to associate with that instance through goec2
|
73
|
+
spindown Terminates the specified instance
|
74
|
+
status Retrieves the status of all running EC2 instances
|
75
|
+
ssh SSH into an EC2 instance
|
76
|
+
scp_up Upload a file to an EC2 instance (goec2 scp_up INSTANCE_NAME SOURCE DEST)
|
77
|
+
webopen Open the EC2 instance in a web browser (Mac OSX only)
|
78
|
+
output Get console output for a given EC2 instance
|
79
|
+
address Get the public address for a given EC2 instance
|
80
|
+
attach_volume Attach an EBS volume to the given EC2 instance
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
end
|
data/lib/utils.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'forgery'
|
2
|
+
require 'dictionaries'
|
3
|
+
require 'yaml'
|
4
|
+
require 'file_reader'
|
5
|
+
|
6
|
+
EC2_SSH_KEY_NAME = ENV['EC2_KEYPAIR_NAME'] || 'default'
|
7
|
+
EC2_SSH_KEY_PATH = ENV['EC2_KEYPAIR_PATH'] || '~/.ec2/default-key-pair.pem'
|
8
|
+
AMI_ID = "ami-fceb0895" # Within3 App Base Image
|
9
|
+
|
10
|
+
Instance = Struct.new(:line, :instance_id, :image, :status, :url, :name)
|
11
|
+
|
12
|
+
module Utils
|
13
|
+
def run_instance(options={})
|
14
|
+
instance_size = options.delete(:instance_size)
|
15
|
+
image_id = options.delete(:ami_id)
|
16
|
+
name = options.delete(:name)
|
17
|
+
zone = options.delete(:zone)
|
18
|
+
user_data_file = options.delete(:user_data_file)
|
19
|
+
instance_options = "-t #{instance_size}"
|
20
|
+
instance_options += " -f #{user_data_file}" if user_data_file
|
21
|
+
instance_options += " -z #{zone}" if zone
|
22
|
+
instance = parse_aws_status_line_into_instance `ec2-run-instances #{image_id} -k #{EC2_SSH_KEY_NAME} #{instance_options}`.split("\n")[-1], name
|
23
|
+
write_instance_to_datastore(instance)
|
24
|
+
instance
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_name_for_instance
|
28
|
+
Dictionaries.new[:company_names].random.downcase
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_aws_status_line_into_instance line, name=nil
|
32
|
+
columns = line.split("\t")
|
33
|
+
instance = Instance.new line, columns[1], columns[2], columns[5], columns[3], name
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_instance(name)
|
37
|
+
instance_id = if name =~ /^i-\w{8}$/
|
38
|
+
name
|
39
|
+
else
|
40
|
+
load_instances_from_datastore[name]
|
41
|
+
end
|
42
|
+
`ec2-describe-instances #{instance_id}`.split("\n").select{ |l| l =~ /^INSTANCE/ }.inject([]) do |result, line|
|
43
|
+
instance = parse_aws_status_line_into_instance(line, name)
|
44
|
+
|
45
|
+
if instance.status != 'terminated'
|
46
|
+
instance.name = name
|
47
|
+
result << instance
|
48
|
+
else
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end.compact.first
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_instances
|
55
|
+
instance_mappings = load_instances_from_datastore
|
56
|
+
`ec2-describe-instances`.split("\n").select{|l| l =~ /^INSTANCE/}.inject([]) do |result, line|
|
57
|
+
instance = parse_aws_status_line_into_instance(line)
|
58
|
+
|
59
|
+
if instance.status != "terminated"
|
60
|
+
instance.name = instance_mappings.find_name_by_instance_id(instance.instance_id)
|
61
|
+
result << instance
|
62
|
+
else
|
63
|
+
result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def terminate_instances *instances
|
69
|
+
`ec2-terminate-instances #{instances.map{|i| i.instance_id}.join(' ')}`
|
70
|
+
end
|
71
|
+
|
72
|
+
def ssh(url, options={})
|
73
|
+
puts url
|
74
|
+
exec "ssh -i #{EC2_SSH_KEY_PATH} #{options[:user]}@#{url}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def scp_up instance, source, destination
|
78
|
+
exec "scp -i #{EC2_SSH_KEY_PATH} #{source} root@#{instance.url}:#{destination}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def console_output instance
|
82
|
+
exec "ec2-get-console-output #{instance.instance_id}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def spinup(options={})
|
86
|
+
puts "Spinning up image"
|
87
|
+
name = options[:name]
|
88
|
+
current_instance = instance = run_instance(options)
|
89
|
+
puts "Instance #{instance.instance_id} spinning up"
|
90
|
+
|
91
|
+
still_loading = true
|
92
|
+
while still_loading
|
93
|
+
puts "waiting...#{current_instance.line}"
|
94
|
+
sleep 2
|
95
|
+
current_instance = get_instance(name)
|
96
|
+
still_loading = current_instance.status == "pending"
|
97
|
+
end
|
98
|
+
|
99
|
+
puts "Instance running!"
|
100
|
+
puts "name: #{current_instance.name}, id: #{current_instance.instance_id}, url: #{current_instance.url}"
|
101
|
+
end
|
102
|
+
|
103
|
+
INSTANCE_MAPPINGS_FILENAME = File.expand_path("~/.ec2/instance_mappings.yml")
|
104
|
+
def write_instance_to_datastore(instance)
|
105
|
+
instances = load_instances_from_datastore || {}
|
106
|
+
instances.merge!({instance.name => instance.instance_id})
|
107
|
+
write_instances_to_datastore(instances)
|
108
|
+
end
|
109
|
+
|
110
|
+
def write_instances_to_datastore(instances)
|
111
|
+
File.open(INSTANCE_MAPPINGS_FILENAME, "w+") do |f|
|
112
|
+
f << instances.to_yaml
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def load_instances_from_datastore
|
117
|
+
data = File.read(INSTANCE_MAPPINGS_FILENAME)
|
118
|
+
instances = YAML.load(data)
|
119
|
+
|
120
|
+
class << instances
|
121
|
+
def find_name_by_instance_id(instance_id)
|
122
|
+
select { |k,v| v == instance_id }[0]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
instances
|
127
|
+
end
|
128
|
+
|
129
|
+
def delete_instance_from_datastore(name)
|
130
|
+
instances = load_instances_from_datastore
|
131
|
+
instances.delete(name)
|
132
|
+
write_instances_to_datastore(instances)
|
133
|
+
end
|
134
|
+
|
135
|
+
def spindown name
|
136
|
+
puts "Spinning down #{name}"
|
137
|
+
instance = get_instance(name)
|
138
|
+
raise "Unknown instance name #{name}" unless instance
|
139
|
+
|
140
|
+
terminate_instances instance
|
141
|
+
|
142
|
+
while instance
|
143
|
+
puts "waiting...#{instance.line}"
|
144
|
+
sleep 2
|
145
|
+
instance = get_instance(name)
|
146
|
+
end
|
147
|
+
|
148
|
+
delete_instance_from_datastore(name)
|
149
|
+
|
150
|
+
puts "Terminated #{name}"
|
151
|
+
end
|
152
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: GoEC2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Within3
|
8
|
+
- Joe Fiorini
|
9
|
+
- Jonathan Penn
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2009-10-09 00:00:00 -04:00
|
15
|
+
default_executable: goec2
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: sevenwire-forgery
|
19
|
+
type: :runtime
|
20
|
+
version_requirement:
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.2.2
|
26
|
+
version:
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jeweler
|
29
|
+
type: :development
|
30
|
+
version_requirement:
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 1.2.1
|
36
|
+
version:
|
37
|
+
description: Simple and opinionated helper for creating and managing Rubygem projects on GitHub
|
38
|
+
email: jfiorini@within3.com
|
39
|
+
executables:
|
40
|
+
- goec2
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- .gitignore
|
47
|
+
- GoEC2.gemspec
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- bin/goec2
|
51
|
+
- lib/installer/goec2
|
52
|
+
- lib/installer/install-goec2
|
53
|
+
- lib/installer/uninstall_goec2
|
54
|
+
- lib/options.rb
|
55
|
+
- lib/runner.rb
|
56
|
+
- lib/utils.rb
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/faithfulgeek/goec2
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --charset=UTF-8
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.5
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Simple and opinionated helper for creating and managing Rubygem projects on GitHub
|
85
|
+
test_files: []
|
86
|
+
|