sproutr 0.2.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/LICENSE +22 -0
- data/README.md +65 -0
- data/bin/sproutr +6 -0
- data/lib/sproutr.rb +213 -0
- data/lib/sproutr/ami.rb +12 -0
- data/lib/sproutr/cloud.rb +33 -0
- data/lib/sproutr/definition.rb +26 -0
- data/lib/sproutr/instance.rb +18 -0
- data/lib/sproutr/utilities.rb +108 -0
- metadata +185 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Peter van Hardenberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#Sprout
|
2
|
+
##EC2 made stupid simple.
|
3
|
+
|
4
|
+
##Introduction
|
5
|
+
|
6
|
+
sproutr is a thor based EC2 instance management library which abstracts the Amazon EC2 API and provides an interactive interface for designing, launching, and managing running instances.
|
7
|
+
|
8
|
+
sproutr is based around the idea of helping you define an instance, launch it, then create as many copies of that as you need. Currently sproutr supports the following tasks:
|
9
|
+
sproutr clone --instance=one two three # Clone N number of running instance
|
10
|
+
sproutr create_ami --ami=AMI --desc=DESC --name=NAME # Create an EBS Ami from a running or stopped instance
|
11
|
+
sproutr define # define a new instance
|
12
|
+
sproutr delete_snapshot --snapshot=one two three # Deletes the given snapshot(s) use --snapshot=
|
13
|
+
sproutr describe --ami=one two three # Describe a specific instance
|
14
|
+
sproutr destroy --ami=one two three # Alias to terminate
|
15
|
+
sproutr help [TASK] # Describe available tasks or one specific task
|
16
|
+
sproutr launch --config-file=CONFIG_FILE # launch an instance from the specified config directory
|
17
|
+
sproutr list # list all the instances and ami's in your ec2 account
|
18
|
+
sproutr list_amis # list all the ami's in your ec2 account
|
19
|
+
sproutr list_instances # list all the instances in your ec2 account
|
20
|
+
sproutr list_snapshots # Alias to list_snapshots
|
21
|
+
sproutr list_snapshots # Lists all the snapshots available and their status
|
22
|
+
sproutr restart --ami=one two three # Restart the specified instance(s), requires --ami=
|
23
|
+
sproutr snapshot --ami=one two three # Create snapshot
|
24
|
+
sproutr start --ami=one two three # Start the specified instance(s), requires --ami=
|
25
|
+
sproutr stop --ami=one two three # Stop the specified instance(s), requires --ami=
|
26
|
+
sproutr terminate --ami=one two three # Terminate the specified instance, requires --ami=
|
27
|
+
|
28
|
+
##Configuration
|
29
|
+
|
30
|
+
sproutr relies on the Swirl library, which needs to be passed your AWS credentials to do its magic. sproutr therefore requires that you provide a .swirlrc file in your home directory (~/) that contains:
|
31
|
+
~/.swirl:
|
32
|
+
---
|
33
|
+
:default:
|
34
|
+
:aws_access_key_id: my_access_key
|
35
|
+
:aws_secret_access_key: my_secret_key
|
36
|
+
|
37
|
+
##Usage
|
38
|
+
|
39
|
+
You can use sproutr to manage your instances from the commandline. You should create an instance which will serve as your "sproutr" and be converted into an AMI.
|
40
|
+
Once you have tested this instance, create a snapshot of the instance, then use it by AMI-Id to launch new instances with their own individual configuration.
|
41
|
+
|
42
|
+
Here's a simple example from the command line. Begin by invoking the define task.
|
43
|
+
|
44
|
+
$ bin/sproutr define
|
45
|
+
|
46
|
+
sproutr's define task will walk you through the process of determining the name, instance size, starting AMI etc.
|
47
|
+
Key to the sproutr experience is the way handles two key features: Chef and Volumes:
|
48
|
+
sproutr builds the instance with knowledge of, and the ability to execute arbitrary chef cookbooks/recipes.
|
49
|
+
While defining an instance you're given the opportunity to specify additional packages, cookbooks and recipes to have installed
|
50
|
+
Currently only Debian (ubuntu, mint, etc.) based distributions are supported.
|
51
|
+
Additionally, be aware that any volumes you specify will be aggregated together using mdadm and lvm into one logical volume.
|
52
|
+
|
53
|
+
Once defined, you can launch the instance via
|
54
|
+
|
55
|
+
$ sproutr launch --definition=filename.json
|
56
|
+
|
57
|
+
You can monitor the instance's fabrication process via
|
58
|
+
|
59
|
+
$ sproutr list
|
60
|
+
|
61
|
+
The instance you created will boot, install your selected packages on top of the stock AMI you selected, then download and cook all the cookbooks and recipes you selected.
|
62
|
+
|
63
|
+
##Inspiration and Thanks
|
64
|
+
|
65
|
+
sproutr is a rewrite and extension of the stem gem, a product (so far as I can tell) a gift of the Heroku development / operations team.
|
data/bin/sproutr
ADDED
data/lib/sproutr.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.dirname(__FILE__)
|
3
|
+
require 'rubygems'
|
4
|
+
require 'swirl/aws'
|
5
|
+
require 'json'
|
6
|
+
require 'thor'
|
7
|
+
require 'ap'
|
8
|
+
require 'terminal-table/import'
|
9
|
+
require 'sproutr/utilities'
|
10
|
+
require 'sproutr/instance'
|
11
|
+
require 'sproutr/cloud'
|
12
|
+
require 'sproutr/ami'
|
13
|
+
require 'sproutr/definition'
|
14
|
+
|
15
|
+
class Sproutr < Thor
|
16
|
+
include Thor::Actions
|
17
|
+
map "-l" => :list
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
super
|
21
|
+
@ec2 ||= Swirl::AWS.new :ec2, load_config
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "clone", "Clone N number of running instance"
|
25
|
+
method_option :instance, :type => :array, :required => true
|
26
|
+
method_option :ami, :type => :string
|
27
|
+
method_option :start, :type => :boolean
|
28
|
+
method_option :tags, :type => :hash
|
29
|
+
|
30
|
+
def clone
|
31
|
+
options[:instance].each do |ami_to_clone|
|
32
|
+
if options[:ami] then
|
33
|
+
ami_id = options[:ami]
|
34
|
+
else
|
35
|
+
ami_id = @ec2.call("CreateImage", "InstanceId" => ami_to_clone, "Name" => "AMI-#{ami_to_clone}-#{Time.now.to_i}",
|
36
|
+
"Description" => "AMI created from #{ami_to_clone} at #{Time.now}", "NoReboot" => "true")["imageId"]
|
37
|
+
end
|
38
|
+
new_config = clone_ami_config(@ec2.call("DescribeInstances", "InstanceId" => ami_to_clone)["reservationSet"][0]["instancesSet"][0], ami_id)
|
39
|
+
until ami_done?(ami_id) do
|
40
|
+
say "Ami has not completed so this clone can not yet be started. Sleeping 30 seconds", :red
|
41
|
+
sleep 30
|
42
|
+
end
|
43
|
+
new_instance = invoke_launch(new_config)
|
44
|
+
say "Created and started #{new_instance}", :green
|
45
|
+
tag_instance(new_instance, options[:tags]) if options[:tags]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "create_ami", "Create an EBS Ami from a running or stopped instance"
|
50
|
+
method_option :ami, :type => :string, :required => true
|
51
|
+
method_option :name, :type => :string, :required => true
|
52
|
+
method_option :desc, :type => :string, :required => true
|
53
|
+
|
54
|
+
def create_ami
|
55
|
+
@ec2.call "CreateImage", "InstanceId" => options[:ami], "Name" => options[:name], "Description" => options[:desc], "NoReboot" => "true"
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "define", "define a new instance"
|
59
|
+
|
60
|
+
def define
|
61
|
+
definition = Definition.new do |d|
|
62
|
+
d.name = demand "What do you want to call this definition (Required): "
|
63
|
+
d.size = demand "What size instance do you want (Required): "
|
64
|
+
d.ami = demand "What base AMI image would you like to use (Required): "
|
65
|
+
ami_info = Cloud.new.describe_image(d.ami)
|
66
|
+
break unless yes? "Is this the Image -- #{ami_info["imagesSet"][0]["name"]} -- you wish to build from?", :red
|
67
|
+
d.packages = ask "What additional packages do you want installed (Optional): ", :green
|
68
|
+
d.gems = ask "What gems do you want to install on this machine by default (Optional): ", :green
|
69
|
+
d.chef_cookbooks = ask "Please name the chef cookbooks you wish to automatically download (Optional): ", :green
|
70
|
+
d.chef_recipes = ask "Enter the recipes you wish chef to run after cookbooks are installed (Optional): ", :green
|
71
|
+
d.user_data = demand "Please copy/paste your Userdata.sh here now. Note, sproutr will automatically add in the
|
72
|
+
requisite code to download and install Cheff cookbooks, and aggregate additional volumes
|
73
|
+
into a singluar raid array (Required): "
|
74
|
+
d.tags = ask "Please enter the tags you'd like in name:value format (Optional, Note that the Name will automatically be specified): ", :green
|
75
|
+
d.volumes = ask "Please enter the number of additional volumes (Optional, Note that these additional volumes will be RAID/LVM enabled as 1 *logical* volume): ", :green
|
76
|
+
d.volume_size = ask "Please enter the size of each component volume in Gigabytes (Optional, Currently all volumes are identical in size): ", :green
|
77
|
+
d.availability_zone = demand "Please enter the availability zone you wish to instantiate this machine in: "
|
78
|
+
d.key_name = demand "Which key-pair would you like to use? use the key name: "
|
79
|
+
end
|
80
|
+
definition.save_to_file(definition.name+"_definition.json")
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "delete_snapshot", "Deletes the given snapshot(s) use --snapshot="
|
84
|
+
method_option :snapshot, :type => :array, :required => true
|
85
|
+
|
86
|
+
def delete_snapshot
|
87
|
+
options[:snapshot].each do |snapshot_id|
|
88
|
+
result = @ec2.call "DeleteSnapshot", "SnapshotId" => snapshot_id
|
89
|
+
say result["return"], :green
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "describe", "Describe a specific instance"
|
94
|
+
method_option :ami, :type => :array, :required => true
|
95
|
+
|
96
|
+
def describe
|
97
|
+
options[:ami].each do |ami|
|
98
|
+
ap @ec2.call("DescribeInstances", "InstanceId" => ami)["reservationSet"][0]["instancesSet"][0]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc "launch", "launch an instance from the specified config directory"
|
103
|
+
method_option :config_file, :type => :string, :required => true
|
104
|
+
|
105
|
+
def launch
|
106
|
+
config = JSON.parse(File.new(options[:config_file]).read) if File.exists? options[:config_file]
|
107
|
+
throw "Failed to read config file, or config file does not specify an ImageId" unless config["ami"]
|
108
|
+
say invoke_launch(validate_launch_config(config)), :blue
|
109
|
+
end
|
110
|
+
|
111
|
+
desc "list", "list all the instances and ami's in your ec2 account"
|
112
|
+
|
113
|
+
def list
|
114
|
+
invoke :list_instances
|
115
|
+
invoke :list_amis
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "list_amis", "list all the ami's in your ec2 account"
|
119
|
+
|
120
|
+
def list_amis
|
121
|
+
@ami_images = Cloud.new.get_images
|
122
|
+
ami_table = table do |a|
|
123
|
+
a.headings = 'Name', 'AMI Id', 'Type', 'State', 'Size', 'Architecture', 'Public?', 'Description'
|
124
|
+
@ami_images.each do |ami|
|
125
|
+
a << [ami.name, ami.imageId, ami.rootDeviceType, ami.imageState, ami.blockDeviceMapping[0]["ebs"]["volumeSize"],
|
126
|
+
ami.architecture, ami.isPublic, ami.description]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
puts ami_table
|
130
|
+
end
|
131
|
+
|
132
|
+
desc "list_instances", "list all the instances in your ec2 account"
|
133
|
+
|
134
|
+
def list_instances
|
135
|
+
instances = Cloud.new.get_instances
|
136
|
+
instance_table = table do |i|
|
137
|
+
i.headings = 'Name', 'Instance Id', 'Status', 'ip Address', 'Instance Type', 'AMI Image', 'Availablity Zone', 'DNS Cname'
|
138
|
+
instances.each do |instance|
|
139
|
+
i << [((instance.tagSet.nil?) ? "Name Not Set" : instance.tagSet[0]["value"]),
|
140
|
+
instance.instanceId, instance.instanceState["name"], instance.ipAddress,
|
141
|
+
instance.instanceType, instance.imageId, instance.placement["availabilityZone"], instance.dnsName]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
puts instance_table
|
145
|
+
end
|
146
|
+
|
147
|
+
desc "list_snapshots", "Lists all the snapshots available and their status"
|
148
|
+
|
149
|
+
def list_snapshots
|
150
|
+
snapshots = @ec2.call("DescribeSnapshots", "Owner" => "self")["snapshotSet"]
|
151
|
+
snapshots_table = table do |s|
|
152
|
+
s.headings = snapshots.first.keys
|
153
|
+
snapshots.each do |snap|
|
154
|
+
s << snap.values
|
155
|
+
end
|
156
|
+
end
|
157
|
+
puts snapshots_table
|
158
|
+
end
|
159
|
+
|
160
|
+
desc "restart", "Restart the specified instance(s), requires --ami="
|
161
|
+
method_option :ami, :type => :array, :required => true
|
162
|
+
|
163
|
+
def restart
|
164
|
+
options[:ami].each { |ami| Instance::call_on("RebootInstances", ami) }
|
165
|
+
end
|
166
|
+
|
167
|
+
desc "snapshot", "Create snapshot"
|
168
|
+
method_option :ami, :type => :array, :required => true
|
169
|
+
method_options :desc => "Snapshot created by sproutr"
|
170
|
+
|
171
|
+
def snapshot
|
172
|
+
options[:ami].each do |ami|
|
173
|
+
instance_volumes = @ec2.call("DescribeInstances", "InstanceId" => ami)["reservationSet"][0]["instancesSet"][0]["blockDeviceMapping"]
|
174
|
+
instance_volumes.each { |volume| ap @ec2.call("CreateSnapshot", "VolumeId" => volume["ebs"]["volumeId"], "Description" => options[:desc]) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
desc "start", "Start the specified instance(s), requires --ami="
|
180
|
+
method_option :ami, :type => :array, :required => true
|
181
|
+
|
182
|
+
def start
|
183
|
+
options[:ami].each { |ami| Instance::call_on("StartInstances", ami) }
|
184
|
+
end
|
185
|
+
|
186
|
+
desc "stop", "Stop the specified instance(s), requires --ami="
|
187
|
+
method_option :ami, :type => :array, :required => true
|
188
|
+
|
189
|
+
def stop
|
190
|
+
options[:ami].each { |ami| Instance::call_on("StopInstances", ami) if yes? "Do you really want to stop the #{instance} instance? ", :red }
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
desc "terminate", "Terminate the specified instance, requires --ami="
|
195
|
+
method_option :ami, :type => :array, :required => true
|
196
|
+
|
197
|
+
def terminate
|
198
|
+
options[:ami].each do |ami|
|
199
|
+
verify = ask "Do you really want to terminate the #{ami} instance? ", :red
|
200
|
+
Instance::call_on("TerminateInstances", ami) if verify.downcase == "y" || verify.downcase == "yes"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
desc "destroy", "Alias to terminate"
|
205
|
+
method_option :ami, :type => :array, :required => true
|
206
|
+
alias :destroy :terminate
|
207
|
+
|
208
|
+
|
209
|
+
desc "list_snapshots", "Alias to list_snapshots"
|
210
|
+
alias :list_snapshot :list_snapshots
|
211
|
+
|
212
|
+
end
|
213
|
+
Sproutr.start
|
data/lib/sproutr/ami.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'swirl/aws'
|
2
|
+
require 'sproutr/utilities'
|
3
|
+
|
4
|
+
class Cloud
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :instances, :images
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@ec2 ||= Swirl::AWS.new :ec2, load_config
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_instances
|
15
|
+
aws_instances = @ec2.call "DescribeInstances"
|
16
|
+
@instances = Array.new
|
17
|
+
aws_instances["reservationSet"].each { |reservation| @instances << Instance.new(reservation["instancesSet"][0]) }
|
18
|
+
@instances
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_images
|
22
|
+
aws_images = @ec2.call "DescribeImages", "Owner" => "self"
|
23
|
+
images = aws_images["imagesSet"].select { |img| img["name"] } rescue nil
|
24
|
+
@images = Array.new
|
25
|
+
images.each { |image| @images << Ami.new(image) }
|
26
|
+
@images
|
27
|
+
end
|
28
|
+
|
29
|
+
def describe_image(ami)
|
30
|
+
@ec2.call "DescribeImages", "ImageId" => ami
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Definition
|
4
|
+
FILE_PREFIX = "~/Sprout_configs/machine_definitions/"
|
5
|
+
%x{mkdir -p #{FILE_PREFIX} } unless File.directory? FILE_PREFIX
|
6
|
+
attr_accessor :name, :gems, :user_data, :packages, :size, :chef_cookbooks, :chef_recipes, :ami, :tags, :volumes, :volume_size, :availability_zone, :key_name, :chef_install
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
yield self if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_from_file(filename)
|
13
|
+
definition_file = File.open(FILE_PREFIX + filename, 'r')
|
14
|
+
JSON.parse(definition_file.read).each do |element, value|
|
15
|
+
instance_variable_set(element, value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def save_to_file(filename)
|
20
|
+
hash_of_definition = Hash.new
|
21
|
+
instance_variables.each do |var|
|
22
|
+
hash_of_definition[var.to_s.gsub("@","")] = instance_variable_get(var)
|
23
|
+
end
|
24
|
+
File.open(FILE_PREFIX + filename, 'w') {|f| f.write(hash_of_definition.to_json) }
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Instance
|
2
|
+
|
3
|
+
def initialize(instance)
|
4
|
+
instance.keys.each do |key|
|
5
|
+
var = "@#{key}"
|
6
|
+
|
7
|
+
self.instance_variable_set(var.to_sym, instance[key])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.call_on(action, ami)
|
12
|
+
@ec2.call action, "InstanceId" => ami
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(sym, *args, &block)
|
16
|
+
self.instance_variable_get "@#{sym}".to_sym
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
def load_config
|
2
|
+
account = "default"
|
3
|
+
swirl_config = "#{ENV["HOME"]}/.swirl"
|
4
|
+
account = account.to_sym
|
5
|
+
|
6
|
+
if File.exists?(swirl_config)
|
7
|
+
data = YAML.load_file(swirl_config)
|
8
|
+
else
|
9
|
+
abort("You are required to write a proper YAML config file called .swirl file in your home directory")
|
10
|
+
end
|
11
|
+
|
12
|
+
if data.key?(account)
|
13
|
+
data[account]
|
14
|
+
else
|
15
|
+
abort("I don't see the account you're looking for")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def invoke_launch(config)
|
20
|
+
response = @ec2.call "RunInstances", config
|
21
|
+
response["instancesSet"].first["instanceId"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_launch_config(config)
|
25
|
+
instance_options = {
|
26
|
+
"SecurityGroup.#" => config["groups"] || [],
|
27
|
+
"MinCount" => "1",
|
28
|
+
"MaxCount" => "1",
|
29
|
+
"KeyName" => config["key_name"] || "Production",
|
30
|
+
"InstanceType" => config["instance_type"] || "m1.small",
|
31
|
+
"ImageId" => config["ami"]
|
32
|
+
}
|
33
|
+
|
34
|
+
if config["volumes"]
|
35
|
+
devices = []
|
36
|
+
sizes = []
|
37
|
+
config["volumes"].each do |v|
|
38
|
+
puts "Adding a volume of #{v["size"]} to be mounted at #{v["device"]}."
|
39
|
+
devices << v["device"]
|
40
|
+
sizes << v["size"].to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
instance_options.merge! "BlockDeviceMapping.#.Ebs.VolumeSize" => sizes, "BlockDeviceMapping.#.DeviceName" => devices
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def clone_ami_config(config, new_ami)
|
49
|
+
throw "No config provided" unless config
|
50
|
+
|
51
|
+
instance_options = {
|
52
|
+
"SecurityGroup.#" => config["groups"] || [],
|
53
|
+
"MinCount" => "1",
|
54
|
+
"MaxCount" => "1",
|
55
|
+
"KeyName" => config["key_name"] || "Production",
|
56
|
+
"InstanceType" => config["instance_type"] || "m1.small",
|
57
|
+
"ImageId" => new_ami
|
58
|
+
}
|
59
|
+
|
60
|
+
devices = []
|
61
|
+
sizes = []
|
62
|
+
config["blockDeviceMapping"].each do |block_device|
|
63
|
+
volume = @ec2.call "DescribeVolumes", "VolumeId" => block_device["ebs"]["volumeId"]
|
64
|
+
next if volume["volumeSet"][0]["attachmentSet"][0]["device"] == "/dev/sda1"
|
65
|
+
say "Adding volume #{volume["volumeSet"][0]["attachmentSet"][0]["device"]} with a size of #{volume["volumeSet"][0]["size"]}", :green
|
66
|
+
devices << volume["volumeSet"][0]["attachmentSet"][0]["device"]
|
67
|
+
sizes << volume["volumeSet"][0]["size"]
|
68
|
+
end
|
69
|
+
|
70
|
+
instance_options.merge! "BlockDeviceMapping.#.Ebs.VolumeSize" => sizes, "BlockDeviceMapping.#.DeviceName" => devices
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def ami_done?(ami)
|
75
|
+
ami_list = @ec2.call "DescribeImages", "Owner" => "self"
|
76
|
+
images = ami_list["imagesSet"].select { |img| img["name"] } rescue nil
|
77
|
+
images = images.map { |image| image["imageId"] if image["imageState"] == "available" }
|
78
|
+
(images.include? ami) ? true : false
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_tags(tags)
|
82
|
+
if tags && !tags.empty?
|
83
|
+
if tags.is_a? Hash
|
84
|
+
{"Tag.#.Key" => tags.keys.map(&:to_s),
|
85
|
+
"Tag.#.Value" => tags.values.map(&:to_s)}
|
86
|
+
elsif tags.is_a? Array
|
87
|
+
{
|
88
|
+
"Tag.#.Key" => tags.map(&:to_s),
|
89
|
+
"Tag.#.Value" => (1..tags.size).map { '' }
|
90
|
+
}
|
91
|
+
else
|
92
|
+
{"Tag.1.Key" => tags.to_s, "Tag.1.Value" => ''}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def tag_instance(instance_id, tags)
|
98
|
+
instance_id = [instance_id] unless instance_id.is_a? Array
|
99
|
+
@ec2.call("CreateTags", parse_tags(tags).merge("ResourceId" => instance_id))
|
100
|
+
end
|
101
|
+
|
102
|
+
def demand(question)
|
103
|
+
answer = nil
|
104
|
+
while answer.nil? or answer.strip == ""
|
105
|
+
answer = ask question, :green
|
106
|
+
end
|
107
|
+
answer
|
108
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sproutr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kevin Poorman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-24 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: swirl
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.7.5
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.14.6
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: terminal-table
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.4.2
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: awesome_print
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 0.3.2
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: json
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 2.5.0
|
80
|
+
type: :development
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rspec-core
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ~>
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 2.5.0
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rspec-expectations
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.5.0
|
102
|
+
type: :development
|
103
|
+
version_requirements: *id008
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rspec-mocks
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ~>
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 2.5.0
|
113
|
+
type: :development
|
114
|
+
version_requirements: *id009
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: vcr
|
117
|
+
prerelease: false
|
118
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ~>
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 1.6.0
|
124
|
+
type: :development
|
125
|
+
version_requirements: *id010
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: webmock
|
128
|
+
prerelease: false
|
129
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ~>
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.6.2
|
135
|
+
type: :development
|
136
|
+
version_requirements: *id011
|
137
|
+
description: Elegant, interactive EC2 Management
|
138
|
+
email:
|
139
|
+
- kjp@brightleafsoftware.com
|
140
|
+
executables:
|
141
|
+
- sproutr
|
142
|
+
extensions: []
|
143
|
+
|
144
|
+
extra_rdoc_files: []
|
145
|
+
|
146
|
+
files:
|
147
|
+
- LICENSE
|
148
|
+
- README.md
|
149
|
+
- lib/sproutr/ami.rb
|
150
|
+
- lib/sproutr/cloud.rb
|
151
|
+
- lib/sproutr/definition.rb
|
152
|
+
- lib/sproutr/instance.rb
|
153
|
+
- lib/sproutr/utilities.rb
|
154
|
+
- lib/sproutr.rb
|
155
|
+
- bin/sproutr
|
156
|
+
has_rdoc: true
|
157
|
+
homepage: https://github.com/noeticpenguin/sproutr
|
158
|
+
licenses: []
|
159
|
+
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
none: false
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: "0"
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: "0"
|
177
|
+
requirements: []
|
178
|
+
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 1.6.2
|
181
|
+
signing_key:
|
182
|
+
specification_version: 2
|
183
|
+
summary: An Ec2 management system designed to help you quickly define a new instance, create N clones of said instance and start/stop them
|
184
|
+
test_files: []
|
185
|
+
|