stackup 0.0.6 → 0.0.7

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: 81fb1c62bb5b074f520f74488f5e030fa6366729
4
- data.tar.gz: ca1c7d4a8117887dacd2b5a12aeaa2184395301e
3
+ metadata.gz: a3d43bfd3b75ab37503b4b44933dcb086ad80351
4
+ data.tar.gz: 6d43253e47b02d61f549ccd6c9064046780ec71f
5
5
  SHA512:
6
- metadata.gz: d3734548eb85b676c9092a6a9ac18e191ed50ed8d255431cffaecd80f475e0ee316c0ce3c77269db5138c4b022bc60c43fb088154129ab96d3876b22069a3b34
7
- data.tar.gz: dfc2b36b8e57da36eadd0f36e4635fec67fa294286d74964141e89f9d6a8b36560a41cb4891777b1fed2f6e619be61d929254e464744c1d3ebd059dc7f847a16
6
+ metadata.gz: 2b6971f3281601543ed3ee5d088ccacc2b7069e5b46acbf5bd8d18c2a911a3db36a6bc532c04eac14660fcccd6538c7d3d8258349eb04e3541013ccb2e002ab8
7
+ data.tar.gz: ecb74e9962901afd46410cdace610dff8b6fbfc75efaec26c346f25f0da47866450cb065c5f75fb7a953cf2d45ed4054710760d7091ff8f723119a484b970693
@@ -4,8 +4,8 @@ module Stackup
4
4
  class Stack
5
5
 
6
6
  attr_reader :stack, :name, :cf, :monitor
7
- SUCESS_STATES = ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
8
- FAILURE_STATES = ["CREATE_FAILED", "DELETE_COMPLETE", "DELETE_FAILED", "UPDATE_ROLLBACK_FAILED", "ROLLBACK_FAILED", "ROLLBACK_COMPLETE", "ROLLBACK_FAILED", "UPDATE_ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_FAILED"]
7
+ SUCESS_STATES = ["CREATE_COMPLETE", "DELETE_COMPLETE", "UPDATE_COMPLETE"]
8
+ FAILURE_STATES = ["CREATE_FAILED", "DELETE_FAILED", "ROLLBACK_COMPLETE", "ROLLBACK_FAILED", "UPDATE_ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_FAILED"]
9
9
  END_STATES = SUCESS_STATES + FAILURE_STATES
10
10
 
11
11
  def initialize(name)
@@ -21,44 +21,47 @@ module Stackup
21
21
  :disable_rollback => true,
22
22
  :capabilities => ["CAPABILITY_IAM"],
23
23
  :parameters => parameters)
24
- wait_till_end
24
+ wait_for_events
25
25
  !response[:stack_id].nil?
26
26
  end
27
27
 
28
- def deploy(template, parameters = [])
29
- if deployed?
30
- update(template, parameters)
31
- else
32
- create(template, parameters)
33
- end
34
- rescue Aws::CloudFormation::Errors::ValidationError => e
35
- puts e.message
36
- end
37
-
38
28
  def update(template, parameters)
39
29
  return false unless deployed?
30
+ if stack.stack_status == "CREATE_FAILED"
31
+ puts "Stack is in CREATE_FAILED state so must be manually deleted before it can be updated"
32
+ return false
33
+ end
34
+ if stack.stack_status == "ROLLBACK_COMPLETE"
35
+ deleted = delete
36
+ return false if !deleted
37
+ end
40
38
  response = cf.update_stack(:stack_name => name, :template_body => template, :parameters => parameters, :capabilities => ["CAPABILITY_IAM"])
41
- wait_till_end
39
+ wait_for_events
42
40
  !response[:stack_id].nil?
43
41
  end
44
42
 
45
43
  def delete
46
- response = cf.delete_stack(:stack_name => name)
47
- stack.wait_until(:max_attempts => 1000, :delay => 10) { |resource| display_events; END_STATES.include?(resource.stack_status) }
44
+ return false unless deployed?
45
+ cf.delete_stack(:stack_name => name)
46
+ status = wait_for_events
47
+ fail UpdateError, "stack delete failed" unless status == "DELETE_COMPLETE"
48
+ true
48
49
  rescue Aws::CloudFormation::Errors::ValidationError
49
50
  puts "Stack does not exist."
50
51
  end
51
52
 
52
- def outputs
53
- puts stack.outputs.flat_map { |output| "#{output.output_key} - #{output.output_value}" }
53
+ def deploy(template, parameters = [])
54
+ if deployed?
55
+ update(template, parameters)
56
+ else
57
+ create(template, parameters)
58
+ end
59
+ rescue Aws::CloudFormation::Errors::ValidationError => e
60
+ puts e.message
54
61
  end
55
62
 
56
- def display_events
57
- monitor.new_events.each do |e|
58
- ts = e.timestamp.localtime.strftime("%H:%M:%S")
59
- fields = [e.logical_resource_id, e.resource_status, e.resource_status_reason]
60
- puts("[#{ts}] #{fields.compact.join(' - ')}")
61
- end
63
+ def outputs
64
+ puts stack.outputs.flat_map { |output| "#{output.output_key} - #{output.output_value}" }
62
65
  end
63
66
 
64
67
  def deployed?
@@ -72,11 +75,28 @@ module Stackup
72
75
  response[:code].nil?
73
76
  end
74
77
 
78
+ class UpdateError < StandardError
79
+ end
80
+
75
81
  private
76
82
 
77
- def wait_till_end
78
- stack.wait_until(:max_attempts => 1000, :delay => 10) { |resource| display_events; END_STATES.include?(resource.stack_status) }
83
+ # Wait (displaying stack events) until the stack reaches a stable state.
84
+ #
85
+ def wait_for_events
86
+ loop do
87
+ display_new_events
88
+ status = stack_status
89
+ return status if status =~ /_(COMPLETE|FAILED)$/
90
+ sleep(poll_interval)
91
+ end
79
92
  end
80
93
 
94
+ def display_new_events
95
+ monitor.new_events.each do |e|
96
+ ts = e.timestamp.localtime.strftime("%H:%M:%S")
97
+ fields = [e.logical_resource_id, e.resource_status, e.resource_status_reason]
98
+ puts("[#{ts}] #{fields.compact.join(' - ')}")
99
+ end
100
+ end
81
101
  end
82
102
  end
Binary file
Binary file
@@ -1,81 +1,222 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Stackup::Stack do
4
- let(:stack) { Stackup::Stack.new("stack_name") }
4
+
5
+ let(:stack) { described_class.new("stack_name") }
6
+
7
+ let(:cf_stack) { instance_double("Aws::CloudFormation::Stack") }
8
+ let(:cf_client) { instance_double("Aws::CloudFormation::Client") }
9
+
5
10
  let(:template) { double(String) }
6
- let(:cf_stack) { double(Aws::CloudFormation::Stack) }
7
- let(:cf) { double(Aws::CloudFormation::Client) }
8
11
  let(:parameters) { [] }
9
12
 
13
+ let(:response) { Seahorse::Client::Http::Response.new }
14
+
10
15
  before do
11
- allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf)
16
+ allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_client)
12
17
  allow(Aws::CloudFormation::Stack).to receive(:new).and_return(cf_stack)
13
18
  end
14
19
 
15
- context "deploy" do
20
+ describe "#create" do
21
+
22
+ subject(:created) { stack.create(template, parameters) }
16
23
 
17
- it "should call create if stack is not deployed" do
18
- allow(stack).to receive(:deployed?).and_return(false)
19
- expect(stack).to receive(:create).and_return(true)
20
- expect(stack.deploy(template, parameters)).to be true
24
+ before do
25
+ allow(cf_client).to receive(:create_stack).and_return(response)
26
+ allow(stack).to receive(:wait_for_events).and_return(true)
21
27
  end
22
- it "should call update if stack is deployed" do
23
- allow(stack).to receive(:deployed?).and_return(true)
24
- expect(stack).to receive(:update).and_return(true)
25
- expect(stack.deploy(template, parameters)).to be true
28
+
29
+ context "when stack gets successfully created" do
30
+ before do
31
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
32
+ end
33
+ it { expect(created).to be true }
34
+ end
35
+
36
+ context "when stack creation fails" do
37
+ before do
38
+ allow(response).to receive(:[]).with(:stack_id).and_return(nil)
39
+ end
40
+ it { expect(created).to be false }
26
41
  end
42
+
27
43
  end
28
44
 
29
- context "update" do
30
- it "should allow stack update" do
31
- allow(cf_stack).to receive(:stack_status).and_return("1")
32
- response = Seahorse::Client::Http::Response.new
33
- allow(response).to receive(:[]).with(:stack_id).and_return("1")
34
- allow(cf).to receive(:update_stack).and_return(response)
35
- allow(cf_stack).to receive(:wait_until).and_return(true)
36
- expect(stack.update(template, parameters)).to be true
45
+ describe "#update" do
46
+ subject(:updated) { stack.update(template, parameters) }
47
+
48
+ context "when there is no existing stack" do
49
+ before do
50
+ allow(stack).to receive(:deployed?).and_return(false)
51
+ end
52
+ it { expect(updated).to be false }
37
53
  end
38
54
 
39
- it "should not update if the stack is not deployed" do
40
- allow(cf_stack).to receive(:stack_status).and_raise(Aws::CloudFormation::Errors::ValidationError.new("1", "2"))
41
- expect(stack.update(template, parameters)).to be false
55
+ context "when there is an existing stack" do
56
+ before do
57
+ allow(stack).to receive(:deployed?).and_return(true)
58
+ allow(cf_client).to receive(:update_stack).and_return(response)
59
+ allow(stack).to receive(:wait_for_events).and_return(true)
60
+ end
61
+
62
+ context "in a successfully deployed state" do
63
+ before do
64
+ allow(cf_stack).to receive(:stack_status).and_return("CREATE_COMPLETE")
65
+ end
66
+
67
+ context "when stack gets successfully updated" do
68
+ before do
69
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
70
+ end
71
+ it { expect(updated).to be true }
72
+ end
73
+
74
+ context "when stack update fails" do
75
+ before do
76
+ allow(response).to receive(:[]).with(:stack_id).and_return(nil)
77
+ end
78
+ it { expect(updated).to be false }
79
+ end
80
+ end
81
+
82
+ context "in a ROLLBACK_COMPLETE state" do
83
+ before do
84
+ allow(cf_stack).to receive(:stack_status).and_return("ROLLBACK_COMPLETE")
85
+ end
86
+
87
+ context "when deleting existing stack succeeds" do
88
+
89
+ it "deletes the existing stack" do
90
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
91
+ expect(stack).to receive(:delete).and_return(true)
92
+ stack.update(template, parameters)
93
+ end
94
+
95
+ context "when stack gets successfully updated" do
96
+ before do
97
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
98
+ allow(stack).to receive(:delete).and_return(true)
99
+ end
100
+ it { expect(updated).to be true }
101
+ end
102
+
103
+ context "when stack update fails" do
104
+ before do
105
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
106
+ allow(stack).to receive(:delete).and_return(false)
107
+ end
108
+ it { expect(updated).to be false }
109
+ end
110
+ end
111
+
112
+ context "when deleting existing stack fails" do
113
+ before do
114
+ allow(stack).to receive(:delete).and_return(false)
115
+ end
116
+ it { expect(updated).to be false }
117
+ end
118
+ end
119
+
120
+ context "in a CREATE_FAILED state" do
121
+ before do
122
+ allow(cf_stack).to receive(:stack_status).and_return("CREATE_FAILED")
123
+ end
124
+
125
+ it "does not try to delete the existing stack" do
126
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
127
+ expect(stack).not_to receive(:delete)
128
+ stack.update(template, parameters)
129
+ end
130
+
131
+ it "does not try to delete the existing stack" do
132
+ allow(response).to receive(:[]).with(:stack_id).and_return("1")
133
+ expect(cf_client).not_to receive(:delete_stack)
134
+ expect(updated).to be false
135
+ end
136
+ end
42
137
  end
43
138
  end
44
139
 
45
- context "delete" do
46
- it "should delete the stack if it exists?" do
47
- response = double(Struct)
48
- allow(cf).to receive(:delete_stack).and_return(response)
49
- allow(cf_stack).to receive(:wait_until).and_return(response)
50
- expect(stack.delete).to be response
140
+ describe "#deploy" do
141
+
142
+ subject(:deploy) { stack.deploy(template, parameters) }
143
+
144
+ context "when stack already exists" do
145
+
146
+ before do
147
+ allow(stack).to receive(:deployed?).and_return(true)
148
+ allow(cf_stack).to receive(:stack_status).and_return("CREATE_COMPLETE")
149
+ allow(cf_client).to receive(:update_stack).and_return({ stack_id: "stack-name" })
150
+ end
151
+
152
+ it "updates the stack" do
153
+ expect(stack).to receive(:update)
154
+ deploy
155
+ end
156
+ end
157
+
158
+ context "when stack does not exist" do
159
+
160
+ before do
161
+ allow(stack).to receive(:deployed?).and_return(false)
162
+ allow(cf_client).to receive(:create_stack).and_return({ stack_id: "stack-name" })
163
+ end
164
+
165
+ it "creates a new stack" do
166
+ expect(stack).to receive(:create)
167
+ deploy
168
+ end
51
169
  end
52
170
  end
53
171
 
54
- context "create" do
55
- let(:response) { Seahorse::Client::Http::Response.new }
56
- it "should create stack if all is well" do
57
- allow(response).to receive(:[]).with(:stack_id).and_return("1")
58
- allow(cf).to receive(:create_stack).and_return(response)
59
- allow(cf_stack).to receive(:wait_until).and_return(true)
60
- expect(stack.create(template, parameters)).to be true
172
+
173
+ describe "#delete" do
174
+
175
+ subject(:deleted) { stack.delete }
176
+
177
+ context "there is no existing stack" do
178
+ before do
179
+ allow(stack).to receive(:deployed?).and_return false
180
+ end
181
+
182
+ it { expect(deleted).to be false }
183
+
184
+ it "does not try to delete the stack" do
185
+ expect(cf_client).not_to receive(:delete_stack)
186
+ end
61
187
  end
62
188
 
63
- it "should return nil if stack was not created" do
64
- allow(response).to receive(:[]).with(:stack_id).and_return(nil)
65
- allow(cf).to receive(:create_stack).and_return(response)
66
- allow(cf_stack).to receive(:wait_until).and_return(false)
67
- expect(stack.create(template, parameters)).to be false
189
+ context "there is an existing stack" do
190
+ before do
191
+ allow(stack).to receive(:deployed?).and_return true
192
+ allow(cf_client).to receive(:delete_stack)
193
+ end
194
+
195
+ context "deleting the stack succeeds" do
196
+ before do
197
+ allow(stack).to receive(:wait_for_events).and_return("DELETE_COMPLETE")
198
+ end
199
+ it { expect(deleted).to be true }
200
+ end
201
+
202
+ context "deleting the stack fails" do
203
+ before do
204
+ allow(stack).to receive(:wait_for_events).and_return("DELETE_FAILED")
205
+ end
206
+ it { expect{ deleted }.to raise_error(Stackup::Stack::UpdateError) }
207
+ end
68
208
  end
69
209
  end
70
210
 
211
+
71
212
  context "validate" do
72
213
  it "should be valid if cf validate say so" do
73
- allow(cf).to receive(:validate_template).and_return({})
214
+ allow(cf_client).to receive(:validate_template).and_return({})
74
215
  expect(stack.valid?(template)).to be true
75
216
  end
76
217
 
77
218
  it "should be invalid if cf validate say so" do
78
- allow(cf).to receive(:validate_template).and_return(:code => "404")
219
+ allow(cf_client).to receive(:validate_template).and_return(:code => "404")
79
220
  expect(stack.valid?(template)).to be false
80
221
  end
81
222
 
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  Gem::Specification.new do |spec|
5
5
 
6
6
  spec.name = "stackup"
7
- spec.version = "0.0.6"
7
+ spec.version = "0.0.7"
8
8
  spec.authors = ["Arvind Kunday", "Mike Williams"]
9
9
  spec.email = ["arvind.kunday@rea-group.com", "mike.williams@rea-group.com"]
10
10
  spec.summary = "Tools for deployment to AWS"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arvind Kunday
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-09-28 00:00:00.000000000 Z
12
+ date: 2015-09-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -62,6 +62,7 @@ files:
62
62
  - pkg/stackup-0.0.4.gem
63
63
  - pkg/stackup-0.0.5.gem
64
64
  - pkg/stackup-0.0.6.gem
65
+ - pkg/stackup-0.0.7.gem
65
66
  - sample.json
66
67
  - spec/spec_helper.rb
67
68
  - spec/stackup/monitor_spec.rb