stack_master 0.15.0 → 0.16.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 44ae5dfa519051496de9c2707df2441f8a0a9115
4
- data.tar.gz: 8f2065b8c59a2a2399f0398a0dc6a8541d864287
3
+ metadata.gz: d5d29ac1458d3d02d58c32fb317667305d2a6ca0
4
+ data.tar.gz: 093812e92afeca3f64aa0db34999222c85161c7b
5
5
  SHA512:
6
- metadata.gz: 50f3472de66c642581c80398f5430c9fcb596f5b973582834a65bfbca589265be7e2951a584e2459f42f2aa9fcdf1fd2c9fd9bfd07dca1397e8007d67641c6f4
7
- data.tar.gz: 2a2e5221add3e4aa4c3910de36f73d26eb551d63b3272526e5329124d386feccc8566bcbe593a4e49833e8996c7835ab1d577b4e72d70326176fbc5242f45c1e
6
+ metadata.gz: a2695db0d497f24a78f583b14377ff3f88869d055262696eca00c61d2ed111faa83801aaf1c5d9499e412c6c51c1c18ae2621188dd88180c361b27a07176f78c
7
+ data.tar.gz: 53940ce6b011d5adbf44bf4c7f3ab0b9467101989eea7d23412198eac63caeb474ca1e95bc606b577588f70d114637f8d08749897494749b02a9468ecc159c28
@@ -23,6 +23,7 @@ module StackMaster
23
23
  :describe_stack_resources,
24
24
  :get_template,
25
25
  :get_stack_policy,
26
+ :set_stack_policy,
26
27
  :describe_stack_events,
27
28
  :update_stack,
28
29
  :create_stack,
@@ -41,7 +41,7 @@ module StackMaster
41
41
  c.summary = 'Creates or updates a stack'
42
42
  c.description = "Creates or updates a stack. Shows a diff of the proposed stack's template and parameters. Tails stack events until CloudFormation has completed."
43
43
  c.example 'update a stack named myapp-vpc in us-east-1', 'stack_master apply us-east-1 myapp-vpc'
44
- c.option '--on-failure ACTION', String, 'Action to take on CREATE_FAILURE. Valid Values: [ DO_NOTHING | ROLLBACK | DELETE ]. Default: ROLLBACK'
44
+ c.option '--on-failure ACTION', String, "Action to take on CREATE_FAILURE. Valid Values: [ DO_NOTHING | ROLLBACK | DELETE ]. Default: ROLLBACK\nNote: You cannot use this option with Serverless Application Model (SAM) templates."
45
45
  c.action do |args, options|
46
46
  options.defaults config: default_config_file
47
47
  execute_stacks_command(StackMaster::Commands::Apply, args, options)
@@ -12,7 +12,7 @@ module StackMaster
12
12
  @stack_definition = stack_definition
13
13
  @from_time = Time.now
14
14
  @options = options
15
- @options.on_failure ||= "ROLLBACK"
15
+ @options.on_failure ||= nil
16
16
  end
17
17
 
18
18
  def perform
@@ -21,6 +21,7 @@ module StackMaster
21
21
  ensure_valid_template_body_size!
22
22
  create_or_update_stack
23
23
  tail_stack_events
24
+ set_stack_policy
24
25
  end
25
26
 
26
27
  private
@@ -62,12 +63,32 @@ module StackMaster
62
63
  end
63
64
 
64
65
  def create_stack
66
+ upload_files
67
+ if use_change_set?
68
+ create_stack_by_change_set
69
+ else
70
+ create_stack_directly
71
+ end
72
+ end
73
+
74
+ def use_change_set?
75
+ @options.on_failure.nil?
76
+ end
77
+
78
+ def create_stack_by_change_set
79
+ @change_set = ChangeSet.create(stack_options.merge(change_set_type: 'CREATE'))
80
+ halt!(@change_set.status_reason) if @change_set.failed?
81
+ @change_set.display(StackMaster.stdout)
65
82
  unless ask?('Create stack (y/n)? ')
66
- failed!("Stack creation aborted")
83
+ cf.delete_stack(stack_name: stack_name)
84
+ halt!('Stack creation aborted')
67
85
  end
68
- upload_files
69
- on_failure = @config.stack_defaults['on_failure'] || @options.on_failure
70
- cf.create_stack(stack_options.merge({tags: proposed_stack.aws_tags, on_failure: on_failure}))
86
+ execute_change_set
87
+ end
88
+
89
+ def create_stack_directly
90
+ failed!('Stack creation aborted') unless ask?('Create stack (y/n)? ')
91
+ cf.create_stack(stack_options.merge(on_failure: @options.on_failure))
71
92
  end
72
93
 
73
94
  def ask_to_cancel_stack_update
@@ -121,10 +142,10 @@ module StackMaster
121
142
  {
122
143
  stack_name: stack_name,
123
144
  parameters: proposed_stack.aws_parameters,
145
+ tags: proposed_stack.aws_tags,
124
146
  capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
125
147
  role_arn: proposed_stack.role_arn,
126
148
  notification_arns: proposed_stack.notification_arns,
127
- stack_policy_body: proposed_stack.stack_policy_body,
128
149
  template_method => template_value
129
150
  }
130
151
  end
@@ -166,6 +187,19 @@ module StackMaster
166
187
  end
167
188
  end
168
189
 
190
+ def set_stack_policy
191
+ current_policy = stack && stack.stack_policy_body
192
+ proposed_policy = proposed_stack.stack_policy_body
193
+ # No need to reset a stack policy if it's nil or not changed
194
+ return if proposed_policy.nil? || proposed_policy == current_policy
195
+ StackMaster.stdout.print 'Setting a stack policy...'
196
+ cf.set_stack_policy(
197
+ stack_name: stack_name,
198
+ stack_policy_body: proposed_policy
199
+ )
200
+ StackMaster.stdout.puts 'done.'
201
+ end
202
+
169
203
  extend Forwardable
170
204
  def_delegators :@stack_definition, :stack_name, :region
171
205
  end
@@ -41,7 +41,7 @@ module StackMaster
41
41
  params_hash[param_struct.parameter_key] = param_struct.parameter_value
42
42
  params_hash
43
43
  end
44
- template_body ||= cf.get_template(stack_name: stack_name).template_body
44
+ template_body ||= cf.get_template(stack_name: stack_name, template_stage: 'Original').template_body
45
45
  template_format = TemplateUtils.identify_template_format(template_body)
46
46
  stack_policy_body ||= cf.get_stack_policy(stack_name: stack_name).stack_policy_body
47
47
  outputs = cf_stack.outputs
@@ -87,6 +87,8 @@ module StackMaster
87
87
  options.merge!(change_set_id: id)
88
88
  @change_sets[id] = options
89
89
  @change_sets[options.fetch(:change_set_name)] = options
90
+ stack_name = options.fetch(:stack_name)
91
+ add_stack(stack_name: stack_name, stack_status: 'REVIEW_IN_PROGRESS') unless @stacks[stack_name]
90
92
  OpenStruct.new(id: id)
91
93
  end
92
94
 
@@ -106,7 +108,7 @@ module StackMaster
106
108
  def execute_change_set(options)
107
109
  change_set_id = options.fetch(:change_set_name)
108
110
  change_set = @change_sets.fetch(change_set_id)
109
- update_stack(change_set)
111
+ @stacks[change_set.fetch(:stack_name)].attributes = change_set
110
112
  end
111
113
 
112
114
  def delete_change_set(options)
@@ -142,17 +144,15 @@ module StackMaster
142
144
  OpenStruct.new(stack_policy_body: @stack_policies[options.fetch(:stack_name)])
143
145
  end
144
146
 
147
+ def set_stack_policy(options)
148
+ @stack_policies[options.fetch(:stack_name)] = options[:stack_policy_body]
149
+ end
150
+
145
151
  def describe_stack_events(options)
146
152
  events = @stack_events[options.fetch(:stack_name)] || []
147
153
  OpenStruct.new(stack_events: events, next_token: nil)
148
154
  end
149
155
 
150
- def update_stack(options)
151
- stack_name = options.fetch(:stack_name)
152
- @stacks[stack_name].attributes = options
153
- @stack_policies[stack_name] = options[:stack_policy_body]
154
- end
155
-
156
156
  def create_stack(options)
157
157
  stack_name = options.fetch(:stack_name)
158
158
  add_stack(options)
@@ -1,3 +1,3 @@
1
1
  module StackMaster
2
- VERSION = "0.15.0"
2
+ VERSION = "0.16.0"
3
3
  end
@@ -12,6 +12,7 @@ RSpec.describe StackMaster::Commands::Apply do
12
12
  let(:parameters) { { 'param_1' => 'hello' } }
13
13
  let(:proposed_stack) { StackMaster::Stack.new(template_body: template_body, template_format: template_format, tags: { 'environment' => 'production' } , parameters: parameters, role_arn: role_arn, notification_arns: [notification_arn], stack_policy_body: stack_policy_body ) }
14
14
  let(:stack_policy_body) { '{}' }
15
+ let(:change_set) { double(display: true, failed?: false, id: '1') }
15
16
 
16
17
  before do
17
18
  allow(StackMaster::Stack).to receive(:find).with(region, stack_name).and_return(stack)
@@ -23,6 +24,10 @@ RSpec.describe StackMaster::Commands::Apply do
23
24
  allow(StackMaster::StackDiffer).to receive(:new).with(proposed_stack, stack).and_return double.as_null_object
24
25
  allow(StackMaster::StackEvents::Streamer).to receive(:stream)
25
26
  allow(StackMaster).to receive(:interactive?).and_return(false)
27
+ allow(cf).to receive(:create_change_set).and_return(OpenStruct.new(id: '1'))
28
+ allow(StackMaster::ChangeSet).to receive(:create).and_return(change_set)
29
+ allow(cf).to receive(:execute_change_set).and_return(OpenStruct.new(id: '1'))
30
+ allow(cf).to receive(:set_stack_policy)
26
31
  end
27
32
 
28
33
  def apply
@@ -31,13 +36,6 @@ RSpec.describe StackMaster::Commands::Apply do
31
36
 
32
37
  context 'the stack exist' do
33
38
  let(:stack) { StackMaster::Stack.new(stack_id: '1') }
34
- let(:change_set) { double(display: true, failed?: false, id: 'id-1') }
35
-
36
- before do
37
- allow(cf).to receive(:create_change_set).and_return(OpenStruct.new(id: '1'))
38
- allow(StackMaster::ChangeSet).to receive(:create).and_return(change_set)
39
- allow(cf).to receive(:execute_change_set).and_return(OpenStruct.new(id: '1'))
40
- end
41
39
 
42
40
  it 'creates a change set' do
43
41
  apply
@@ -47,10 +45,12 @@ RSpec.describe StackMaster::Commands::Apply do
47
45
  parameters: [
48
46
  { parameter_key: 'param_1', parameter_value: 'hello' }
49
47
  ],
48
+ tags: [
49
+ { key: 'environment', value: 'production' }
50
+ ],
50
51
  capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
51
52
  role_arn: role_arn,
52
- notification_arns: [notification_arn],
53
- stack_policy_body: stack_policy_body
53
+ notification_arns: [notification_arn]
54
54
  )
55
55
  end
56
56
 
@@ -61,6 +61,23 @@ RSpec.describe StackMaster::Commands::Apply do
61
61
  end
62
62
  end
63
63
 
64
+ it 'attaches a stack policy to the stack' do
65
+ apply
66
+ expect(cf).to have_received(:set_stack_policy).with(
67
+ stack_name: stack_name,
68
+ stack_policy_body: stack_policy_body
69
+ )
70
+ end
71
+
72
+ context 'stack policy is not changed' do
73
+ let(:stack) { StackMaster::Stack.new(stack_id: '1', stack_policy_body: stack_policy_body) }
74
+
75
+ it 'does not set a stack policy' do
76
+ apply
77
+ expect(cf).to_not have_received(:set_stack_policy)
78
+ end
79
+ end
80
+
64
81
  context 'when using s3' do
65
82
  before do
66
83
  stack_definition.s3 = {
@@ -122,24 +139,52 @@ RSpec.describe StackMaster::Commands::Apply do
122
139
  context 'the stack does not exist' do
123
140
  let(:stack) { nil }
124
141
 
125
- it 'calls the create stack API method' do
142
+ it 'creates a change set for a new stack' do
126
143
  apply
127
- expect(cf).to have_received(:create_stack).with(
144
+ expect(StackMaster::ChangeSet).to have_received(:create).with(
128
145
  stack_name: stack_name,
129
146
  template_body: proposed_stack.template_body,
130
147
  parameters: [
131
148
  { parameter_key: 'param_1', parameter_value: 'hello' }
132
149
  ],
133
150
  tags: [
134
- {
135
- key: 'environment',
136
- value: 'production'
137
- }],
151
+ { key: 'environment', value: 'production' }
152
+ ],
138
153
  capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
139
154
  role_arn: role_arn,
140
155
  notification_arns: [notification_arn],
141
- stack_policy_body: stack_policy_body,
142
- on_failure: 'ROLLBACK'
156
+ change_set_type: 'CREATE'
157
+ )
158
+ end
159
+
160
+ context 'on_failure option is set' do
161
+ it 'calls the create stack API method' do
162
+ options = Commander::Command::Options.new
163
+ options.on_failure = 'ROLLBACK'
164
+ StackMaster::Commands::Apply.perform(config, stack_definition, options)
165
+ apply
166
+ expect(cf).to have_received(:create_stack).with(
167
+ stack_name: stack_name,
168
+ template_body: proposed_stack.template_body,
169
+ parameters: [
170
+ { parameter_key: 'param_1', parameter_value: 'hello' }
171
+ ],
172
+ tags: [
173
+ { key: 'environment', value: 'production' }
174
+ ],
175
+ capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
176
+ role_arn: role_arn,
177
+ notification_arns: [notification_arn],
178
+ on_failure: 'ROLLBACK'
179
+ )
180
+ end
181
+ end
182
+
183
+ it 'attaches a stack policy to the created stack' do
184
+ apply
185
+ expect(cf).to have_received(:set_stack_policy).with(
186
+ stack_name: stack_name,
187
+ stack_policy_body: stack_policy_body
143
188
  )
144
189
  end
145
190
 
@@ -161,21 +206,21 @@ RSpec.describe StackMaster::Commands::Apply do
161
206
  end
162
207
  end
163
208
 
164
- it 'on_failure can be set to a custom value' do
165
- config.stack_defaults['on_failure'] = 'DELETE'
166
- apply
167
- expect(cf).to have_received(:create_stack).with(
168
- hash_including(on_failure: 'DELETE')
169
- )
170
- end
209
+ context 'user decides to not create a stack' do
210
+ before do
211
+ allow(StackMaster).to receive(:non_interactive_answer).and_return('n')
212
+ allow(cf).to receive(:delete_stack)
213
+ allow(StackMaster::ChangeSet).to receive(:execute)
214
+ apply
215
+ end
171
216
 
172
- it 'on_failure can be passed in options' do
173
- options = Commander::Command::Options.new
174
- options.on_failure = 'DELETE'
175
- StackMaster::Commands::Apply.perform(config, stack_definition, options)
176
- expect(cf).to have_received(:create_stack).with(
177
- hash_including(on_failure: 'DELETE')
178
- )
217
+ it 'deletes the stack' do
218
+ expect(cf).to have_received(:delete_stack).with(stack_name: stack_name)
219
+ end
220
+
221
+ it "doesn't execute the change set" do
222
+ expect(StackMaster::ChangeSet).to_not have_received(:execute).with(change_set.id)
223
+ end
179
224
  end
180
225
  end
181
226
 
@@ -8,7 +8,6 @@ RSpec.describe StackMaster::TestDriver::CloudFormation do
8
8
  test_cf_driver.create_stack(stack_id: "1", stack_name: 'stack-1')
9
9
  test_cf_driver.create_stack(stack_id: "2", stack_name: 'stack-2')
10
10
  expect(test_cf_driver.describe_stacks.stacks.map(&:stack_id)).to eq(["1", "2"])
11
-
12
11
  end
13
12
 
14
13
  it 'adds and gets stack events' do
@@ -22,6 +21,12 @@ RSpec.describe StackMaster::TestDriver::CloudFormation do
22
21
  test_cf_driver.set_template('stack-1', 'blah')
23
22
  expect(test_cf_driver.get_template(stack_name: 'stack-1').template_body).to eq 'blah'
24
23
  end
24
+
25
+ it 'sets and gets stack policies' do
26
+ stack_policy_body = '{}'
27
+ test_cf_driver.set_stack_policy(stack_name: 'stack-1', stack_policy_body: stack_policy_body)
28
+ expect(test_cf_driver.get_stack_policy(stack_name: 'stack-1').stack_policy_body).to eq(stack_policy_body)
29
+ end
25
30
  end
26
31
 
27
32
  context 'change sets' do
@@ -39,6 +44,14 @@ RSpec.describe StackMaster::TestDriver::CloudFormation do
39
44
  expect(change_set.change_set_id).to eq change_set_id
40
45
  expect(change_set.change_set_name).to eq 'change-set-1'
41
46
  end
47
+
48
+ it 'creates stacks using change sets and describes stacks' do
49
+ change_set1 = test_cf_driver.create_change_set(change_set_name: 'change-set-1', stack_name: 'stack-1', change_set_type: 'CREATE')
50
+ change_set2 = test_cf_driver.create_change_set(change_set_name: 'change-set-2', stack_name: 'stack-2', change_set_type: 'CREATE')
51
+ test_cf_driver.execute_change_set(change_set_name: change_set1.id)
52
+ test_cf_driver.execute_change_set(change_set_name: change_set2.id)
53
+ expect(test_cf_driver.describe_stacks.stacks.map(&:stack_name)).to eq(['stack-1', 'stack-2'])
54
+ end
42
55
  end
43
56
 
44
57
  it 'deletes change sets' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stack_master
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodgkiss
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-04-27 00:00:00.000000000 Z
12
+ date: 2017-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -450,7 +450,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
450
450
  version: '0'
451
451
  requirements: []
452
452
  rubyforge_project:
453
- rubygems_version: 2.6.11
453
+ rubygems_version: 2.6.8
454
454
  signing_key:
455
455
  specification_version: 4
456
456
  summary: StackMaster is a sure-footed way of creating, updating and keeping track