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.
@@ -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