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 +4 -4
- data/lib/stackup/stack.rb +46 -26
- data/pkg/stackup-0.0.6.gem +0 -0
- data/pkg/stackup-0.0.7.gem +0 -0
- data/spec/stackup/stack_spec.rb +185 -44
- data/stackup.gemspec +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3d43bfd3b75ab37503b4b44933dcb086ad80351
|
4
|
+
data.tar.gz: 6d43253e47b02d61f549ccd6c9064046780ec71f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b6971f3281601543ed3ee5d088ccacc2b7069e5b46acbf5bd8d18c2a911a3db36a6bc532c04eac14660fcccd6538c7d3d8258349eb04e3541013ccb2e002ab8
|
7
|
+
data.tar.gz: ecb74e9962901afd46410cdace610dff8b6fbfc75efaec26c346f25f0da47866450cb065c5f75fb7a953cf2d45ed4054710760d7091ff8f723119a484b970693
|
data/lib/stackup/stack.rb
CHANGED
@@ -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", "
|
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
|
-
|
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
|
-
|
39
|
+
wait_for_events
|
42
40
|
!response[:stack_id].nil?
|
43
41
|
end
|
44
42
|
|
45
43
|
def delete
|
46
|
-
|
47
|
-
|
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
|
53
|
-
|
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
|
57
|
-
|
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
|
-
|
78
|
-
|
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
|
data/pkg/stackup-0.0.6.gem
CHANGED
Binary file
|
Binary file
|
data/spec/stackup/stack_spec.rb
CHANGED
@@ -1,81 +1,222 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Stackup::Stack do
|
4
|
-
|
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(
|
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
|
-
|
20
|
+
describe "#create" do
|
21
|
+
|
22
|
+
subject(:created) { stack.create(template, parameters) }
|
16
23
|
|
17
|
-
|
18
|
-
allow(
|
19
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
expect(
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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(
|
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(
|
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
|
|
data/stackup.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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
|