stackster 0.1.3 → 0.2.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/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