stackup 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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