stackster 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -28,6 +28,10 @@ stackster create -n STACK_NAME -a ATTRIBUTES -t TEMPLATE_PATH
28
28
  stackster update -n STACK_NAME -a ATTRIBUTES
29
29
  stackster show -n STACK_NAME
30
30
  stackster destroy -n STACK_NAME
31
+
32
+ Attributes are specified as '=' seperated key value pairs. Multiple can be specified. For example:
33
+
34
+ stackster create -n test-stack -t ~/my-template.json -a arg1=val1 arg2=vol2
31
35
  ```
32
36
 
33
37
  For more information, run stackster -h.
data/lib/stackster/cli.rb CHANGED
@@ -14,9 +14,12 @@ stackster update -n STACK_NAME -a ATTRIBUTES
14
14
  stackster show -n STACK_NAME
15
15
  stackster destroy -n STACK_NAME
16
16
 
17
+ Attributes are specified as '=' seperated key value pairs. Multiple can be specified. For example:
18
+
19
+ stackster create -n test-stack -t ~/my-template.json -a arg1=val1 arg2=vol2
17
20
  EOS
18
21
  opt :help, "Display Help"
19
- opt :attributes, "CSV list of = seperated attributes to set", :type => :string
22
+ opt :attributes, "CSV list of = seperated attributes to set", :type => :strings
20
23
  opt :name, "Stack name to manage", :type => :string
21
24
  opt :template, "Path to the template file", :type => :string
22
25
  end
@@ -29,8 +32,6 @@ EOS
29
32
  :config => nil
30
33
  end
31
34
 
32
- read_attributes
33
-
34
35
  case @cmd
35
36
  when 'create'
36
37
  @s.create :attributes => attributes,
@@ -45,12 +46,14 @@ EOS
45
46
  when 'show'
46
47
  puts @s.display.to_yaml
47
48
  when 'list'
48
- puts Stack.list(:config => nil)
49
+ puts StackLister.new.all
49
50
  else
50
51
  puts "Unkown command '#{@cmd}'"
51
52
  end
52
53
  end
53
54
 
55
+ private
56
+
54
57
  def self.attributes
55
58
  attrs = []
56
59
  read_attributes.each do |attribs|
@@ -61,7 +64,7 @@ EOS
61
64
  end
62
65
 
63
66
  def self.read_attributes
64
- @opts[:attributes].nil? ? [] : @opts[:attributes].split(',')
67
+ @opts[:attributes].nil? ? [] : @opts[:attributes]
65
68
  end
66
69
 
67
70
  end
@@ -1,25 +1,34 @@
1
+ require 'logger'
2
+
1
3
  module Stackster
2
4
  class Config
3
5
 
4
6
  def initialize(args = {})
5
- @config = args[:config]
6
- end
7
-
8
- def load_config_file
9
- config_file = "#{ENV['HOME']}/.stackster.yml"
10
- @config ? @config : (YAML::load File.open( config_file ))
7
+ @config = load_config_file args[:config]
8
+ @logger = args[:logger] ||= StacksterLogger.new
11
9
  end
12
10
 
13
11
  def access_key
14
- load_config_file['access_key']
12
+ @config['access_key']
15
13
  end
16
14
 
17
15
  def secret_key
18
- load_config_file['secret_key']
16
+ @config['secret_key']
19
17
  end
20
18
 
21
19
  def region
22
- load_config_file['region']
20
+ @config['region']
21
+ end
22
+
23
+ def logger
24
+ @logger
25
+ end
26
+
27
+ private
28
+
29
+ def load_config_file(config = nil)
30
+ config_file = "#{ENV['HOME']}/.stackster.yml"
31
+ config ? config : (YAML::load File.open( config_file ))
23
32
  end
24
33
 
25
34
  end
@@ -4,16 +4,21 @@ module Stackster
4
4
  def initialize(args)
5
5
  @domain = 'stacks'
6
6
  @config = args[:config]
7
- @sdb_connect = AWS::SimpleDB.new :config => args[:config]
8
7
  end
9
8
 
10
9
  def all
11
- entries = @sdb_connect.select "select * from #{@domain}"
10
+ entries = sdb_connect.select "select * from #{@domain}"
12
11
  entries.keys.map do |name|
13
12
  remove_region_from_entry(name)
14
13
  end
15
14
  end
16
15
 
16
+ private
17
+
18
+ def sdb_connect
19
+ @sdb_connect ||= AWS::SimpleDB.new :config => @config
20
+ end
21
+
17
22
  def remove_region_from_entry(name)
18
23
  name.gsub(/-[a-z]{2}-[a-z]*-[0-9]{1,2}$/, '')
19
24
  end
@@ -7,53 +7,54 @@ module Stackster
7
7
  def initialize(args)
8
8
  @domain = 'stacks'
9
9
  @config = args[:config]
10
- self.name = "#{args[:name]}-#{@config.region}"
10
+ self.name = region_specific_name args[:name]
11
11
  get_attributes
12
12
  end
13
13
 
14
14
  def self.find(args)
15
- e = Entry.new :name => args[:name],
16
- :config => args[:config]
17
- e.get_attributes
18
- e
15
+ entry = Entry.new :name => args[:name],
16
+ :config => args[:config]
17
+ entry
19
18
  end
20
19
 
21
- def create_domain
22
- sdb_connect.create_domain @domain
20
+ def set_attributes(a)
21
+ a.each { |attribute| set_attribute(attribute) }
22
+ end
23
+
24
+ def save
25
+ create_domain
26
+ attributes.each_pair do |k,v|
27
+ sdb_connect.put_attributes('stacks', name, { k => v }, { :replace => k })
28
+ end
29
+ end
30
+
31
+ def delete_attributes
32
+ sdb_connect.delete('stacks', name)
23
33
  end
24
34
 
35
+ private
36
+
25
37
  def get_attributes
26
38
  u = {}
27
39
  attrs = sdb_connect.select("select * from stacks where itemName() = '#{name}'")
28
40
  if attrs[name]
29
- attrs[name].each_pair do |k, v|
30
- u[k] = v.first
31
- end
41
+ attrs[name].each_pair { |k, v| u[k] = v.first }
32
42
  end
33
43
  self.attributes = u
34
44
  end
35
45
 
36
- def save
37
- attributes.each_pair do |k,v|
38
- sdb_connect.put_attributes('stacks', name, { k => v }, { :replace => k })
39
- end
46
+ def region_specific_name(name)
47
+ "#{name}-#{@config.region}"
40
48
  end
41
49
 
42
- def set_attributes(a)
43
- a.each { |attribute| set_attribute(attribute) }
50
+ def create_domain
51
+ sdb_connect.create_domain @domain
44
52
  end
45
53
 
46
54
  def set_attribute(attribute)
47
- create_domain
48
55
  self.attributes = attributes.merge(attribute)
49
56
  end
50
57
 
51
- def delete_attributes
52
- sdb_connect.delete('stacks', name)
53
- end
54
-
55
- private
56
-
57
58
  def sdb_connect
58
59
  @sdb_connect ||= AWS::SimpleDB.new :config => @config
59
60
  end
@@ -2,18 +2,20 @@ module Stackster
2
2
  class InstanceReader
3
3
 
4
4
  def initialize(args)
5
- @ec2 = AWS::EC2.new :config => args[:config]
5
+ @config = args[:config]
6
6
  end
7
7
 
8
8
  # Data structure is return deffernelty for class
9
+ # ToDo - Add support for classic
9
10
  # Currently only supports VPC
10
11
  def list_stack_instances(stack_name)
11
12
  h = []
12
13
  describe_instances.each do |instance|
13
14
  tag_set = instance['instancesSet'].first['tagSet']
14
- if tag_set['aws:cloudformation:stack-name']
15
- if tag_set['aws:cloudformation:stack-name'] == stack_name
16
- h << instance if instance_running? instance
15
+ instance_stack_name = tag_set['aws:cloudformation:stack-name']
16
+ if instance_stack_name && instance_stack_name == stack_name
17
+ if instance_running? instance
18
+ h << instance
17
19
  end
18
20
  end
19
21
  end
@@ -22,6 +24,10 @@ module Stackster
22
24
 
23
25
  private
24
26
 
27
+ def ec2
28
+ @ec2 ||= AWS::EC2.new :config => @config
29
+ end
30
+
25
31
  def get_instance_state(instance_hash)
26
32
  instance_hash['instancesSet'].first['instanceState']['name']
27
33
  end
@@ -31,7 +37,7 @@ module Stackster
31
37
  end
32
38
 
33
39
  def describe_instances
34
- @ec2.describe_instances
40
+ ec2.describe_instances
35
41
  end
36
42
 
37
43
  end
@@ -0,0 +1,21 @@
1
+ module Stackster
2
+ class StacksterLogger
3
+
4
+ def initialize(args = {})
5
+ @logger = args[:logger] ||= Logger.new(STDOUT)
6
+
7
+ unless args[:logger]
8
+ @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
9
+ @logger.formatter = proc do |severity, datetime, progname, msg|
10
+ "#{datetime}: #{msg}\n"
11
+ end
12
+ end
13
+
14
+ @logger
15
+ end
16
+
17
+ def info(msg)
18
+ @logger.info msg
19
+ end
20
+ end
21
+ end
@@ -8,7 +8,18 @@ module Stackster
8
8
  @entry = args[:entry]
9
9
  @name = args[:name]
10
10
  @template = read_template_from_file args[:template_file]
11
- @cf = AWS::CloudFormation.new :config => @config
11
+ end
12
+
13
+ def create
14
+ cloud_formation.create :name => @name,
15
+ :parameters => read_parameters_from_entry,
16
+ :template => @template
17
+ end
18
+
19
+ private
20
+
21
+ def cloud_formation
22
+ @cf ||= AWS::CloudFormation.new :config => @config
12
23
  end
13
24
 
14
25
  def read_template_from_file(template_file)
@@ -23,17 +34,12 @@ module Stackster
23
34
 
24
35
  def read_parameters_from_entry
25
36
  h = {}
37
+ attributes = @entry.attributes
26
38
  read_parameters_from_template.each do |p|
27
- h[p] = @entry.attributes[p] if @entry.attributes[p]
39
+ h[p] = attributes[p] if attributes[p]
28
40
  end
29
41
  h
30
42
  end
31
43
 
32
- def create
33
- @cf.create :name => @name,
34
- :parameters => read_parameters_from_entry,
35
- :template => @template
36
- end
37
-
38
44
  end
39
45
  end
@@ -4,11 +4,16 @@ module Stackster
4
4
  def initialize(args)
5
5
  @config = args[:config]
6
6
  @name = args[:name]
7
- @cf = AWS::CloudFormation.new :config => @config
8
7
  end
9
8
 
10
9
  def destroy
11
- @cf.destroy @name
10
+ cloud_formation.destroy @name
11
+ end
12
+
13
+ private
14
+
15
+ def cloud_formation
16
+ @cf ||= AWS::CloudFormation.new :config => @config
12
17
  end
13
18
 
14
19
  end
@@ -13,38 +13,9 @@ module Stackster
13
13
  'outputs' => stack_reader.outputs,
14
14
  'events' => stack_reader.events,
15
15
  'resources' => stack_reader.resources,
16
- 'instances' => instances_private_ip_addresses
17
16
  }
18
17
  end
19
18
 
20
- def instances_private_ip_addresses
21
- stack_reader.instances.map do |i|
22
- i['instancesSet'].first['privateIpAddress'] if i['instancesSet'].first['privateIpAddress']
23
- end
24
- end
25
-
26
- def attribute_names
27
- attributes.keys
28
- end
29
-
30
- def attributes
31
- stack_reader.attributes
32
- end
33
-
34
- def resources_summary
35
- stack_reader.resources.map do |i|
36
- { 'LogicalResourceId' => i['LogicalResourceId'],
37
- 'PhysicalResourceId' => i['PhysicalResourceId'] }
38
- end
39
- end
40
-
41
- def events_summary
42
- stack_reader.events.map do |i|
43
- { 'ResourceStatus' => i['ResourceStatus'],
44
- 'LogicalResourceId' => i['LogicalResourceId'] }
45
- end
46
- end
47
-
48
19
  private
49
20
 
50
21
  def stack_reader
@@ -1,13 +1,18 @@
1
1
  module Stackster
2
2
  class StackLister
3
3
 
4
- def initialize(args={})
5
- @config = args[:config]
4
+ def initialize(args = {})
5
+ @config = args[:config] ||= Config.new
6
6
  end
7
7
 
8
8
  def all
9
- e = EntryLister.new :config => @config
10
- e.all
9
+ entry_lister.all
10
+ end
11
+
12
+ private
13
+
14
+ def entry_lister
15
+ @entry_lister ||= EntryLister.new :config => @config
11
16
  end
12
17
 
13
18
  end
@@ -4,38 +4,53 @@ module Stackster
4
4
  def initialize(args)
5
5
  @name = args[:name]
6
6
  @config = args[:config]
7
- @cf = AWS::CloudFormation.new :config => @config
8
- @entry = Entry.find :name => @name,
9
- :config => @config
10
7
  end
11
8
 
12
9
  def attributes
13
- @entry.attributes
10
+ entry.attributes
14
11
  end
15
12
 
16
13
  def outputs
17
- @cf.stack_outputs @name
14
+ cloud_formation.stack_outputs @name
18
15
  end
19
16
 
20
17
  def status
21
- @cf.stack_status @name
18
+ cloud_formation.stack_status @name
22
19
  end
23
20
 
24
21
  def events
25
- @cf.stack_events @name
22
+ cloud_formation.stack_events @name
26
23
  end
27
24
 
28
25
  def resources
29
- @cf.stack_resources @name
26
+ cloud_formation.stack_resources @name
30
27
  end
31
28
 
32
29
  def template
33
- @cf.template @name
30
+ cloud_formation.template @name
31
+ end
32
+
33
+ def parameters
34
+ json = JSON.parse template
35
+ json['Parameters'].nil? ? [] : json['Parameters'].keys
34
36
  end
35
37
 
36
38
  def instances
37
- ir = InstanceReader.new :config => @config
38
- ir.list_stack_instances @name
39
+ instance_reader.list_stack_instances @name
40
+ end
41
+
42
+ private
43
+
44
+ def entry
45
+ @entry ||= Entry.find :name => @name, :config => @config
46
+ end
47
+
48
+ def cloud_formation
49
+ @cloud_formation ||= AWS::CloudFormation.new :config => @config
50
+ end
51
+
52
+ def instance_reader
53
+ @instance_reader ||= InstanceReader.new :config => @config
39
54
  end
40
55
 
41
56
  end
@@ -8,26 +8,44 @@ module Stackster
8
8
  @entry = args[:entry]
9
9
  @name = args[:name]
10
10
  @template_body = args[:template_body]
11
- @cf = AWS::CloudFormation.new :config => @config
12
11
  end
13
12
 
13
+ def update_stack_if_parameters_changed(attributes)
14
+ parameter_updated?(attributes) ? update : false
15
+ end
16
+
17
+ private
18
+
14
19
  def update
15
- @cf.update :name => @name,
16
- :parameters => read_parameters_from_entry,
17
- :template => @template_body
20
+ cloud_formation.update :name => @name,
21
+ :parameters => read_parameters_from_entry_attributes,
22
+ :template => @template_body
23
+ end
24
+
25
+ def parameter_updated?(attributes)
26
+ (template_parameters - updated_parameters(attributes)) != template_parameters
27
+ end
28
+
29
+ def template_parameters
30
+ json = JSON.parse @template_body
31
+ json['Parameters'].nil? ? [] : json['Parameters'].keys
32
+ end
33
+
34
+ def updated_parameters attributes
35
+ (attributes.map { |s| s.keys }).flatten
18
36
  end
19
37
 
20
- def read_parameters_from_entry
38
+ def read_parameters_from_entry_attributes
21
39
  h = {}
22
- read_parameters_from_template.each do |p|
23
- h[p] = @entry.attributes[p] if @entry.attributes[p]
40
+ entry_attributes = @entry.attributes
41
+ template_parameters.each do |p|
42
+ h[p] = entry_attributes[p] if entry_attributes[p]
24
43
  end
25
44
  h
26
45
  end
27
46
 
28
- def read_parameters_from_template
29
- t = JSON.parse @template_body
30
- t['Paramaters'].nil? ? t['Parameters'].keys : []
47
+ def cloud_formation
48
+ @cloud_formation ||= AWS::CloudFormation.new :config => @config
31
49
  end
32
50
 
33
51
  end
@@ -9,52 +9,45 @@ module Stackster
9
9
  class Stack
10
10
 
11
11
  def initialize(args)
12
- @config = Config.new :config => args[:config]
13
12
  @name = args[:name]
14
- @entry = Entry.new :name => @name,
13
+ @config = Config.new :config => args[:config],
14
+ :logger => args[:logger]
15
+ @entry = Entry.new :name => @name,
15
16
  :config => @config
16
17
  end
17
18
 
18
- def self.list(args)
19
- @config = Config.new :config => args[:config]
20
- stack_lister.all
21
- end
22
-
19
+ # ToDo - maybe in stack creater
20
+ # Rescure stack creation errors and verify entry is not saved
23
21
  def create(args)
24
22
  @template_file = args[:template]
25
23
  @entry.set_attributes args[:attributes]
26
- @entry.save
27
24
  stack_creater.create
25
+ @entry.save
28
26
  end
29
27
 
28
+ # To Do - maybe in stack udpater
29
+ # Rescure stack creation errors and verify entry is not saved
30
30
  def update(args)
31
31
  @template_body = template
32
32
  @entry.set_attributes args[:attributes]
33
+ stack_updater.update_stack_if_parameters_changed args[:attributes]
33
34
  @entry.save
34
- update_stack_if_parameters_changed args[:attributes]
35
- end
36
-
37
- def update_stack_if_parameters_changed(updated_parameters)
38
- current_parameters = JSON.parse(template)['Parameters'].keys
39
- updated_parameters = (updated_parameters.map { |s| s.keys }).flatten
40
-
41
- if (current_parameters - updated_parameters) != current_parameters
42
- stack_updater.update
43
- end
44
35
  end
45
36
 
37
+ # To do - maybe in stack destroyer
38
+ # Rescue stack creation exception and only delete on success
46
39
  def destroy
47
40
  stack_destroyer.destroy
48
41
  @entry.delete_attributes
49
42
  end
50
-
51
- def attributes
52
- stack_formater.attributes
53
- end
54
43
 
55
44
  def display
56
45
  stack_formater.display
57
46
  end
47
+
48
+ def attributes
49
+ stack_reader.attributes
50
+ end
58
51
 
59
52
  def show
60
53
  stack_reader.show
@@ -76,14 +69,6 @@ module Stackster
76
69
  stack_reader.instances
77
70
  end
78
71
 
79
- def instances_public_ip_addresses
80
- stack_formater.instances_public_ip_addresses
81
- end
82
-
83
- def instances_private_ip_addresses
84
- stack_formater.instances_private_ip_addresses
85
- end
86
-
87
72
  def resources
88
73
  stack_reader.resources
89
74
  end
@@ -92,6 +77,10 @@ module Stackster
92
77
  stack_reader.template
93
78
  end
94
79
 
80
+ def parameters
81
+ stack_reader.parameters
82
+ end
83
+
95
84
  private
96
85
 
97
86
  def stack_creater
@@ -123,9 +112,5 @@ module Stackster
123
112
  :config => @config
124
113
  end
125
114
 
126
- def self.stack_lister
127
- @stack_lister ||= StackLister.new :config => @config
128
- end
129
-
130
115
  end
131
116
  end
@@ -1,3 +1,3 @@
1
1
  module Stackster
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/stackster.rb CHANGED
@@ -2,5 +2,6 @@ require "stackster/aws"
2
2
  require "stackster/entry"
3
3
  require "stackster/stack"
4
4
  require "stackster/instance"
5
+ require "stackster/logger"
5
6
  require "stackster/config"
6
7
  require "stackster/version"
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ ['create', 'update', 'destroy', 'describe_stack', 'stack_resources',
6
+ 'stack_events', 'stack_status', 'stack_outputs', 'template'].each do |t|
7
+ it "should test #{t}"
8
+ end
9
+
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "should test describe instances"
6
+
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ ['domains', 'create_domain', 'put_attributes',
6
+ 'select', 'delete' ].each do |t|
7
+ it "should test #{t}"
8
+ end
9
+
10
+ end
data/spec/config_spec.rb CHANGED
@@ -1,11 +1,38 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Config do
3
+ describe Stackster do
4
4
 
5
- it "should create a new config object" do
5
+ before do
6
+ @config = { 'access_key' => 'the-key',
7
+ 'secret_key' => 'secret',
8
+ 'region' => 'us-west-1' }
9
+ end
10
+
11
+ it "should create a new config object from the hash passed as config" do
12
+ config = Stackster::Config.new :config => @config
13
+ config.access_key.should == @config['access_key']
14
+ config.secret_key.should == @config['secret_key']
15
+ config.region.should == @config['region']
16
+ end
17
+
18
+ it "should create a new config object and read from ~/.stackster.yml" do
19
+ File.should_receive(:open).with("#{ENV['HOME']}/.stackster.yml").
20
+ and_return(@config.to_yaml)
6
21
  config = Stackster::Config.new
7
- config.class.should == Stackster::Config
22
+ config.access_key.should == @config['access_key']
23
+ config.secret_key.should == @config['secret_key']
24
+ config.region.should == @config['region']
8
25
  end
9
26
 
10
- end
27
+ it "should create a new logger when one is not specified" do
28
+ config = Stackster::Config.new :config => @config
29
+ config.logger.class.should == Stackster::StacksterLogger
30
+ end
31
+
32
+ it "should accept a logger passed as :logger" do
33
+ config = Stackster::Config.new :config => @config,
34
+ :logger => 'the-logger'
35
+ config.logger.should == 'the-logger'
36
+ end
11
37
 
38
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "should create a list of entries" do
6
+ @simple_db_mock = mock 'simple db'
7
+ config = Stackster::Config.new
8
+ Stackster::AWS::SimpleDB.should_receive(:new).and_return @simple_db_mock
9
+ @simple_db_mock.should_receive(:select).
10
+ with("select * from stacks").
11
+ and_return('stack-to-find-us-west-1' => { 'attr1' => 'value1' })
12
+ entry_lister = Stackster::EntryLister.new :config => config
13
+ entry_lister.all.should == ['stack-to-find']
14
+ end
15
+
16
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "should create a new entry object" do
6
+ @simple_db_mock = mock 'simple db'
7
+ config = Stackster::Config.new
8
+ Stackster::AWS::SimpleDB.should_receive(:new).and_return @simple_db_mock
9
+ @simple_db_mock.should_receive(:select).
10
+ with("select * from stacks where itemName() = 'test-stack-us-west-1'").
11
+ and_return('stack-to-find' => { 'attr1' => 'value1' })
12
+ entry = Stackster::Entry.new :config => config,
13
+ :name => 'test-stack'
14
+ entry.class.should == Stackster::Entry
15
+ end
16
+
17
+ it "should find the requested stack in simple db" do
18
+ @simple_db_mock = mock 'simple db'
19
+ Stackster::AWS::SimpleDB.should_receive(:new).and_return @simple_db_mock
20
+
21
+ @simple_db_mock.should_receive(:select).
22
+ with("select * from stacks where itemName() = 'stack-to-find-us-west-1'").
23
+ and_return('stack-to-find' => { 'attr1' => 'value1' })
24
+ Stackster::Entry.find :name => 'stack-to-find',
25
+ :config => Stackster::Config.new
26
+ end
27
+
28
+ context "with stack object" do
29
+ before do
30
+ @config = Stackster::Config.new
31
+ @simple_db_mock = mock 'simple db'
32
+ Stackster::AWS::SimpleDB.should_receive(:new).and_return @simple_db_mock
33
+ @simple_db_mock.should_receive(:select).
34
+ with("select * from stacks where itemName() = 'test-stack-us-west-1'").
35
+ and_return('test-stack' => { 'attr1' => 'value1' })
36
+ @entry = Stackster::Entry.new :config => @config,
37
+ :name => 'test-stack'
38
+ end
39
+
40
+ it "should set the name to region-name for the stack" do
41
+ @entry.name.should == 'test-stack-us-west-1'
42
+ end
43
+
44
+ it "should set the attributes in simple db" do
45
+ @simple_db_mock.should_receive(:create_domain).with('stacks')
46
+ @simple_db_mock.should_receive(:put_attributes).
47
+ with("stacks", "test-stack-us-west-1", {"key"=>"value"}, {:replace=>"key"})
48
+ @entry.attributes = {"key"=>"value"}
49
+
50
+ @entry.save
51
+ end
52
+
53
+ it "should set the attributes" do
54
+ @entry.attributes = {'key1' => 'value1' }
55
+ @entry.set_attributes(['key2' => 'value2'])
56
+ @entry.attributes.should == {'key1' => 'value1', 'key2' => 'value2' }
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+
6
+ describe "with a single running instance" do
7
+ before do
8
+ @instances = [
9
+ { 'instancesSet' =>
10
+ [
11
+ { 'tagSet' =>
12
+ { 'aws:cloudformation:stack-name' => 'my-stack' },
13
+ 'instanceState' =>
14
+ { 'name' => 'running' }
15
+ }
16
+ ]
17
+ },
18
+ { 'instancesSet' =>
19
+ [
20
+ { 'tagSet' =>
21
+ { 'aws:cloudformation:stack-name' => 'my-stack' },
22
+ 'instanceState' =>
23
+ { 'name' => 'terminated' }
24
+ }
25
+ ]
26
+ },
27
+ { 'instancesSet' =>
28
+ [
29
+ { 'tagSet' =>
30
+ { 'aws:cloudformation:stack-name' => 'dead-stack' },
31
+ 'instanceState' =>
32
+ { 'name' => 'terminated' }
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ end
38
+
39
+ it "should list running instance within the given stack" do
40
+ @ec2_mock = mock 'simple ec2'
41
+ config = Stackster::Config.new
42
+ Stackster::AWS::EC2.should_receive(:new).
43
+ with(:config => config).
44
+ and_return @ec2_mock
45
+ instance_reader = Stackster::InstanceReader.new :config => config
46
+ @ec2_mock.should_receive(:describe_instances).
47
+ and_return(@instances)
48
+ instance_reader.list_stack_instances('my-stack').count.
49
+ should == 1
50
+ end
51
+
52
+ it "should not include instances which are in different stacks" do
53
+ @ec2_mock = mock 'simple ec2'
54
+ config = Stackster::Config.new
55
+ Stackster::AWS::EC2.should_receive(:new).
56
+ with(:config => config).
57
+ and_return @ec2_mock
58
+ instance_reader = Stackster::InstanceReader.new :config => config
59
+ @ec2_mock.should_receive(:describe_instances).
60
+ and_return(@instances)
61
+ instance_reader.list_stack_instances('another-stack').count.
62
+ should == 0
63
+ end
64
+
65
+ it "should not include instances which are not running" do
66
+ @ec2_mock = mock 'simple ec2'
67
+ config = Stackster::Config.new
68
+ Stackster::AWS::EC2.should_receive(:new).
69
+ with(:config => config).
70
+ and_return @ec2_mock
71
+ instance_reader = Stackster::InstanceReader.new :config => config
72
+ @ec2_mock.should_receive(:describe_instances).
73
+ and_return(@instances)
74
+ instance_reader.list_stack_instances('dead-stack').count.
75
+ should == 0
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "should create a new logger object from the hash passed as :logger" do
6
+ logger_mock = mock 'logger'
7
+ logger_mock.should_receive(:info).with 'a message'
8
+ logger = Stackster::StacksterLogger.new :logger => logger_mock
9
+ logger.info 'a message'
10
+ end
11
+
12
+ it "should create a new logger object when one is not passed" do
13
+ logger_mock = mock 'logger'
14
+ Logger.should_receive(:new).with(STDOUT).and_return logger_mock
15
+ logger_mock.should_receive(:info).with 'a message'
16
+ logger = Stackster::StacksterLogger.new
17
+ logger.info 'a message'
18
+ end
19
+
20
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe Stackster do
5
+
6
+ before do
7
+ @attributes = { "param1" => "value1", "param3" => "value3" }
8
+ @template_json = '{ "Parameters":
9
+ {
10
+ "param1" :
11
+ {
12
+ "Description" : "param-1"
13
+ },
14
+ "param2" :
15
+ {
16
+ "Description" : "param-2"
17
+ }
18
+ }
19
+ }'
20
+ end
21
+
22
+ it "should map the attributes to a template's parameters and create a stack " do
23
+ config_mock = mock 'config mock'
24
+ entry_mock = mock 'entry mock'
25
+ file_mock = mock 'file mock'
26
+ cloud_formation_mock = mock 'cloud formation mock'
27
+ Stackster::AWS::CloudFormation.should_receive(:new).
28
+ with(:config => config_mock).
29
+ and_return cloud_formation_mock
30
+ File.should_receive(:open).with('path_to_file').
31
+ and_return file_mock
32
+ file_mock.should_receive(:read).and_return @template_json
33
+ entry_mock.should_receive(:attributes).and_return @attributes
34
+ cloud_formation_mock.should_receive(:create).
35
+ with(:name => 'test-stack',
36
+ :parameters => { 'param1' => 'value1' },
37
+ :template => @template_json)
38
+ stack_creater = Stackster::StackCreater.new :name => 'test-stack',
39
+ :template_file => 'path_to_file',
40
+ :entry => entry_mock,
41
+ :config => config_mock
42
+
43
+ stack_creater.create
44
+ end
45
+
46
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "should destroy the stack" do
6
+ config_mock = mock 'config mock'
7
+ cloud_formation_mock = mock 'cloud formation mock'
8
+ Stackster::AWS::CloudFormation.should_receive(:new).
9
+ with(:config => config_mock).
10
+ and_return cloud_formation_mock
11
+ cloud_formation_mock.should_receive(:destroy).with 'test-stack'
12
+
13
+ stack_destroyer = Stackster::StackDestroyer.new :name => 'test-stack',
14
+ :config => config_mock
15
+ stack_destroyer.destroy
16
+ end
17
+
18
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ ['attributes', 'attribute_names', 'instances_private_ip_addresses',
6
+ 'display', 'resources', 'template'].each do |t|
7
+ it "should test #{t}"
8
+ end
9
+
10
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "should list the stack entries" do
6
+ config_mock = mock 'config mock'
7
+ entry_lister_mock = mock 'entry lister mock'
8
+ Stackster::EntryLister.should_receive(:new).
9
+ with(:config => config_mock).
10
+ and_return entry_lister_mock
11
+ entry_lister_mock.should_receive(:all)
12
+
13
+ stack_lister = Stackster::StackLister.new :config => config_mock
14
+ stack_lister.all
15
+ end
16
+
17
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ ['attributes', 'outputs', 'status', 'events', 'resources',
6
+ 'instances', 'resources', 'template'].each do |t|
7
+ it "should test #{t}"
8
+ end
9
+
10
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe Stackster do
5
+
6
+ before do
7
+ @template_body = '{ "Parameters":
8
+ {
9
+ "param1" :
10
+ {
11
+ "Description" : "param-1"
12
+ },
13
+ "param2" :
14
+ {
15
+ "Description" : "param-2"
16
+ }
17
+ }
18
+ }'
19
+ end
20
+
21
+ it "should update the stack when parameters change" do
22
+ attributes = { "param1" => "value1", "param3" => "value3" }
23
+ config_mock = mock 'config mock'
24
+ entry_mock = mock 'entry mock'
25
+ cloud_formation_mock = mock 'cloud formation mock'
26
+ Stackster::AWS::CloudFormation.should_receive(:new).
27
+ with(:config => config_mock).
28
+ and_return cloud_formation_mock
29
+ entry_mock.should_receive(:attributes).and_return attributes
30
+ cloud_formation_mock.should_receive(:update).
31
+ with(:name => 'test-stack',
32
+ :parameters => { 'param1' => 'value1' },
33
+ :template => @template_body).
34
+ and_return true
35
+ stack_updater = Stackster::StackUpdater.new :name => 'test-stack',
36
+ :template_body => @template_body,
37
+ :entry => entry_mock,
38
+ :config => config_mock
39
+
40
+ stack_updater.update_stack_if_parameters_changed( [ { 'param1' => 'new-value' } ] ).
41
+ should == true
42
+ end
43
+
44
+ it "should not update the stack when parameters don't change" do
45
+ attributes = { "param3" => "value3" }
46
+ config_mock = mock 'config mock'
47
+ entry_mock = mock 'entry mock'
48
+ Stackster::AWS::CloudFormation.should_receive(:new).exactly(0).times
49
+ stack_updater = Stackster::StackUpdater.new :name => 'test-stack',
50
+ :template_body => @template_body,
51
+ :entry => entry_mock,
52
+ :config => config_mock
53
+
54
+ stack_updater.update_stack_if_parameters_changed( [ { 'another-param' => 'new-value' } ] ).
55
+ should == false
56
+ end
57
+
58
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ it "create a new stack object" do
6
+ config_mock = mock 'config mock'
7
+ Stackster::Config.should_receive(:new).and_return config_mock
8
+ Stackster::Entry.should_receive(:new).with :name => 'test-stack',
9
+ :config => config_mock
10
+ stack = Stackster::Stack.new :name => 'test-stack'
11
+ stack.class.should == Stackster::Stack
12
+ end
13
+
14
+ describe "mocking a stack object" do
15
+ before do
16
+ @config_mock = mock 'config mock'
17
+ Stackster::Config.should_receive(:new).and_return @config_mock
18
+ @entry_mock = mock 'entry mock'
19
+ Stackster::Entry.should_receive(:new).with(:name => 'test-stack',
20
+ :config => @config_mock).
21
+ and_return @entry_mock
22
+ @stack = Stackster::Stack.new :name => 'test-stack',
23
+ :config => 'my-config',
24
+ :logger => 'my-logger'
25
+ end
26
+
27
+ context "when creating a stack" do
28
+
29
+ it "should create a new stack" do
30
+ stack_creater_mock = mock 'stack creater'
31
+ @entry_mock.should_receive(:set_attributes).with({ 'test-attr' => 'test-value' })
32
+ @entry_mock.should_receive(:save)
33
+ Stackster::StackCreater.should_receive(:new).with(:name => 'test-stack',
34
+ :entry => @entry_mock,
35
+ :template_file => 'template_file',
36
+ :config => @config_mock).
37
+ and_return stack_creater_mock
38
+ stack_creater_mock.should_receive(:create)
39
+
40
+ @stack.create :template => 'template_file',
41
+ :attributes => { 'test-attr' => 'test-value' }
42
+ end
43
+
44
+ it "should not create a stack entry if the create fails"
45
+
46
+ end
47
+
48
+ context "when updating a stack" do
49
+
50
+ it "should should update an existing stack" do
51
+ stack_updater_mock = mock 'stack updater'
52
+ stack_reader_mock = mock 'stack reader'
53
+ @entry_mock.should_receive(:set_attributes).
54
+ with({ 'update' => 'test-attrs' })
55
+ @entry_mock.should_receive(:save)
56
+ stack_reader_mock.should_receive(:template).and_return('template-body-json')
57
+
58
+ Stackster::StackReader.should_receive(:new).with(:name => 'test-stack',
59
+ :config => @config_mock).
60
+ and_return stack_reader_mock
61
+ Stackster::StackUpdater.should_receive(:new).with(:name => 'test-stack',
62
+ :entry => @entry_mock,
63
+ :template_body => 'template-body-json',
64
+ :config => @config_mock).
65
+ and_return stack_updater_mock
66
+ stack_updater_mock.should_receive(:update_stack_if_parameters_changed)
67
+ @stack.update :attributes => { 'update' => 'test-attrs' }
68
+ end
69
+
70
+ it "should not update a stack entry if the create fails"
71
+ end
72
+
73
+ context "when destroying a stack" do
74
+ it "should destroy a stack" do
75
+ stack_destroyer_mock = mock 'stack destroyer'
76
+ Stackster::StackDestroyer.should_receive(:new).with(:name => 'test-stack',
77
+ :config => @config_mock).
78
+ and_return stack_destroyer_mock
79
+ stack_destroyer_mock.should_receive(:destroy)
80
+ @entry_mock.should_receive(:delete_attributes)
81
+ @stack.destroy
82
+ end
83
+ end
84
+
85
+ ['attributes', 'display', 'show', 'outputs',
86
+ 'status', 'events', 'instances', 'instances_public_ip_addresses',
87
+ 'instances_private_ip_addresses', 'resources', 'template'].each do |t|
88
+ it "should test #{t}"
89
+ end
90
+
91
+ end
92
+
93
+ end
data/stackster.gemspec CHANGED
@@ -22,5 +22,6 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency "rspec"
23
23
 
24
24
  s.add_runtime_dependency "fog"
25
+ s.add_runtime_dependency 'logger'
25
26
  s.add_runtime_dependency "trollop"
26
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-22 00:00:00.000000000 Z
12
+ date: 2012-06-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70157233988800 !ruby/object:Gem::Requirement
16
+ requirement: &70115631981100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70157233988800
24
+ version_requirements: *70115631981100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: fog
27
- requirement: &70157233987100 !ruby/object:Gem::Requirement
27
+ requirement: &70115631980080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,21 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70157233987100
35
+ version_requirements: *70115631980080
36
+ - !ruby/object:Gem::Dependency
37
+ name: logger
38
+ requirement: &70115631979060 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70115631979060
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: trollop
38
- requirement: &70157233986600 !ruby/object:Gem::Requirement
49
+ requirement: &70115631978360 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
@@ -43,7 +54,7 @@ dependencies:
43
54
  version: '0'
44
55
  type: :runtime
45
56
  prerelease: false
46
- version_requirements: *70157233986600
57
+ version_requirements: *70115631978360
47
58
  description: Thats what I do
48
59
  email:
49
60
  - brett@weav.net
@@ -69,6 +80,7 @@ files:
69
80
  - lib/stackster/entry/entry_lister.rb
70
81
  - lib/stackster/instance.rb
71
82
  - lib/stackster/instance/instance_reader.rb
83
+ - lib/stackster/logger.rb
72
84
  - lib/stackster/stack.rb
73
85
  - lib/stackster/stack/stack_creater.rb
74
86
  - lib/stackster/stack/stack_destroyer.rb
@@ -78,8 +90,22 @@ files:
78
90
  - lib/stackster/stack/stack_updater.rb
79
91
  - lib/stackster/version.rb
80
92
  - script/ci_setup
93
+ - spec/aws/cloud_formation_spec.rb
94
+ - spec/aws/ec2_spec.rb
95
+ - spec/aws/simpledb_spec.rb
81
96
  - spec/config_spec.rb
97
+ - spec/entry/entry_lister_spec.rb
98
+ - spec/entry_spec.rb
99
+ - spec/instance/instance_reader_spec.rb
100
+ - spec/logger_spec.rb
82
101
  - spec/spec_helper.rb
102
+ - spec/stack/stack_creater_spec.rb
103
+ - spec/stack/stack_destroyer_spec.rb
104
+ - spec/stack/stack_formater_spec.rb
105
+ - spec/stack/stack_lister_spec.rb
106
+ - spec/stack/stack_reader_spec.rb
107
+ - spec/stack/stack_updater_spec.rb
108
+ - spec/stack_spec.rb
83
109
  - stackster.gemspec
84
110
  homepage: ''
85
111
  licenses: []
@@ -95,7 +121,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
121
  version: '0'
96
122
  segments:
97
123
  - 0
98
- hash: -1350487739952735544
124
+ hash: -3503830738136148484
99
125
  required_rubygems_version: !ruby/object:Gem::Requirement
100
126
  none: false
101
127
  requirements:
@@ -104,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
130
  version: '0'
105
131
  segments:
106
132
  - 0
107
- hash: -1350487739952735544
133
+ hash: -3503830738136148484
108
134
  requirements: []
109
135
  rubyforge_project: stackster
110
136
  rubygems_version: 1.8.16
@@ -112,5 +138,19 @@ signing_key:
112
138
  specification_version: 3
113
139
  summary: I make deployments easier
114
140
  test_files:
141
+ - spec/aws/cloud_formation_spec.rb
142
+ - spec/aws/ec2_spec.rb
143
+ - spec/aws/simpledb_spec.rb
115
144
  - spec/config_spec.rb
145
+ - spec/entry/entry_lister_spec.rb
146
+ - spec/entry_spec.rb
147
+ - spec/instance/instance_reader_spec.rb
148
+ - spec/logger_spec.rb
116
149
  - spec/spec_helper.rb
150
+ - spec/stack/stack_creater_spec.rb
151
+ - spec/stack/stack_destroyer_spec.rb
152
+ - spec/stack/stack_formater_spec.rb
153
+ - spec/stack/stack_lister_spec.rb
154
+ - spec/stack/stack_reader_spec.rb
155
+ - spec/stack/stack_updater_spec.rb
156
+ - spec/stack_spec.rb