kitchen-cloudformation 1.3.0 → 1.4.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/README.md +17 -2
- data/kitchen-cloudformation.gemspec +1 -1
- data/lib/kitchen/driver/aws/cf_client.rb +22 -1
- data/lib/kitchen/driver/cloudformation.rb +106 -11
- data/lib/kitchen/driver/cloudformation_version.rb +1 -1
- data/lib/kitchen/provisioner/cloudformation.rb +47 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f659bed0985a19bcd7e5eba0dbe15caba286f37f
|
|
4
|
+
data.tar.gz: 7ccbe3c1e21ff7185f7d3d2da75ebb52f10b4926
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58f11f1fdc38766e6fe3135b81182acd388d170300a0f2cf0462ab09a1f0b1b892d18bb37f6e1ea47c1f65f2f94f8bb01c436b8c0af7884ff99ffcee0cbf4c5b
|
|
7
|
+
data.tar.gz: ea164a0d10ca0291244d7875002b7629de197cf6b6508a4091ba0b10be6346cfee6012b4d660af228662704c9c42ae15ce28efcef417341693cab121c741fe55
|
data/README.md
CHANGED
|
@@ -32,6 +32,9 @@ shared_credentials_profile| nil|Specify Credentials Using a Profile Name
|
|
|
32
32
|
key | default value | Notes
|
|
33
33
|
----|---------------|--------
|
|
34
34
|
capabilities||Array of capabilities that must be specified before creating or updating certain stacks accepts CAPABILITY_IAM, CAPABILITY_NAMED_IAM
|
|
35
|
+
change_set_name ||Name of the Cloud Formation Change Set to create and then execute at converge time
|
|
36
|
+
change_set_template_file||File containing the Cloudformation template to use to create the change set
|
|
37
|
+
change_set_type | UPDATE |Cloud Formation Change Set can be CREATE or UPDATE
|
|
35
38
|
disable_rollback||If the template gets an error don't rollback changes. true/false. default false.
|
|
36
39
|
notification_arns| [] |The Simple Notification Service (SNS) topic ARNs to publish stack related events. Array of Strings.
|
|
37
40
|
on_failure||Determines what action will be taken if stack creation fails. accepts DO_NOTHING, ROLLBACK, DELETE. You can specify either on_failure or disable_rollback, but not both.
|
|
@@ -94,6 +97,16 @@ A file ca-bundle.crt is supplied inside this gem for this purpose so you can set
|
|
|
94
97
|
|
|
95
98
|
## Example
|
|
96
99
|
|
|
100
|
+
See example at https://github.com/neillturner/cloudformation_repo
|
|
101
|
+
|
|
102
|
+
kitchen create default-test -l debug
|
|
103
|
+
|
|
104
|
+
Create the stack if it does not exist and creates a change set if one is specified.
|
|
105
|
+
|
|
106
|
+
kitchen converge default-test -l debug
|
|
107
|
+
|
|
108
|
+
Executes the change set if one has been created
|
|
109
|
+
|
|
97
110
|
The following could be used in a `.kitchen.yml` or in a `.kitchen.local.yml`
|
|
98
111
|
to override default configuration.
|
|
99
112
|
|
|
@@ -105,12 +118,14 @@ driver:
|
|
|
105
118
|
template_file: /test/example.template
|
|
106
119
|
parameters:
|
|
107
120
|
base_package: wget
|
|
121
|
+
change_set_name: mystack-cs
|
|
122
|
+
change_set_template_file: TestSecurityGroupCs.template
|
|
108
123
|
|
|
109
124
|
provisioner:
|
|
110
|
-
name:
|
|
125
|
+
name: Cloudformation
|
|
111
126
|
|
|
112
127
|
platforms:
|
|
113
|
-
- name:
|
|
128
|
+
- name: test
|
|
114
129
|
driver: Cloudformation
|
|
115
130
|
|
|
116
131
|
suites:
|
|
@@ -19,6 +19,6 @@ Gem::Specification.new do |gem|
|
|
|
19
19
|
gem.add_dependency 'test-kitchen', '~> 1.4', '>= 1.4.1'
|
|
20
20
|
gem.add_dependency 'excon'
|
|
21
21
|
gem.add_dependency 'multi_json'
|
|
22
|
-
gem.add_dependency 'aws-sdk', '~> 2'
|
|
22
|
+
gem.add_dependency 'aws-sdk-cloudformation', '~> 1.2'
|
|
23
23
|
gem.add_dependency 'retryable', '~> 2.0'
|
|
24
24
|
end
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
require 'aws-sdk'
|
|
15
|
+
require 'aws-sdk-cloudformation'
|
|
16
16
|
require 'aws-sdk-core/credentials'
|
|
17
17
|
require 'aws-sdk-core/shared_credentials'
|
|
18
18
|
require 'aws-sdk-core/instance_profile_credentials'
|
|
@@ -94,10 +94,31 @@ module Kitchen
|
|
|
94
94
|
resource.create_stack(options)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
def update_stack(options)
|
|
98
|
+
resource.update_stack(options)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def create_change_set(options)
|
|
102
|
+
client.create_change_set(options)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def execute_change_set(options)
|
|
106
|
+
client.execute_change_set(options)
|
|
107
|
+
end
|
|
108
|
+
|
|
97
109
|
def get_stack(stack_name)
|
|
98
110
|
resource.stack(stack_name)
|
|
99
111
|
end
|
|
100
112
|
|
|
113
|
+
# rubocop:disable Lint/RescueWithoutErrorClass
|
|
114
|
+
def describe_change_set(stack_name, change_set_name)
|
|
115
|
+
client.describe_change_set(change_set_name: change_set_name,
|
|
116
|
+
stack_name: stack_name)
|
|
117
|
+
rescue
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
# rubocop:enable Lint/RescueWithoutErrorClass
|
|
121
|
+
|
|
101
122
|
def get_stack_events(stack_name)
|
|
102
123
|
client.describe_stack_events(stack_name: stack_name)
|
|
103
124
|
end
|
|
@@ -37,12 +37,12 @@ module Kitchen
|
|
|
37
37
|
default_config :ssl_verify_peer, true
|
|
38
38
|
default_config :stack_name, nil
|
|
39
39
|
default_config :template_file, nil
|
|
40
|
+
|
|
40
41
|
default_config :capabilities, nil
|
|
41
42
|
default_config :parameters, {}
|
|
42
43
|
default_config :disable_rollback, nil
|
|
43
44
|
default_config :timeout_in_minutes, 0
|
|
44
45
|
default_config :parameters, {}
|
|
45
|
-
|
|
46
46
|
default_config :ssh_key, nil
|
|
47
47
|
default_config :username, 'root'
|
|
48
48
|
default_config :hostname, nil
|
|
@@ -53,13 +53,16 @@ module Kitchen
|
|
|
53
53
|
default_config :stack_policy_body, nil
|
|
54
54
|
default_config :stack_policy_url, nil
|
|
55
55
|
default_config :tags, {}
|
|
56
|
+
default_config :change_set_name, nil
|
|
57
|
+
default_config :change_set_type, 'UPDATE'
|
|
58
|
+
default_config :change_set_template_file, nil
|
|
56
59
|
|
|
57
60
|
required_config :stack_name
|
|
58
61
|
|
|
59
62
|
# rubocop:disable Lint/RescueWithoutErrorClass
|
|
60
63
|
def create(state)
|
|
61
64
|
copy_deprecated_configs(state)
|
|
62
|
-
return if state[:stack_name]
|
|
65
|
+
# return if state[:stack_name]
|
|
63
66
|
|
|
64
67
|
info(Kitchen::Util.outdent!(<<-TEXT))
|
|
65
68
|
Creating CloudFormation Stack <#{config[:stack_name]}>...
|
|
@@ -68,33 +71,83 @@ module Kitchen
|
|
|
68
71
|
should be minimal, but neither Test Kitchen nor its maintainers
|
|
69
72
|
are responsible for your incurred costs.
|
|
70
73
|
TEXT
|
|
74
|
+
unless stack_exists
|
|
75
|
+
begin
|
|
76
|
+
stack = create_stack
|
|
77
|
+
rescue
|
|
78
|
+
error("CloudFormation #{$ERROR_INFO}.") # e.message
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
state[:stack_name] = stack.stack_name unless state[:stack_name]
|
|
82
|
+
info("Stack <#{state[:stack_name]}> requested.")
|
|
83
|
+
# tag_stack(stack)
|
|
84
|
+
|
|
85
|
+
s = cf.get_stack(state[:stack_name])
|
|
86
|
+
while s.stack_status == 'CREATE_IN_PROGRESS'
|
|
87
|
+
debug_stack_events(state[:stack_name])
|
|
88
|
+
info("CloudFormation waiting for stack <#{state[:stack_name]}> to be created.....")
|
|
89
|
+
sleep(30)
|
|
90
|
+
s = cf.get_stack(state[:stack_name])
|
|
91
|
+
end
|
|
92
|
+
display_stack_events(state[:stack_name])
|
|
93
|
+
if s.stack_status == 'CREATE_COMPLETE'
|
|
94
|
+
outputs = Hash[*s.outputs.map do |o|
|
|
95
|
+
[o[:output_key], o[:output_value]]
|
|
96
|
+
end.flatten]
|
|
97
|
+
state[:hostname] = config[:hostname].gsub(/\${([^}]+)}/) { outputs[Regexp.last_match(1)] || '' } if config[:hostname]
|
|
98
|
+
info("CloudFormation stack <#{state[:stack_name]}> created.")
|
|
99
|
+
else
|
|
100
|
+
error("CloudFormation stack <#{stack.stack_name}> failed to create....attempting to delete")
|
|
101
|
+
destroy(state)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
return if change_set_exists
|
|
105
|
+
begin
|
|
106
|
+
create_change_set
|
|
107
|
+
rescue
|
|
108
|
+
error("CloudFormation #{$ERROR_INFO}.") # e.message
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
state[:stack_name] = config[:stack_name] unless state[:stack_name]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def update(state)
|
|
115
|
+
info("Stack <#{state[:stack_name]}> Execute Change Set requested to update stack.")
|
|
116
|
+
stack = cf.get_stack(state[:stack_name])
|
|
117
|
+
if stack.nil?
|
|
118
|
+
info("CloudFormation stack <#{state[:stack_name]}> doesn't exist.")
|
|
119
|
+
return
|
|
120
|
+
end
|
|
121
|
+
unless change_set_exists
|
|
122
|
+
info("CloudFormation change set <#{config[:change_set_name]}> doesn't exist for stack <#{state[:stack_name]}>.")
|
|
123
|
+
return
|
|
124
|
+
end
|
|
71
125
|
begin
|
|
72
|
-
stack =
|
|
126
|
+
stack = execute_change_set
|
|
73
127
|
rescue
|
|
74
128
|
error("CloudFormation #{$ERROR_INFO}.") # e.message
|
|
75
129
|
return
|
|
76
130
|
end
|
|
77
|
-
state[:stack_name] = stack.stack_name
|
|
131
|
+
state[:stack_name] = stack.stack_name unless state[:stack_name]
|
|
78
132
|
info("Stack <#{state[:stack_name]}> requested.")
|
|
79
133
|
# tag_stack(stack)
|
|
80
134
|
|
|
81
135
|
s = cf.get_stack(state[:stack_name])
|
|
82
|
-
while s.stack_status == '
|
|
136
|
+
while s.stack_status == 'UPDATE_IN_PROGRESS'
|
|
83
137
|
debug_stack_events(state[:stack_name])
|
|
84
|
-
info("CloudFormation waiting for stack <#{state[:stack_name]}> to be
|
|
85
|
-
sleep(
|
|
138
|
+
info("CloudFormation waiting for stack <#{state[:stack_name]}> to be updated.....")
|
|
139
|
+
sleep(20)
|
|
86
140
|
s = cf.get_stack(state[:stack_name])
|
|
87
141
|
end
|
|
88
142
|
display_stack_events(state[:stack_name])
|
|
89
|
-
if s.stack_status == '
|
|
143
|
+
if s.stack_status == 'UPDATE_COMPLETE'
|
|
90
144
|
outputs = Hash[*s.outputs.map do |o|
|
|
91
145
|
[o[:output_key], o[:output_value]]
|
|
92
146
|
end.flatten]
|
|
93
147
|
state[:hostname] = config[:hostname].gsub(/\${([^}]+)}/) { outputs[Regexp.last_match(1)] || '' } if config[:hostname]
|
|
94
|
-
info("CloudFormation stack <#{state[:stack_name]}>
|
|
148
|
+
info("CloudFormation stack <#{state[:stack_name]}> updated.")
|
|
95
149
|
else
|
|
96
|
-
error("CloudFormation stack <#{stack.stack_name}> failed to
|
|
97
|
-
destroy(state)
|
|
150
|
+
error("CloudFormation stack <#{stack.stack_name}> failed to update.")
|
|
98
151
|
end
|
|
99
152
|
end
|
|
100
153
|
|
|
@@ -163,6 +216,48 @@ module Kitchen
|
|
|
163
216
|
cf.create_stack(stack_data)
|
|
164
217
|
end
|
|
165
218
|
|
|
219
|
+
def stack_exists
|
|
220
|
+
s = cf.get_stack(config[:stack_name])
|
|
221
|
+
info("Stack #{s.stack_name} already exists so not creating") if s.exists?
|
|
222
|
+
s.exists?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def change_set_exists
|
|
226
|
+
s = cf.describe_change_set(config[:stack_name], config[:change_set_name])
|
|
227
|
+
if s.nil? || s.status == 'DELETE_COMPLETE'
|
|
228
|
+
false
|
|
229
|
+
else
|
|
230
|
+
info("Change Set #{s[:change_set_name]} already exists so not creating")
|
|
231
|
+
true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def create_change_set
|
|
236
|
+
stack_data = stack_generator.cf_stack_data
|
|
237
|
+
stack_data[:change_set_name] = config[:change_set_name] if config[:change_set_name]
|
|
238
|
+
stack_data[:change_set_type] = config[:change_set_type] if config[:change_set_type] # accepts CREATE, UPDATE
|
|
239
|
+
info("Creating Change Set for CloudFormation Stack #{stack_data[:stack_name]}")
|
|
240
|
+
stack_data[:template_url] = config[:change_set_template_url] if config[:change_set_template_file]
|
|
241
|
+
if config[:change_set_template_file]
|
|
242
|
+
stack_data[:template_body] = File.open(config[:change_set_template_file], 'rb') { |file| file.read }
|
|
243
|
+
end
|
|
244
|
+
cf.create_change_set(stack_data)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def execute_change_set
|
|
248
|
+
stack_data = {}
|
|
249
|
+
stack_data[:stack_name] = config[:stack_name]
|
|
250
|
+
stack_data[:change_set_name] = config[:change_set_name]
|
|
251
|
+
info("Execute Change Set #{stack_data[:change_set_name]} for CloudFormation Stack #{stack_data[:stack_name]}")
|
|
252
|
+
cf.execute_change_set(stack_data)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def update_stack
|
|
256
|
+
stack_data = stack_generator.cf_stack_data
|
|
257
|
+
info("Updating CloudFormation Stack #{stack_data[:stack_name]}")
|
|
258
|
+
cf.update_stack(stack_data)
|
|
259
|
+
end
|
|
260
|
+
|
|
166
261
|
def debug_stack_events(stack_name)
|
|
167
262
|
return unless logger.debug?
|
|
168
263
|
response = cf.get_stack_events(stack_name)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
require 'kitchen'
|
|
17
|
+
# require "kitchen/cloudformation/configurable"
|
|
18
|
+
|
|
19
|
+
# The design of the provisioner is unconventional compared to other Test Kitchen provisioner plugins. Since Cloudformation
|
|
20
|
+
# creates and provisions resources when applying an execution plan, managed by the driver, the provisioner simply
|
|
21
|
+
# proxies the driver's create action to apply any changes to the existing Cloudformation state.
|
|
22
|
+
#
|
|
23
|
+
# === Configuration
|
|
24
|
+
#
|
|
25
|
+
# ==== Example .kitchen.yml snippet
|
|
26
|
+
#
|
|
27
|
+
# provisioner:
|
|
28
|
+
# name: cloudformation
|
|
29
|
+
#
|
|
30
|
+
# @see ::Kitchen::Driver::Cloudformation
|
|
31
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
|
32
|
+
class ::Kitchen::Provisioner::Cloudformation < ::Kitchen::Provisioner::Base
|
|
33
|
+
kitchen_provisioner_api_version 2
|
|
34
|
+
|
|
35
|
+
# include ::Kitchen::Cloudformation::Configurable
|
|
36
|
+
|
|
37
|
+
# Proxies the driver's create action.
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# `kitchen converge suite-name`
|
|
41
|
+
# @param state [::Hash] the mutable instance and provisioner state.
|
|
42
|
+
# @raise [::Kitchen::ActionFailed] if the result of the action is a failure.
|
|
43
|
+
def call(state)
|
|
44
|
+
info("State is <#{state}>.")
|
|
45
|
+
instance.driver.update state
|
|
46
|
+
end
|
|
47
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-cloudformation
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neill Turner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-10-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: test-kitchen
|
|
@@ -59,19 +59,19 @@ dependencies:
|
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
60
|
version: '0'
|
|
61
61
|
- !ruby/object:Gem::Dependency
|
|
62
|
-
name: aws-sdk
|
|
62
|
+
name: aws-sdk-cloudformation
|
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '2'
|
|
67
|
+
version: '1.2'
|
|
68
68
|
type: :runtime
|
|
69
69
|
prerelease: false
|
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '2'
|
|
74
|
+
version: '1.2'
|
|
75
75
|
- !ruby/object:Gem::Dependency
|
|
76
76
|
name: retryable
|
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -102,6 +102,7 @@ files:
|
|
|
102
102
|
- lib/kitchen/driver/aws/stack_generator.rb
|
|
103
103
|
- lib/kitchen/driver/cloudformation.rb
|
|
104
104
|
- lib/kitchen/driver/cloudformation_version.rb
|
|
105
|
+
- lib/kitchen/provisioner/cloudformation.rb
|
|
105
106
|
homepage: https://github.com/neillturner/kitchen-cloudformation
|
|
106
107
|
licenses:
|
|
107
108
|
- Apache-2.0
|