stack_master 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +208 -0
  7. data/Rakefile +11 -0
  8. data/apply_demo.gif +0 -0
  9. data/bin/stack_master +16 -0
  10. data/example/simple/Gemfile +3 -0
  11. data/example/simple/parameters/myapp_vpc.yml +1 -0
  12. data/example/simple/parameters/myapp_web.yml +2 -0
  13. data/example/simple/stack_master.yml +13 -0
  14. data/example/simple/templates/myapp_vpc.rb +39 -0
  15. data/example/simple/templates/myapp_web.rb +16 -0
  16. data/features/apply.feature +241 -0
  17. data/features/delete.feature +43 -0
  18. data/features/diff.feature +191 -0
  19. data/features/events.feature +38 -0
  20. data/features/init.feature +6 -0
  21. data/features/outputs.feature +49 -0
  22. data/features/region_aliases.feature +66 -0
  23. data/features/resources.feature +45 -0
  24. data/features/stack_defaults.feature +88 -0
  25. data/features/status.feature +124 -0
  26. data/features/step_definitions/stack_steps.rb +50 -0
  27. data/features/support/env.rb +14 -0
  28. data/lib/stack_master.rb +81 -0
  29. data/lib/stack_master/aws_driver/cloud_formation.rb +56 -0
  30. data/lib/stack_master/cli.rb +164 -0
  31. data/lib/stack_master/command.rb +13 -0
  32. data/lib/stack_master/commands/apply.rb +104 -0
  33. data/lib/stack_master/commands/delete.rb +53 -0
  34. data/lib/stack_master/commands/diff.rb +31 -0
  35. data/lib/stack_master/commands/events.rb +39 -0
  36. data/lib/stack_master/commands/init.rb +109 -0
  37. data/lib/stack_master/commands/list_stacks.rb +16 -0
  38. data/lib/stack_master/commands/outputs.rb +27 -0
  39. data/lib/stack_master/commands/resources.rb +33 -0
  40. data/lib/stack_master/commands/status.rb +47 -0
  41. data/lib/stack_master/commands/validate.rb +17 -0
  42. data/lib/stack_master/config.rb +86 -0
  43. data/lib/stack_master/ctrl_c.rb +4 -0
  44. data/lib/stack_master/parameter_loader.rb +17 -0
  45. data/lib/stack_master/parameter_resolver.rb +45 -0
  46. data/lib/stack_master/parameter_resolvers/secret.rb +42 -0
  47. data/lib/stack_master/parameter_resolvers/security_group.rb +20 -0
  48. data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +29 -0
  49. data/lib/stack_master/parameter_resolvers/stack_output.rb +53 -0
  50. data/lib/stack_master/prompter.rb +14 -0
  51. data/lib/stack_master/security_group_finder.rb +29 -0
  52. data/lib/stack_master/sns_topic_finder.rb +27 -0
  53. data/lib/stack_master/stack.rb +96 -0
  54. data/lib/stack_master/stack_definition.rb +49 -0
  55. data/lib/stack_master/stack_differ.rb +80 -0
  56. data/lib/stack_master/stack_events/fetcher.rb +45 -0
  57. data/lib/stack_master/stack_events/presenter.rb +27 -0
  58. data/lib/stack_master/stack_events/streamer.rb +55 -0
  59. data/lib/stack_master/stack_states.rb +34 -0
  60. data/lib/stack_master/template_compiler.rb +21 -0
  61. data/lib/stack_master/test_driver/cloud_formation.rb +139 -0
  62. data/lib/stack_master/testing.rb +7 -0
  63. data/lib/stack_master/utils.rb +31 -0
  64. data/lib/stack_master/validator.rb +25 -0
  65. data/lib/stack_master/version.rb +3 -0
  66. data/logo.png +0 -0
  67. data/script/buildkite/bundle.sh +5 -0
  68. data/script/buildkite/clean.sh +3 -0
  69. data/script/buildkite_rspec.sh +27 -0
  70. data/spec/fixtures/parameters/myapp_vpc.yml +1 -0
  71. data/spec/fixtures/stack_master.yml +35 -0
  72. data/spec/fixtures/templates/myapp_vpc.json +1 -0
  73. data/spec/spec_helper.rb +99 -0
  74. data/spec/stack_master/commands/apply_spec.rb +92 -0
  75. data/spec/stack_master/commands/delete_spec.rb +40 -0
  76. data/spec/stack_master/commands/init_spec.rb +17 -0
  77. data/spec/stack_master/commands/status_spec.rb +38 -0
  78. data/spec/stack_master/commands/validate_spec.rb +26 -0
  79. data/spec/stack_master/config_spec.rb +81 -0
  80. data/spec/stack_master/parameter_loader_spec.rb +81 -0
  81. data/spec/stack_master/parameter_resolver_spec.rb +58 -0
  82. data/spec/stack_master/parameter_resolvers/secret_spec.rb +66 -0
  83. data/spec/stack_master/parameter_resolvers/security_group_spec.rb +17 -0
  84. data/spec/stack_master/parameter_resolvers/sns_topic_name_spec.rb +43 -0
  85. data/spec/stack_master/parameter_resolvers/stack_output_spec.rb +77 -0
  86. data/spec/stack_master/security_group_finder_spec.rb +49 -0
  87. data/spec/stack_master/sns_topic_finder_spec.rb +25 -0
  88. data/spec/stack_master/stack_definition_spec.rb +37 -0
  89. data/spec/stack_master/stack_differ_spec.rb +34 -0
  90. data/spec/stack_master/stack_events/fetcher_spec.rb +65 -0
  91. data/spec/stack_master/stack_events/presenter_spec.rb +18 -0
  92. data/spec/stack_master/stack_events/streamer_spec.rb +33 -0
  93. data/spec/stack_master/stack_spec.rb +157 -0
  94. data/spec/stack_master/template_compiler_spec.rb +48 -0
  95. data/spec/stack_master/test_driver/cloud_formation_spec.rb +24 -0
  96. data/spec/stack_master/utils_spec.rb +30 -0
  97. data/spec/stack_master/validator_spec.rb +38 -0
  98. data/stack_master.gemspec +38 -0
  99. data/stacktemplates/parameter_region.yml +3 -0
  100. data/stacktemplates/parameter_stack_name.yml +3 -0
  101. data/stacktemplates/stack.json.erb +20 -0
  102. data/stacktemplates/stack_master.yml.erb +6 -0
  103. metadata +427 -0
@@ -0,0 +1,7 @@
1
+ require 'stack_master'
2
+
3
+ Aws.config[:stub_responses] = true
4
+
5
+ require 'stack_master/test_driver/cloud_formation'
6
+
7
+ StackMaster.cloud_formation_driver = StackMaster::TestDriver::CloudFormation.new
@@ -0,0 +1,31 @@
1
+ module StackMaster
2
+ module Utils
3
+ extend self
4
+
5
+ def hash_to_aws_parameters(params)
6
+ params.inject([]) do |params, (key, value)|
7
+ params << { parameter_key: key, parameter_value: value }
8
+ params
9
+ end
10
+ end
11
+
12
+ def hash_to_aws_tags(tags)
13
+ return [] if tags.nil?
14
+ tags.inject([]) do |aws_tags, (key, value)|
15
+ aws_tags << { key: key, value: value }
16
+ aws_tags
17
+ end
18
+ end
19
+
20
+ def underscore_to_hyphen(string)
21
+ string.to_s.gsub('_', '-')
22
+ end
23
+
24
+ def underscore_keys_to_hyphen(hash)
25
+ hash.inject({}) do |hash, (key, value)|
26
+ hash[underscore_to_hyphen(key)] = value
27
+ hash
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ module StackMaster
2
+ class Validator
3
+ include Command
4
+
5
+ def initialize(stack_definition)
6
+ @stack_definition = stack_definition
7
+ end
8
+
9
+ def perform
10
+ template_body = TemplateCompiler.compile(@stack_definition.template_file_path)
11
+ cf.validate_template(template_body: template_body)
12
+ StackMaster.stdout.puts "Valid"
13
+ rescue Aws::CloudFormation::Errors::ValidationError => e
14
+ $stderr.puts "Validation Failed"
15
+ $stderr.puts e.message
16
+ end
17
+
18
+ private
19
+
20
+ def cf
21
+ @cf ||= StackMaster.cloud_formation_driver
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module StackMaster
2
+ VERSION = "0.0.1"
3
+ end
Binary file
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+
3
+ set -e -x
4
+
5
+ bundle check || bundle --binstubs bin --path .bundle --without darwin,debugger,development
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ git clean -fd
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ echo
6
+ echo "--- Cleaning up working tree"
7
+ echo
8
+
9
+ time ./script/buildkite/clean.sh
10
+
11
+ echo
12
+ echo "--- Bundling"
13
+ echo
14
+
15
+ time ./script/buildkite/bundle.sh
16
+
17
+ echo
18
+ echo "+++ Running rspec"
19
+ echo
20
+
21
+ time bundle exec rspec
22
+
23
+ echo
24
+ echo "+++ Running cucumber"
25
+ echo
26
+
27
+ time bundle exec cucumber
@@ -0,0 +1 @@
1
+ param_1: 'hello'
@@ -0,0 +1,35 @@
1
+ region_aliases:
2
+ production: us-east-1
3
+ staging: ap-southeast-2
4
+ stack_defaults:
5
+ tags:
6
+ application: my-awesome-blog
7
+ region_defaults:
8
+ us_east_1:
9
+ tags:
10
+ environment: production
11
+ notification_arns:
12
+ - test_arn
13
+ secret_file: production.yml.gpg
14
+ stack_policy_file: my_policy.json
15
+ staging:
16
+ tags:
17
+ environment: staging
18
+ notification_arns:
19
+ - test_arn_3
20
+ secret_file: staging.yml.gpg
21
+ stacks:
22
+ us-east-1:
23
+ myapp_vpc:
24
+ template: myapp_vpc.json
25
+ notification_arns:
26
+ - test_arn_2
27
+ myapp_web:
28
+ template: myapp_web.rb
29
+ ap-southeast-2:
30
+ myapp_vpc:
31
+ template: myapp_vpc.rb
32
+ notification_arns:
33
+ - test_arn_4
34
+ myapp_web:
35
+ template: myapp_web
@@ -0,0 +1,99 @@
1
+ require 'stack_master'
2
+ require 'timecop'
3
+ require 'pry'
4
+ Aws.config[:stub_responses] = true
5
+ # This file was generated by the `rspec --init` command. Conventionally, all
6
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
7
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
8
+ # this file to always be loaded, without a need to explicitly require it in any
9
+ # files.
10
+ #
11
+ # Given that it is always loaded, you are encouraged to keep this file as
12
+ # light-weight as possible. Requiring heavyweight dependencies from this file
13
+ # will add to the boot time of your test suite on EVERY test run, even for an
14
+ # individual file that may not need all of that loaded. Instead, consider making
15
+ # a separate helper file that requires the additional dependencies and performs
16
+ # the additional setup, and require it from the spec files that actually need
17
+ # it.
18
+ #
19
+ # The `.rspec` file also contains a few flags that are not defaults but that
20
+ # users commonly want.
21
+ #
22
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
23
+ RSpec.configure do |config|
24
+ config.before do
25
+ StackMaster.cloud_formation_driver = nil
26
+ end
27
+ # rspec-expectations config goes here. You can use an alternate
28
+ # assertion/expectation library such as wrong or the stdlib/minitest
29
+ # assertions if you prefer.
30
+ config.expect_with :rspec do |expectations|
31
+ # This option will default to `true` in RSpec 4. It makes the `description`
32
+ # and `failure_message` of custom matchers include text for helper methods
33
+ # defined using `chain`, e.g.:
34
+ # be_bigger_than(2).and_smaller_than(4).description
35
+ # # => "be bigger than 2 and smaller than 4"
36
+ # ...rather than:
37
+ # # => "be bigger than 2"
38
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
39
+ end
40
+
41
+ # rspec-mocks config goes here. You can use an alternate test double
42
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
43
+ config.mock_with :rspec do |mocks|
44
+ # Prevents you from mocking or stubbing a method that does not exist on
45
+ # a real object. This is generally recommended, and will default to
46
+ # `true` in RSpec 4.
47
+ mocks.verify_partial_doubles = true
48
+ end
49
+
50
+ # These two settings work together to allow you to limit a spec run
51
+ # to individual examples or groups you care about by tagging them with
52
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
53
+ # get run.
54
+ config.filter_run :focus
55
+ config.run_all_when_everything_filtered = true
56
+
57
+ # Allows RSpec to persist some state between runs in order to support
58
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
59
+ # you configure your source control system to ignore this file.
60
+ config.example_status_persistence_file_path = "spec/examples.txt"
61
+
62
+ # Limits the available syntax to the non-monkey patched syntax that is
63
+ # recommended. For more details, see:
64
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
65
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
67
+ config.disable_monkey_patching!
68
+
69
+ # This setting enables warnings. It's recommended, but in some cases may
70
+ # be too noisy due to issues in dependencies.
71
+ # config.warnings = true
72
+
73
+ # Many RSpec users commonly either run the entire suite or an individual
74
+ # file, and it's useful to allow more verbose output when running an
75
+ # individual spec file.
76
+ if config.files_to_run.one?
77
+ # Use the documentation formatter for detailed output,
78
+ # unless a formatter has already been configured
79
+ # (e.g. via a command-line flag).
80
+ config.default_formatter = 'doc'
81
+ end
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ config.profile_examples = 10
87
+
88
+ # Run specs in random order to surface order dependencies. If you find an
89
+ # order dependency and want to debug it, you can fix the order by providing
90
+ # the seed, which is printed after each run.
91
+ # --seed 1234
92
+ config.order = :random
93
+
94
+ # Seed global randomization in this process using the `--seed` CLI option.
95
+ # Setting this allows you to use `--seed` to deterministically reproduce
96
+ # test failures related to randomization by passing the same `--seed` value
97
+ # as the one that triggered the failure.
98
+ Kernel.srand config.seed
99
+ end
@@ -0,0 +1,92 @@
1
+ RSpec.describe StackMaster::Commands::Apply do
2
+ let(:cf) { instance_double(Aws::CloudFormation::Client) }
3
+ let(:region) { 'us-east-1' }
4
+ let(:stack_name) { 'myapp-vpc' }
5
+ let(:config) { double(find_stack: stack_definition) }
6
+ let(:notification_arn) { 'test_arn' }
7
+ let(:stack_definition) { StackMaster::StackDefinition.new(base_dir: '/base_dir', region: region, stack_name: stack_name) }
8
+ let(:template_body) { '{}' }
9
+ let(:proposed_stack) { StackMaster::Stack.new(template_body: template_body, tags: { 'environment' => 'production' } , parameters: { 'param_1' => 'hello' }, notification_arns: [notification_arn], stack_policy_body: stack_policy_body ) }
10
+ let(:stack_policy_body) { '{}' }
11
+
12
+ before do
13
+ allow(StackMaster::Stack).to receive(:find).with(region, stack_name).and_return(stack)
14
+ allow(StackMaster::Stack).to receive(:generate).with(stack_definition, config).and_return(proposed_stack)
15
+ allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf)
16
+ allow(cf).to receive(:update_stack)
17
+ allow(cf).to receive(:create_stack)
18
+ allow(StackMaster::StackDiffer).to receive(:new).with(proposed_stack, stack).and_return double.as_null_object
19
+ allow(STDOUT).to receive(:print)
20
+ allow(STDIN).to receive(:getch).and_return('y')
21
+ allow(StackMaster::StackEvents::Streamer).to receive(:stream)
22
+ end
23
+
24
+ def apply
25
+ StackMaster::Commands::Apply.perform(config, stack_definition)
26
+ end
27
+
28
+ context 'the stack exist' do
29
+ let(:stack) { StackMaster::Stack.new(stack_id: '1') }
30
+
31
+ it 'calls the update stack API method' do
32
+ apply
33
+ expect(cf).to have_received(:update_stack).with(
34
+ stack_name: stack_name,
35
+ template_body: proposed_stack.template_body,
36
+ parameters: [
37
+ { parameter_key: 'param_1', parameter_value: 'hello' }
38
+ ],
39
+ capabilities: ['CAPABILITY_IAM'],
40
+ notification_arns: [notification_arn],
41
+ stack_policy_body: stack_policy_body
42
+ )
43
+ end
44
+
45
+ it 'streams events' do
46
+ Timecop.freeze(Time.local(1990)) do
47
+ apply
48
+ expect(StackMaster::StackEvents::Streamer).to have_received(:stream).with(stack_name, region, io: STDOUT, from: Time.now)
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'the stack does not exist' do
54
+ let(:stack) { nil }
55
+
56
+ it 'calls the create stack API method' do
57
+ apply
58
+ expect(cf).to have_received(:create_stack).with(
59
+ stack_name: stack_name,
60
+ template_body: proposed_stack.template_body,
61
+ parameters: [
62
+ { parameter_key: 'param_1', parameter_value: 'hello' }
63
+ ],
64
+ tags: [
65
+ {
66
+ key: 'environment',
67
+ value: 'production'
68
+ }],
69
+ capabilities: ['CAPABILITY_IAM'],
70
+ notification_arns: [notification_arn],
71
+ stack_policy_body: stack_policy_body
72
+ )
73
+ end
74
+
75
+ context 'the stack is too large' do
76
+ let(:big_string) { 'x' * 60000 }
77
+ let(:template_body) do
78
+ "{\"a\":\"#{big_string}\"}"
79
+ end
80
+ it 'exits with a message' do
81
+ expect { apply }.to output(/The \(space compressed\) stack is larger than the limit set by AWS/).to_stdout
82
+ end
83
+ end
84
+
85
+ it 'streams events' do
86
+ Timecop.freeze(Time.local(1990)) do
87
+ apply
88
+ expect(StackMaster::StackEvents::Streamer).to have_received(:stream).with(stack_name, region, io: STDOUT, from: Time.now)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,40 @@
1
+ RSpec.describe StackMaster::Commands::Delete do
2
+
3
+ subject(:delete) { described_class.new(stack_name, region) }
4
+ let(:cf) { Aws::CloudFormation::Client.new }
5
+ let(:region) { 'us-east-1' }
6
+ let(:stack_name) { 'mystack' }
7
+
8
+ before do
9
+ StackMaster.cloud_formation_driver.set_region(region)
10
+ allow(Aws::CloudFormation::Client).to receive(:new).with(region: region).and_return(cf)
11
+ allow(delete).to receive(:ask?).and_return('y')
12
+ allow(StackMaster::StackEvents::Streamer).to receive(:stream)
13
+ end
14
+
15
+ describe "#perform" do
16
+ context "The stack exists" do
17
+ before do
18
+ cf.stub_responses(:describe_stacks, stacks: [{ stack_id: "ABC", stack_name: stack_name, creation_time: Time.now, stack_status: 'UPDATE_COMPLETE', parameters: []}])
19
+
20
+ end
21
+ it "deletes the stack and tails the events" do
22
+ expect(cf).to receive(:delete_stack).with({:stack_name => region})
23
+ expect(StackMaster::StackEvents::Streamer).to receive(:stream)
24
+ delete.perform
25
+ end
26
+ end
27
+
28
+ context "The stack does not exist" do
29
+ before do
30
+ cf.stub_responses(:describe_stacks, Aws::CloudFormation::Errors::ValidationError.new("x", "y"))
31
+ end
32
+ it "raises an error" do
33
+ expect(StackMaster::StackEvents::Streamer).to_not receive(:stream)
34
+ expect(cf).to_not receive(:delete_stack)
35
+ delete.perform
36
+ end
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,17 @@
1
+ RSpec.describe StackMaster::Commands::Init do
2
+
3
+ subject(:init_command) { described_class.new(false, region, stack_name) }
4
+ let(:region) { "us-east-1" }
5
+ let(:stack_name) { "test-stack" }
6
+
7
+ describe "#perform" do
8
+ it "creates all the expected files" do
9
+ expect(IO).to receive(:write).with("stack_master.yml", "stacks:\n us-east-1:\n test-stack:\n template: test-stack.json\n tags:\n environment: production\n")
10
+ expect(IO).to receive(:write).with("parameters/test_stack.yml", "# Add parameters here:\n# param1: value1\n# param2: value2\n")
11
+ expect(IO).to receive(:write).with("parameters/us-east-1/test_stack.yml", "# Add parameters here:\n# param1: value1\n# param2: value2\n")
12
+ expect(IO).to receive(:write).with("templates/test-stack.json", "{\n \"AWSTemplateFormatVersion\" : \"2010-09-09\",\n \"Description\" : \"Cloudformation stack for test-stack\",\n\n \"Parameters\" : {\n \"InstanceType\" : {\n \"Description\" : \"EC2 instance type\",\n \"Type\" : \"String\"\n }\n },\n\n \"Mappings\" : {\n },\n\n \"Resources\" : {\n },\n\n \"Outputs\" : {\n }\n}\n")
13
+ init_command.perform()
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,38 @@
1
+ RSpec.describe StackMaster::Commands::Status do
2
+ subject(:status) { described_class.new(config) }
3
+ let(:config) { instance_double(StackMaster::Config, stacks: stacks) }
4
+ let(:stacks) { [stack_definition_1, stack_definition_2] }
5
+ let(:stack_definition_1) { double(:stack_definition_1, region: 'us-east-1', stack_name: 'stack1') }
6
+ let(:stack_definition_2) { double(:stack_definition_2, region: 'us-east-1', stack_name: 'stack2', stack_status: 'CREATE_COMPLETE') }
7
+ let(:cf) { Aws::CloudFormation::Client.new(region: 'us-east-1') }
8
+
9
+ before do
10
+ allow(Aws::CloudFormation::Client).to receive(:new).with(region: 'us-east-1').and_return cf
11
+ allow(StackMaster::Stack).to receive(:find).and_return stack1, stack2
12
+ allow(StackMaster::Stack).to receive(:generate).and_return proposed_stack1, proposed_stack2
13
+ end
14
+
15
+ context "#perform" do
16
+ context "some parameters are different" do
17
+ let(:stack1) { double(:stack1, template_hash: {}, parameters_with_defaults: {a: 1}, stack_status: 'UPDATE_COMPLETE') }
18
+ let(:stack2) { double(:stack2, template_hash: {}, parameters_with_defaults: {a: 2}, stack_status: 'CREATE_COMPLETE') }
19
+ let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
20
+ let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
21
+ it "returns the status of call stacks" do
22
+ out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | No \nus-east-1 | stack2 | CREATE_COMPLETE | Yes \n"
23
+ expect { status.perform }.to output(out).to_stdout
24
+ end
25
+ end
26
+
27
+ context "some templates are different" do
28
+ let(:stack1) { double(:stack1, template_hash: {foo: 'bar'}, parameters_with_defaults: {a: 1}, stack_status: 'UPDATE_COMPLETE') }
29
+ let(:stack2) { double(:stack2, template_hash: {}, parameters_with_defaults: {a: 1}, stack_status: 'CREATE_COMPLETE') }
30
+ let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
31
+ let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
32
+ it "returns the status of call stacks" do
33
+ out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | Yes \nus-east-1 | stack2 | CREATE_COMPLETE | No \n"
34
+ expect { status.perform }.to output(out).to_stdout
35
+ end
36
+ end
37
+ end
38
+ end