stackup 0.1.0 → 0.2.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 +4 -4
- data/Gemfile.lock +12 -12
- data/README.md +38 -15
- data/bin/stackup +76 -34
- data/doc/Stackup/ErrorMappingProxy.html +378 -0
- data/doc/Stackup/InvalidStateError.html +142 -0
- data/doc/Stackup/NoSuchStack.html +142 -0
- data/doc/Stackup/NoUpdateRequired.html +134 -0
- data/doc/Stackup/Service.html +399 -0
- data/doc/Stackup/ServiceError.html +138 -0
- data/doc/Stackup/Stack.html +1269 -0
- data/doc/Stackup/StackUpdateError.html +142 -0
- data/doc/Stackup/StackWatcher.html +455 -0
- data/doc/Stackup.html +202 -0
- data/doc/_index.html +199 -0
- data/doc/class_list.html +58 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file.README.html +143 -0
- data/doc/file_list.html +60 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +143 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +181 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +195 -0
- data/doc/top-level-namespace.html +194 -0
- data/lib/stackup/error_mapping_proxy.rb +37 -0
- data/lib/stackup/errors.rb +9 -6
- data/lib/stackup/service.rb +39 -0
- data/lib/stackup/stack.rb +52 -59
- data/lib/stackup/stack_watcher.rb +16 -10
- data/lib/stackup.rb +26 -0
- data/spec/spec_helper.rb +15 -1
- data/spec/stackup/stack_spec.rb +114 -48
- data/spec/stackup/stack_watcher_spec.rb +20 -15
- data/stackup.gemspec +1 -1
- data/woollyams/template.json +44 -0
- metadata +29 -6
- data/pkg/stackup-0.0.1.gem +0 -0
- data/pkg/stackup-0.0.8.gem +0 -0
- data/pkg/stackup-0.0.9.gem +0 -0
- data/woollyams/sample-template.json +0 -79
@@ -0,0 +1,37 @@
|
|
1
|
+
require "stackup/errors"
|
2
|
+
|
3
|
+
module Stackup
|
4
|
+
|
5
|
+
# An error-mapping proxy for Aws::CloudFormation models.
|
6
|
+
#
|
7
|
+
# It exists to convert certain types of `ValidationError`, where useful
|
8
|
+
# information is hidden inside the "message", to Stackup exceptions.
|
9
|
+
#
|
10
|
+
class ErrorMappingProxy
|
11
|
+
|
12
|
+
def initialize(delegate)
|
13
|
+
@delegate = delegate
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(*args)
|
17
|
+
@delegate.send(*args)
|
18
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
19
|
+
case e.message
|
20
|
+
when "No updates are to be performed."
|
21
|
+
raise NoUpdateRequired, "no updates are required"
|
22
|
+
when /Stack .* does not exist$/
|
23
|
+
raise NoSuchStack, "no such stack"
|
24
|
+
when / can ?not be /
|
25
|
+
raise InvalidStateError, e.message
|
26
|
+
else
|
27
|
+
raise e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def respond_to?(method)
|
32
|
+
@delegate.respond_to?(method)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/stackup/errors.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
module Stackup
|
2
2
|
|
3
3
|
# Base Stackup Exception class
|
4
|
-
class ServiceError < StandardError
|
5
|
-
end
|
4
|
+
class ServiceError < StandardError; end
|
6
5
|
|
7
6
|
# Raised when the specified stack does not exist
|
8
|
-
class NoSuchStack < ServiceError
|
9
|
-
end
|
7
|
+
class NoSuchStack < ServiceError; end
|
10
8
|
|
11
9
|
# Raised to indicate a problem updating a stack
|
12
|
-
class StackUpdateError < ServiceError
|
13
|
-
|
10
|
+
class StackUpdateError < ServiceError; end
|
11
|
+
|
12
|
+
# Raised if we can't perform that operation now
|
13
|
+
class InvalidStateError < ServiceError; end
|
14
|
+
|
15
|
+
# Raised when a stack is already up-to-date
|
16
|
+
class NoUpdateRequired < StandardError; end
|
14
17
|
|
15
18
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "aws-sdk-core"
|
2
|
+
require "stackup/stack"
|
3
|
+
|
4
|
+
module Stackup
|
5
|
+
|
6
|
+
# A handle to CloudFormation.
|
7
|
+
#
|
8
|
+
class Service
|
9
|
+
|
10
|
+
def initialize(cf_client = {})
|
11
|
+
cf_client = Aws::CloudFormation::Client.new(cf_client) if cf_client.is_a?(Hash)
|
12
|
+
@cf_client = cf_client
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Stackup::Stack] the named stack
|
16
|
+
#
|
17
|
+
def stack(name, options = {})
|
18
|
+
Stack.new(name, cf_client, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Enumeration<String>] names of existing stacks
|
22
|
+
#
|
23
|
+
def stack_names
|
24
|
+
Enumerator.new do |y|
|
25
|
+
cf_client.describe_stacks.each do |response|
|
26
|
+
response.stacks.each do |stack|
|
27
|
+
y << stack.stack_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :cf_client
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/stackup/stack.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "aws-sdk-resources"
|
2
2
|
require "logger"
|
3
|
-
require "stackup/
|
3
|
+
require "stackup/error_mapping_proxy"
|
4
4
|
require "stackup/stack_watcher"
|
5
5
|
|
6
6
|
module Stackup
|
@@ -13,7 +13,6 @@ module Stackup
|
|
13
13
|
client = Aws::CloudFormation::Client.new(client) if client.is_a?(Hash)
|
14
14
|
@name = name
|
15
15
|
@cf_client = client
|
16
|
-
@watcher = Stackup::StackWatcher.new(cf_stack)
|
17
16
|
options.each do |key, value|
|
18
17
|
public_send("#{key}=", value)
|
19
18
|
end
|
@@ -35,8 +34,6 @@ module Stackup
|
|
35
34
|
#
|
36
35
|
def status
|
37
36
|
cf_stack.stack_status
|
38
|
-
rescue Aws::CloudFormation::Errors::ValidationError => e
|
39
|
-
handle_validation_error(e)
|
40
37
|
end
|
41
38
|
|
42
39
|
# @return [boolean] true iff the stack exists
|
@@ -50,16 +47,20 @@ module Stackup
|
|
50
47
|
|
51
48
|
# Create or update the stack.
|
52
49
|
#
|
53
|
-
# @param [
|
54
|
-
#
|
50
|
+
# @param [Hash] options create/update options
|
51
|
+
# accepts a superset of the options supported by
|
52
|
+
# +Aws::CloudFormation::Stack#update+
|
53
|
+
# (see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Stack.html#update-instance_method)
|
55
54
|
# @return [Symbol] `:created` or `:updated` if successful
|
56
55
|
# @raise [Stackup::StackUpdateError] if operation fails
|
57
56
|
#
|
58
|
-
def create_or_update(
|
57
|
+
def create_or_update(options)
|
58
|
+
options = options.dup
|
59
|
+
options[:capabilities] ||= ["CAPABILITY_IAM"]
|
59
60
|
delete if ALMOST_DEAD_STATUSES.include?(status)
|
60
|
-
update(
|
61
|
+
update(options)
|
61
62
|
rescue NoSuchStack
|
62
|
-
create(
|
63
|
+
create(options)
|
63
64
|
end
|
64
65
|
|
65
66
|
alias_method :up, :create_or_update
|
@@ -74,17 +75,37 @@ module Stackup
|
|
74
75
|
# @raise [Stackup::StackUpdateError] if operation fails
|
75
76
|
#
|
76
77
|
def delete
|
77
|
-
|
78
|
+
begin
|
79
|
+
@stack_id = cf_stack.stack_id
|
80
|
+
rescue NoSuchStack
|
81
|
+
return nil
|
82
|
+
end
|
78
83
|
status = modify_stack do
|
79
|
-
|
84
|
+
cf_stack.delete
|
80
85
|
end
|
81
|
-
fail StackUpdateError, "stack delete failed" unless status
|
82
|
-
rescue NoSuchStack
|
86
|
+
fail StackUpdateError, "stack delete failed" unless status == "DELETE_COMPLETE"
|
83
87
|
:deleted
|
88
|
+
ensure
|
89
|
+
@stack_id = nil
|
84
90
|
end
|
85
91
|
|
86
92
|
alias_method :down, :delete
|
87
93
|
|
94
|
+
# Cancel update in-progress.
|
95
|
+
#
|
96
|
+
# @return [Symbol] `:update_cancelled` if successful
|
97
|
+
# @raise [Stackup::StackUpdateError] if operation fails
|
98
|
+
#
|
99
|
+
def cancel_update
|
100
|
+
status = modify_stack do
|
101
|
+
cf_stack.cancel_update
|
102
|
+
end
|
103
|
+
fail StackUpdateError, "update cancel failed" unless status =~ /_COMPLETE$/
|
104
|
+
:update_cancelled
|
105
|
+
rescue InvalidStateError
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
88
109
|
# Get stack outputs.
|
89
110
|
#
|
90
111
|
# @return [Hash<String, String>]
|
@@ -97,28 +118,23 @@ module Stackup
|
|
97
118
|
h[output.output_key] = output.output_value
|
98
119
|
end
|
99
120
|
end
|
100
|
-
rescue Aws::CloudFormation::Errors::ValidationError => e
|
101
|
-
handle_validation_error(e)
|
102
121
|
end
|
103
122
|
|
104
123
|
private
|
105
124
|
|
106
|
-
def create(
|
125
|
+
def create(options)
|
126
|
+
options[:stack_name] = name
|
107
127
|
status = modify_stack do
|
108
|
-
|
109
|
-
:stack_name => name,
|
110
|
-
:template_body => template,
|
111
|
-
:capabilities => ["CAPABILITY_IAM"],
|
112
|
-
:parameters => parameters
|
113
|
-
)
|
128
|
+
ErrorMappingProxy.new(cf).create_stack(options)
|
114
129
|
end
|
115
130
|
fail StackUpdateError, "stack creation failed" unless status == "CREATE_COMPLETE"
|
116
131
|
:created
|
117
132
|
end
|
118
133
|
|
119
|
-
def update(
|
134
|
+
def update(options)
|
135
|
+
options.delete(:disable_rollback)
|
120
136
|
status = modify_stack do
|
121
|
-
|
137
|
+
cf_stack.update(options)
|
122
138
|
end
|
123
139
|
fail StackUpdateError, "stack update failed" unless status == "UPDATE_COMPLETE"
|
124
140
|
:updated
|
@@ -127,11 +143,17 @@ module Stackup
|
|
127
143
|
end
|
128
144
|
|
129
145
|
def logger
|
130
|
-
@logger ||=
|
146
|
+
@logger ||= cf_client.config[:logger]
|
147
|
+
@logger ||= Logger.new($stdout).tap { |l| l.level = Logger::INFO }
|
148
|
+
end
|
149
|
+
|
150
|
+
def cf
|
151
|
+
Aws::CloudFormation::Resource.new(:client => cf_client)
|
131
152
|
end
|
132
153
|
|
133
154
|
def cf_stack
|
134
|
-
|
155
|
+
id_or_name = @stack_id || name
|
156
|
+
ErrorMappingProxy.new(cf.stack(id_or_name))
|
135
157
|
end
|
136
158
|
|
137
159
|
def event_handler
|
@@ -146,47 +168,18 @@ module Stackup
|
|
146
168
|
# @return the final stack status
|
147
169
|
#
|
148
170
|
def modify_stack
|
171
|
+
watcher = Stackup::StackWatcher.new(cf_stack)
|
149
172
|
watcher.zero
|
150
173
|
yield
|
151
|
-
wait_until_stable
|
152
|
-
rescue Aws::CloudFormation::Errors::ValidationError => e
|
153
|
-
handle_validation_error(e)
|
154
|
-
end
|
155
|
-
|
156
|
-
# Wait (displaying stack events) until the stack reaches a stable state.
|
157
|
-
#
|
158
|
-
# @return the final stack status
|
159
|
-
#
|
160
|
-
def wait_until_stable
|
161
174
|
loop do
|
162
|
-
|
163
|
-
|
175
|
+
watcher.each_new_event(&event_handler)
|
176
|
+
status = self.status
|
177
|
+
logger.debug("stack_status=#{status}")
|
164
178
|
return status if status.nil? || status =~ /_(COMPLETE|FAILED)$/
|
165
179
|
sleep(5)
|
166
180
|
end
|
167
181
|
end
|
168
182
|
|
169
|
-
def report_new_events
|
170
|
-
watcher.new_events.each do |e|
|
171
|
-
event_handler.call(e)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def handle_validation_error(e)
|
176
|
-
case e.message
|
177
|
-
when "No updates are to be performed."
|
178
|
-
fail NoUpdateRequired, "no updates are required"
|
179
|
-
when / does not exist$/
|
180
|
-
fail NoSuchStack, "no such stack: #{name}"
|
181
|
-
else
|
182
|
-
raise e
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# Raised when a stack is already up-to-date
|
187
|
-
class NoUpdateRequired < StandardError
|
188
|
-
end
|
189
|
-
|
190
183
|
end
|
191
184
|
|
192
185
|
end
|
@@ -2,6 +2,10 @@ require "aws-sdk-core"
|
|
2
2
|
|
3
3
|
module Stackup
|
4
4
|
|
5
|
+
# A stack event observer.
|
6
|
+
#
|
7
|
+
# Keeps track of previously processed events, and yields the new ones.
|
8
|
+
#
|
5
9
|
class StackWatcher
|
6
10
|
|
7
11
|
def initialize(stack)
|
@@ -13,22 +17,24 @@ module Stackup
|
|
13
17
|
|
14
18
|
# Yield all events since the last call
|
15
19
|
#
|
16
|
-
def
|
17
|
-
[]
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@processed_event_ids.add(event.event_id)
|
22
|
-
end
|
20
|
+
def each_new_event
|
21
|
+
buffer = []
|
22
|
+
stack.events.each do |event|
|
23
|
+
break if @processed_event_ids.include?(event.event_id)
|
24
|
+
buffer.unshift(event)
|
23
25
|
end
|
24
|
-
|
25
|
-
|
26
|
+
buffer.each do |event|
|
27
|
+
yield event if block_given?
|
28
|
+
@processed_event_ids.add(event.event_id)
|
29
|
+
end
|
30
|
+
rescue Aws::CloudFormation::Errors::ValidationError
|
31
|
+
# okay
|
26
32
|
end
|
27
33
|
|
28
34
|
# Consume all new events
|
29
35
|
#
|
30
36
|
def zero
|
31
|
-
|
37
|
+
each_new_event
|
32
38
|
nil
|
33
39
|
end
|
34
40
|
|
data/lib/stackup.rb
CHANGED
@@ -1 +1,27 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "stackup/service"
|
1
3
|
require "stackup/stack"
|
4
|
+
|
5
|
+
# Allow use of `Stackup.stacks` rather than `Stackup().stacks`
|
6
|
+
#
|
7
|
+
module Stackup
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def service(client = {})
|
12
|
+
Stackup::Service.new(client)
|
13
|
+
end
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :service, :stack, :stack_names
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
# rubocop:disable Style/MethodName
|
24
|
+
|
25
|
+
def Stackup(*args)
|
26
|
+
Stackup.service(*args)
|
27
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
-
require
|
1
|
+
require "console_logger"
|
2
|
+
|
3
|
+
module CfStubbing
|
4
|
+
|
5
|
+
def stub_cf_client
|
6
|
+
client_options = { :stub_responses => true }
|
7
|
+
if ENV.key?("AWS_DEBUG")
|
8
|
+
client_options[:logger] = ConsoleLogger.new(STDOUT, true)
|
9
|
+
client_options[:log_level] = :debug
|
10
|
+
end
|
11
|
+
Aws::CloudFormation::Client.new(client_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
2
15
|
|
3
16
|
RSpec.configure do |c|
|
4
17
|
c.mock_with :rspec
|
18
|
+
c.include CfStubbing
|
5
19
|
end
|
data/spec/stackup/stack_spec.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
require "stackup/stack"
|
4
|
+
|
3
5
|
describe Stackup::Stack do
|
4
6
|
|
5
|
-
let(:cf_client)
|
6
|
-
client_options = { :stub_responses => true }
|
7
|
-
if ENV.key?("AWS_DEBUG")
|
8
|
-
client_options[:logger] = Logger.new(STDOUT)
|
9
|
-
client_options[:log_level] = :debug
|
10
|
-
end
|
11
|
-
Aws::CloudFormation::Client.new(client_options)
|
12
|
-
end
|
7
|
+
let(:cf_client) { stub_cf_client }
|
13
8
|
|
14
9
|
let(:stack_name) { "stack_name" }
|
15
10
|
let(:unique_stack_id) { "ID:#{stack_name}" }
|
@@ -23,24 +18,21 @@ describe Stackup::Stack do
|
|
23
18
|
allow(cf_client).to receive(:create_stack).and_call_original
|
24
19
|
allow(cf_client).to receive(:delete_stack).and_call_original
|
25
20
|
allow(cf_client).to receive(:update_stack).and_call_original
|
21
|
+
allow(cf_client).to receive(:cancel_update_stack).and_call_original
|
26
22
|
end
|
27
23
|
|
28
|
-
def
|
24
|
+
def validation_error(message)
|
29
25
|
{
|
30
26
|
:status_code => 400,
|
31
27
|
:headers => {},
|
32
|
-
:body =>
|
28
|
+
:body => [
|
29
|
+
"<ErrorResponse><Error><Code>ValidationError</Code><Message>",
|
30
|
+
message,
|
31
|
+
"</Message></Error></ErrorResponse>"
|
32
|
+
].join
|
33
33
|
}
|
34
34
|
end
|
35
35
|
|
36
|
-
def stack_does_not_exist
|
37
|
-
service_error("ValidationError", "Stack with id #{stack_name} does not exist")
|
38
|
-
end
|
39
|
-
|
40
|
-
def no_update_required
|
41
|
-
service_error("ValidationError", "No updates are to be performed.")
|
42
|
-
end
|
43
|
-
|
44
36
|
def stack_description(stack_status)
|
45
37
|
{
|
46
38
|
:stacks => [
|
@@ -58,7 +50,7 @@ describe Stackup::Stack do
|
|
58
50
|
|
59
51
|
let(:describe_stacks_responses) do
|
60
52
|
[
|
61
|
-
|
53
|
+
validation_error("Stack with id #{stack_name} does not exist")
|
62
54
|
]
|
63
55
|
end
|
64
56
|
|
@@ -85,7 +77,7 @@ describe Stackup::Stack do
|
|
85
77
|
let(:template) { "stack template" }
|
86
78
|
|
87
79
|
def create_or_update
|
88
|
-
stack.create_or_update(template)
|
80
|
+
stack.create_or_update(:template_body => template)
|
89
81
|
end
|
90
82
|
|
91
83
|
context "successful" do
|
@@ -162,14 +154,14 @@ describe Stackup::Stack do
|
|
162
154
|
let(:describe_stacks_responses) do
|
163
155
|
super() + [
|
164
156
|
stack_description("DELETE_IN_PROGRESS"),
|
165
|
-
|
157
|
+
stack_description("DELETE_COMPLETE")
|
166
158
|
]
|
167
159
|
end
|
168
160
|
|
169
161
|
it "calls delete_stack" do
|
170
162
|
stack.delete
|
171
163
|
expect(cf_client).to have_received(:delete_stack)
|
172
|
-
.with(hash_including(:stack_name =>
|
164
|
+
.with(hash_including(:stack_name => unique_stack_id))
|
173
165
|
end
|
174
166
|
|
175
167
|
it "returns :deleted" do
|
@@ -199,28 +191,36 @@ describe Stackup::Stack do
|
|
199
191
|
|
200
192
|
let(:template) { "stack template" }
|
201
193
|
|
194
|
+
let(:options) do
|
195
|
+
{ :template_body => template }
|
196
|
+
end
|
197
|
+
|
202
198
|
def create_or_update
|
203
|
-
stack.create_or_update(
|
199
|
+
stack.create_or_update(options)
|
204
200
|
end
|
205
201
|
|
206
|
-
|
202
|
+
let(:describe_stacks_responses) do
|
203
|
+
super() + [
|
204
|
+
stack_description("UPDATE_IN_PROGRESS"),
|
205
|
+
final_describe_stacks_response
|
206
|
+
]
|
207
|
+
end
|
207
208
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
stack_description("UPDATE_COMPLETE")
|
212
|
-
]
|
213
|
-
end
|
209
|
+
let(:final_describe_stacks_response) do
|
210
|
+
stack_description("UPDATE_COMPLETE")
|
211
|
+
end
|
214
212
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
213
|
+
it "calls :update_stack" do
|
214
|
+
expected_args = {
|
215
|
+
:stack_name => stack_name,
|
216
|
+
:template_body => template
|
217
|
+
}
|
218
|
+
create_or_update
|
219
|
+
expect(cf_client).to have_received(:update_stack)
|
220
|
+
.with(hash_including(expected_args))
|
221
|
+
end
|
222
|
+
|
223
|
+
context "successful" do
|
224
224
|
|
225
225
|
it "returns :updated" do
|
226
226
|
expect(create_or_update).to eq(:updated)
|
@@ -229,7 +229,10 @@ describe Stackup::Stack do
|
|
229
229
|
context "if no updates are required" do
|
230
230
|
|
231
231
|
before do
|
232
|
-
cf_client.stub_responses(
|
232
|
+
cf_client.stub_responses(
|
233
|
+
:update_stack,
|
234
|
+
validation_error("No updates are to be performed.")
|
235
|
+
)
|
233
236
|
end
|
234
237
|
|
235
238
|
it "returns nil" do
|
@@ -242,11 +245,8 @@ describe Stackup::Stack do
|
|
242
245
|
|
243
246
|
context "unsuccessful" do
|
244
247
|
|
245
|
-
let(:
|
246
|
-
|
247
|
-
stack_description("UPDATE_IN_PROGRESS"),
|
248
|
-
stack_description("UPDATE_ROLLBACK_COMPLETE")
|
249
|
-
]
|
248
|
+
let(:final_describe_stacks_response) do
|
249
|
+
stack_description("UPDATE_ROLLBACK_COMPLETE")
|
250
250
|
end
|
251
251
|
|
252
252
|
it "raises a StackUpdateError" do
|
@@ -255,6 +255,20 @@ describe Stackup::Stack do
|
|
255
255
|
|
256
256
|
end
|
257
257
|
|
258
|
+
context "with :disable_rollback" do
|
259
|
+
|
260
|
+
before do
|
261
|
+
options[:disable_rollback] = true
|
262
|
+
end
|
263
|
+
|
264
|
+
it "calls :update_stack" do
|
265
|
+
create_or_update
|
266
|
+
expect(cf_client).to have_received(:update_stack)
|
267
|
+
.with(hash_not_including(:disable_rollback))
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
258
272
|
end
|
259
273
|
|
260
274
|
%w(CREATE_FAILED ROLLBACK_COMPLETE).each do |initial_status|
|
@@ -267,20 +281,23 @@ describe Stackup::Stack do
|
|
267
281
|
let(:template) { "stack template" }
|
268
282
|
|
269
283
|
def create_or_update
|
270
|
-
stack.create_or_update(template)
|
284
|
+
stack.create_or_update(:template_body => template)
|
271
285
|
end
|
272
286
|
|
273
287
|
let(:describe_stacks_responses) do
|
274
288
|
super() + [
|
275
289
|
stack_description("DELETE_IN_PROGRESS"),
|
276
|
-
|
290
|
+
validation_error("Stack with id #{stack_name} does not exist"),
|
277
291
|
stack_description("CREATE_IN_PROGRESS"),
|
278
292
|
stack_description("CREATE_COMPLETE")
|
279
293
|
]
|
280
294
|
end
|
281
295
|
|
282
296
|
before do
|
283
|
-
cf_client.stub_responses(
|
297
|
+
cf_client.stub_responses(
|
298
|
+
:update_stack,
|
299
|
+
validation_error("Stack [woollyams-test] does not exist")
|
300
|
+
)
|
284
301
|
end
|
285
302
|
|
286
303
|
it "calls :delete_stack, then :create_stack first" do
|
@@ -294,6 +311,55 @@ describe Stackup::Stack do
|
|
294
311
|
end
|
295
312
|
end
|
296
313
|
|
314
|
+
context "when status is stable" do
|
315
|
+
|
316
|
+
before do
|
317
|
+
cf_client.stub_responses(
|
318
|
+
:cancel_update_stack,
|
319
|
+
validation_error("that cannot be called from current stack status")
|
320
|
+
)
|
321
|
+
end
|
322
|
+
|
323
|
+
describe "#cancel_update" do
|
324
|
+
|
325
|
+
it "returns nil" do
|
326
|
+
expect(stack.cancel_update).to be_nil
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
context "when status is UPDATE_IN_PROGRESS" do
|
334
|
+
|
335
|
+
let(:stack_status) { "UPDATE_IN_PROGRESS" }
|
336
|
+
|
337
|
+
describe "#cancel_update" do
|
338
|
+
|
339
|
+
let(:describe_stacks_responses) do
|
340
|
+
super() + [
|
341
|
+
stack_description("UPDATE_ROLLBACK_IN_PROGRESS"),
|
342
|
+
stack_description("UPDATE_ROLLBACK_COMPLETE")
|
343
|
+
]
|
344
|
+
end
|
345
|
+
|
346
|
+
it "calls :cancel_update_stack" do
|
347
|
+
expected_args = {
|
348
|
+
:stack_name => stack_name
|
349
|
+
}
|
350
|
+
stack.cancel_update
|
351
|
+
expect(cf_client).to have_received(:cancel_update_stack)
|
352
|
+
.with(hash_including(expected_args))
|
353
|
+
end
|
354
|
+
|
355
|
+
it "returns :update_cancelled" do
|
356
|
+
expect(stack.cancel_update).to eq(:update_cancelled)
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
297
363
|
end
|
298
364
|
|
299
365
|
end
|