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