stem 0.4.9 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- description = {}
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 =~ /^(\w+)@(\w+)$/
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 = nil
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
- account = "default"
5
- etc = "#{ENV["HOME"]}/.swirl"
6
- config = \
7
- if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
8
- {
9
- :aws_access_key_id => ENV["AWS_ACCESS_KEY_ID"],
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
- account = account.to_sym
15
-
16
- if File.exists?(etc)
17
- data = YAML.load_file(etc)
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 |(k, v), n|
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
@@ -14,3 +14,6 @@ require 'stem/ip'
14
14
  require 'stem/key_pair'
15
15
  require 'stem/tag'
16
16
 
17
+ module Stem
18
+ autoload :Family, 'stem/family'
19
+ end
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: 29
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 9
10
- version: 0.4.9
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: 2010-12-12 00:00:00 -08:00
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