stem 0.4.9 → 0.5.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/lib/stem/family.rb +116 -0
- data/lib/stem/group.rb +5 -6
- data/lib/stem/image.rb +11 -2
- data/lib/stem/instance.rb +2 -12
- data/lib/stem/util.rb +58 -24
- data/lib/stem.rb +3 -0
- metadata +8 -5
data/lib/stem/family.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
module Stem
|
2
|
+
module Family
|
3
|
+
include Util
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def ami_for(family, release, architecture = "x86_64")
|
7
|
+
amis = Stem::Image::tagged(:family => family,
|
8
|
+
:release => release,
|
9
|
+
:architecture => architecture)
|
10
|
+
throw "More than one AMI matched release." if amis.length > 1
|
11
|
+
amis[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def unrelease family, release_name
|
15
|
+
prev = Stem::Image::tagged(:family => family, :release => release_name)
|
16
|
+
prev.each { |ami| Stem::Tag::destroy(ami, :release => release_name) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def member? family, ami
|
20
|
+
desc = Stem::Image::describe(ami)
|
21
|
+
throw "AMI #{ami} does not exist" if desc.nil?
|
22
|
+
tagset_to_hash(desc["tagSet"])["family"] == family
|
23
|
+
end
|
24
|
+
|
25
|
+
def members family
|
26
|
+
Stem::Image.tagged("family" => family)
|
27
|
+
end
|
28
|
+
|
29
|
+
def describe_members family
|
30
|
+
Stem::Image.describe_tagged("family" => family)
|
31
|
+
end
|
32
|
+
|
33
|
+
def release family, release_name, *amis
|
34
|
+
amis.each do |ami|
|
35
|
+
throw "#{ami} not part of #{family}" unless member?(family, ami)
|
36
|
+
end
|
37
|
+
unrelease family, release_name
|
38
|
+
amis.each { |ami| Stem::Tag::create(ami, :release => release_name) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_image(family, config, userdata)
|
42
|
+
log = lambda { |msg|
|
43
|
+
puts "[#{family}|#{Time.now.to_s}] #{msg}"
|
44
|
+
}
|
45
|
+
|
46
|
+
aggregate_hash_options_for_ami!(config)
|
47
|
+
sha1 = image_hash(config, userdata)
|
48
|
+
|
49
|
+
log.call "Beginning to build image for #{family}"
|
50
|
+
log.call "Config:\n------\n#{ config.inspect }\n-------"
|
51
|
+
instance_id = Stem::Instance.launch(config, userdata)
|
52
|
+
|
53
|
+
log.call "Booting #{instance_id} to produce your prototype instance"
|
54
|
+
wait_for_stopped instance_id, log
|
55
|
+
|
56
|
+
timestamp = Time.now.utc.iso8601
|
57
|
+
image_id = Stem::Image.create("#{family}-#{timestamp}",
|
58
|
+
instance_id,
|
59
|
+
{
|
60
|
+
:created => timestamp,
|
61
|
+
:family => family,
|
62
|
+
:sha1 => sha1,
|
63
|
+
:source_ami => config["ami"]
|
64
|
+
})
|
65
|
+
log.call "Image ID is #{image_id}"
|
66
|
+
|
67
|
+
wait_for_available(image_id, log)
|
68
|
+
|
69
|
+
log "Terminating #{instance_id} now that the image is captured"
|
70
|
+
Stem::Instance::destroy(instance_id)
|
71
|
+
end
|
72
|
+
|
73
|
+
def image_already_built?(family, config, userdata)
|
74
|
+
aggregate_hash_options_for_ami!(config)
|
75
|
+
sha1 = image_hash(config, userdata)
|
76
|
+
!Stem::Image.tagged(:family => family, :sha1 => sha1).empty?
|
77
|
+
end
|
78
|
+
|
79
|
+
def image_hash(config, userdata)
|
80
|
+
Digest::SHA1::hexdigest([config.to_s, userdata].join(' '))
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def wait_for_stopped(instance_id, log)
|
86
|
+
log.call "waiting for instance to reach state stopped -- "
|
87
|
+
while sleep 10
|
88
|
+
state = Stem::Instance.describe(instance_id)["instanceState"]["name"]
|
89
|
+
log.call "instance #{instance_id} #{state}"
|
90
|
+
break if state == "stopped"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def wait_for_available(image_id, log)
|
95
|
+
log.call "Waiting for image to finish capturing..."
|
96
|
+
while sleep 10
|
97
|
+
begin
|
98
|
+
state = Stem::Image.describe(image_id)["imageState"]
|
99
|
+
log.call "Image #{image_id} #{state}"
|
100
|
+
case state
|
101
|
+
when "available"
|
102
|
+
log.call("Image capturing succeeded")
|
103
|
+
break
|
104
|
+
when "pending" #continue
|
105
|
+
when "terminated"
|
106
|
+
log "Image capture failed (#{image_id})"
|
107
|
+
return false
|
108
|
+
else throw "Image unexpectedly entered #{state}";
|
109
|
+
end
|
110
|
+
rescue Swirl::InvalidRequest => e
|
111
|
+
raise unless e.message =~ /does not exist/
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/stem/group.rb
CHANGED
@@ -22,17 +22,16 @@ module Stem
|
|
22
22
|
nil
|
23
23
|
end
|
24
24
|
|
25
|
-
def create(name, rules = nil)
|
26
|
-
create!(name, rules)
|
25
|
+
def create(name, rules = nil, description = "")
|
26
|
+
create!(name, rules, description)
|
27
27
|
true
|
28
28
|
rescue Swirl::InvalidRequest => e
|
29
29
|
raise e unless e.message =~ /The security group '\S+' already exists/
|
30
30
|
false
|
31
31
|
end
|
32
32
|
|
33
|
-
def create!(name, rules = nil)
|
34
|
-
|
35
|
-
swirl.call "CreateSecurityGroup", "GroupName" => name, "GroupDescription" => "%%" + description.to_json
|
33
|
+
def create!(name, rules = nil, description = "")
|
34
|
+
swirl.call "CreateSecurityGroup", "GroupName" => name, "GroupDescription" => description
|
36
35
|
auth(name, rules) if rules
|
37
36
|
end
|
38
37
|
|
@@ -63,7 +62,7 @@ module Stem
|
|
63
62
|
def gen_authorize_target(index, target)
|
64
63
|
if target =~ /^\d+\.\d+\.\d+.\d+\/\d+$/
|
65
64
|
{ "IpPermissions.#{index}.IpRanges.1.CidrIp" => target }
|
66
|
-
elsif target =~ /^(
|
65
|
+
elsif target =~ /^(.+)@(\w+)$/
|
67
66
|
{ "IpPermissions.#{index}.Groups.1.GroupName" => $1,
|
68
67
|
"IpPermissions.#{index}.Groups.1.UserId" => $2 }
|
69
68
|
elsif target =~ /^@(\w+)$/
|
data/lib/stem/image.rb
CHANGED
@@ -3,8 +3,7 @@ module Stem
|
|
3
3
|
include Util
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def create name, instance, tags
|
7
|
-
raise "You already have an image named '#{name}'" if named(name)
|
6
|
+
def create name, instance, tags = {}
|
8
7
|
image_id = swirl.call("CreateImage", "Name" => name, "InstanceId" => instance)["imageId"]
|
9
8
|
unless tags.empty?
|
10
9
|
# We'll retry this once if necessary due to consistency issues on the AWS side
|
@@ -38,8 +37,18 @@ module Stem
|
|
38
37
|
swirl.call("DescribeImages", opts)['imagesSet'].map {|image| image['imageId'] }
|
39
38
|
end
|
40
39
|
|
40
|
+
def describe_tagged tags
|
41
|
+
opts = tags_to_filter(tags).merge("Owner" => "self")
|
42
|
+
images = swirl.call("DescribeImages", opts)["imagesSet"]
|
43
|
+
images.each {|image| image["tags"] = tagset_to_hash(image["tagSet"]) }
|
44
|
+
images
|
45
|
+
end
|
46
|
+
|
41
47
|
def describe image
|
42
48
|
swirl.call("DescribeImages", "ImageId" => image)["imagesSet"][0]
|
49
|
+
rescue Swirl::InvalidRequest => e
|
50
|
+
raise e unless e.message =~ /does not exist/
|
51
|
+
nil
|
43
52
|
end
|
44
53
|
end
|
45
54
|
end
|
data/lib/stem/instance.rb
CHANGED
@@ -5,18 +5,8 @@ module Stem
|
|
5
5
|
|
6
6
|
def launch config, userdata = nil
|
7
7
|
throw "No config provided" unless config
|
8
|
-
|
9
|
-
ami =
|
10
|
-
if config["ami"]
|
11
|
-
ami = config["ami"]
|
12
|
-
elsif config["ami-name"]
|
13
|
-
ami = Image::named(config["ami-name"])
|
14
|
-
throw "AMI named #{config["ami-name"]} was not found. (Does it need creating?)" unless ami
|
15
|
-
elsif config["ami-tags"]
|
16
|
-
ami = Image::tagged(config['ami-tags'])[0]
|
17
|
-
throw "AMI tagged with #{config['ami-tags'].inspect} was not found. (Does it need creating?)" unless ami
|
18
|
-
end
|
19
|
-
throw "No AMI specified." unless ami
|
8
|
+
config = aggregate_hash_options_for_ami!(config)
|
9
|
+
ami = config["ami"]
|
20
10
|
|
21
11
|
opt = {
|
22
12
|
"SecurityGroup.#" => config["groups"] || [],
|
data/lib/stem/util.rb
CHANGED
@@ -1,32 +1,19 @@
|
|
1
1
|
module Stem
|
2
2
|
module Util
|
3
3
|
def swirl
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:aws_secret_access_key => ENV["AWS_SECRET_ACCESS_KEY"],
|
11
|
-
:version => "2010-08-31"
|
12
|
-
}
|
4
|
+
@swirl ||= Swirl::EC2.new load_config
|
5
|
+
end
|
6
|
+
|
7
|
+
def tagset_to_hash(tagset)
|
8
|
+
if tagset["item"].is_a?(Hash)
|
9
|
+
{tagset["item"]["key"] => tagset["item"]["value"]}
|
13
10
|
else
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
abort("I was expecting to find a .swirl file in your home directory.")
|
20
|
-
end
|
21
|
-
|
22
|
-
if data.key?(account)
|
23
|
-
data[account]
|
24
|
-
else
|
25
|
-
abort("I don't see the account you're looking for")
|
11
|
+
tagset["item"].inject({}) do |h,item|
|
12
|
+
k, v = item["key"], item["value"]
|
13
|
+
h[k] = v
|
14
|
+
h
|
26
15
|
end
|
27
16
|
end
|
28
|
-
|
29
|
-
@swirl = Swirl::EC2.new config
|
30
17
|
end
|
31
18
|
|
32
19
|
def tags_to_filter(tags)
|
@@ -47,7 +34,8 @@ module Stem
|
|
47
34
|
|
48
35
|
def get_filter_opts(filters)
|
49
36
|
opts = {}
|
50
|
-
filters.each_with_index do |
|
37
|
+
filters.keys.sort.each_with_index do |k, n|
|
38
|
+
v = filters[k]
|
51
39
|
opts["Filter.#{n}.Name"] = k.to_s
|
52
40
|
v = [ v ] unless v.is_a? Array
|
53
41
|
v.each_with_index do |v, i|
|
@@ -56,6 +44,52 @@ module Stem
|
|
56
44
|
end
|
57
45
|
opts
|
58
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def load_config
|
51
|
+
account = "default"
|
52
|
+
etc = "#{ENV["HOME"]}/.swirl"
|
53
|
+
if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
|
54
|
+
{
|
55
|
+
:aws_access_key_id => ENV["AWS_ACCESS_KEY_ID"],
|
56
|
+
:aws_secret_access_key => ENV["AWS_SECRET_ACCESS_KEY"],
|
57
|
+
:version => "2010-08-31"
|
58
|
+
}
|
59
|
+
else
|
60
|
+
account = account.to_sym
|
61
|
+
|
62
|
+
if File.exists?(etc)
|
63
|
+
data = YAML.load_file(etc)
|
64
|
+
else
|
65
|
+
abort("I was expecting to find a .swirl file in your home directory.")
|
66
|
+
end
|
67
|
+
|
68
|
+
if data.key?(account)
|
69
|
+
data[account]
|
70
|
+
else
|
71
|
+
abort("I don't see the account you're looking for")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def aggregate_hash_options_for_ami!(config)
|
77
|
+
if config["ami"]
|
78
|
+
return config
|
79
|
+
elsif config["ami-name"]
|
80
|
+
name = config.delete("ami-name")
|
81
|
+
config["ami"] = Image::named(name)
|
82
|
+
throw "AMI named #{name} was not found. (Does it need creating?)" unless config["ami"]
|
83
|
+
elsif config["ami-tags"]
|
84
|
+
tags = config.delete('ami-tags')
|
85
|
+
config["ami"] = Image::tagged(tags)[0]
|
86
|
+
throw "AMI tagged with #{tags.inspect} was not found. (Does it need creating?)" unless config["ami"]
|
87
|
+
else
|
88
|
+
throw "No AMI specified."
|
89
|
+
end
|
90
|
+
config
|
91
|
+
end
|
92
|
+
|
59
93
|
end
|
60
94
|
end
|
61
95
|
|
data/lib/stem.rb
CHANGED
metadata
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 5
|
9
|
+
- 0
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Peter van Hardenberg
|
14
14
|
- Orion Henry
|
15
|
+
- Blake Gentry
|
15
16
|
autorequire:
|
16
17
|
bindir: bin
|
17
18
|
cert_chain: []
|
18
19
|
|
19
|
-
date:
|
20
|
+
date: 2011-02-25 00:00:00 -08:00
|
20
21
|
default_executable:
|
21
22
|
dependencies:
|
22
23
|
- !ruby/object:Gem::Dependency
|
@@ -39,6 +40,7 @@ description: minimalist EC2 instance management
|
|
39
40
|
email:
|
40
41
|
- pvh@heroku.com
|
41
42
|
- orion@heroku.com
|
43
|
+
- b@heroku.com
|
42
44
|
executables:
|
43
45
|
- stem
|
44
46
|
extensions: []
|
@@ -49,6 +51,7 @@ files:
|
|
49
51
|
- LICENSE
|
50
52
|
- README.md
|
51
53
|
- lib/stem/cli.rb
|
54
|
+
- lib/stem/family.rb
|
52
55
|
- lib/stem/group.rb
|
53
56
|
- lib/stem/image.rb
|
54
57
|
- lib/stem/instance.rb
|