kitchen-cloudformation 1.3.0 → 1.5.2

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
- SHA1:
3
- metadata.gz: 6891d8c196ba95d50531fbb357b0202854d4b63e
4
- data.tar.gz: aebccfc91fa225fad525b43639a5ddb850fd6d17
2
+ SHA256:
3
+ metadata.gz: 96f4545854d99212bb47aa812fbfedf485b674855f19ef36f5eff82548ca6a63
4
+ data.tar.gz: d3a7035c27afc23f3527d89d1ba0efcce65ceb265592e889dbcd30f1df60494c
5
5
  SHA512:
6
- metadata.gz: 1322158e69d5b0c67d1adad03e1ec6d6017c4cf3b37e842a6b1563a8af74daf63179767ad3b6bae417777d6accf8789995f29a72bb66166a1fda30c75089f6e1
7
- data.tar.gz: 4d70a45467995a1885d8c212c13386adac17e5ed09de73a33f6ffbc1037eaa46867beb44aecb4d06222d7312ec046fcec5af228b67283284ee12ba21ea4b43c9
6
+ metadata.gz: 923ed16c853ad13e22ab8b0eb39d66106b6cf81aee6646a4673a138d8bcdc5e55efb0f83e50e774900ec936924087594de98d838d899bb2808bf34d1e24f0d28
7
+ data.tar.gz: 5a274acd9f323d95b4bcc9744fd741bab91768dea2e831cb39755fd8d21973cc0dc3765e41a29c9982528c32a50e417d92cca622cc430d66f6b87bf72f85bd77
data/README.md CHANGED
@@ -12,7 +12,7 @@ If you wish to use servers specified as a hostname in the converge step then use
12
12
  So you can deploy and test say a Mongodb High Availability cluster by using cloud formation to create the servers
13
13
  and then converge each of the servers in the cluster and run tests.
14
14
 
15
- This can be used with [kitchen-verifier-awspec](https://github.com/neillturner/kitchen-verifier-awspec) to do verification of AWS infrastructure.
15
+ This can be used with [kitchen-inspec](https://github.com/chef/kitchen-inspec) or [kitchen-verifier-awspec](https://github.com/neillturner/kitchen-verifier-awspec) to do verification of AWS infrastructure.
16
16
 
17
17
  ## Requirements
18
18
 
@@ -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.
@@ -80,6 +83,22 @@ through CI we no longer recommend storing the AWS credentials in the
80
83
  `.kitchen.yml` file. Instead, specify them as environment variables or in the
81
84
  `~/.aws/credentials` file.
82
85
 
86
+ ## Change Sets
87
+
88
+ Normally kitchen-cloudformation creates the stack from the template file when the kitchen create command if run.
89
+
90
+ If you specify a change_set_name and a change_set_template_file
91
+
92
+ ```yaml
93
+ template_file: TestSecurityGroup.template
94
+ change_set_name: mystack-cs
95
+ change_set_template_file: TestSecurityGroupCs.template
96
+ ```
97
+
98
+ then it will also create the change set from the change_set_template_file.
99
+
100
+ Then when the kitchen converge is run it will apply the change-set so it can be tested.
101
+
83
102
 
84
103
  ## SSL Certificate File Issues
85
104
 
@@ -94,6 +113,16 @@ A file ca-bundle.crt is supplied inside this gem for this purpose so you can set
94
113
 
95
114
  ## Example
96
115
 
116
+ See example at https://github.com/neillturner/cloudformation_repo
117
+
118
+ kitchen create default-test -l debug
119
+
120
+ Create the stack if it does not exist and creates a change set if one is specified.
121
+
122
+ kitchen converge default-test -l debug
123
+
124
+ Executes the change set if one has been created
125
+
97
126
  The following could be used in a `.kitchen.yml` or in a `.kitchen.local.yml`
98
127
  to override default configuration.
99
128
 
@@ -105,12 +134,14 @@ driver:
105
134
  template_file: /test/example.template
106
135
  parameters:
107
136
  base_package: wget
137
+ change_set_name: mystack-cs
138
+ change_set_template_file: TestSecurityGroupCs.template
108
139
 
109
140
  provisioner:
110
- name: chef_zero
141
+ name: Cloudformation
111
142
 
112
143
  platforms:
113
- - name: centos-6.4
144
+ - name: test
114
145
  driver: Cloudformation
115
146
 
116
147
  suites:
@@ -16,9 +16,9 @@ Gem::Specification.new do |gem|
16
16
  gem.executables = []
17
17
  gem.require_paths = ['lib']
18
18
  gem.required_ruby_version = '>= 2.2.2'
19
- gem.add_dependency 'test-kitchen', '~> 1.4', '>= 1.4.1'
19
+ gem.add_dependency 'aws-sdk', '~> 3'
20
20
  gem.add_dependency 'excon'
21
21
  gem.add_dependency 'multi_json'
22
- gem.add_dependency 'aws-sdk', '~> 2'
23
22
  gem.add_dependency 'retryable', '~> 2.0'
23
+ gem.add_dependency 'test-kitchen', '>= 1.4.1'
24
24
  end
@@ -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
@@ -34,7 +34,7 @@ module Kitchen
34
34
  # can be passed in null, others need to be ommitted if they are null
35
35
  def cf_stack_data
36
36
  s = { stack_name: config[:stack_name] }
37
- s[:template_url] = config[:template_url] if config[:template_file]
37
+ s[:template_url] = config[:template_url] if config[:template_file].nil?
38
38
  if config[:template_file]
39
39
  s[:template_body] = File.open(config[:template_file], 'rb') { |file| file.read }
40
40
  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 = create_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 == 'CREATE_IN_PROGRESS'
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 created.....")
85
- sleep(30)
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 == 'CREATE_COMPLETE'
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]}> created.")
148
+ info("CloudFormation stack <#{state[:stack_name]}> updated.")
95
149
  else
96
- error("CloudFormation stack <#{stack.stack_name}> failed to create....attempting to delete")
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)
@@ -14,6 +14,6 @@
14
14
  module Kitchen
15
15
  module Driver
16
16
  # Version string for CloudFormation Test Kitchen driver
17
- CLOUDFORMATION_VERSION = '1.3.0'.freeze
17
+ CLOUDFORMATION_VERSION = '1.5.2'.freeze
18
18
  end
19
19
  end
@@ -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,35 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-cloudformation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.5.2
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-09-21 00:00:00.000000000 Z
11
+ date: 2021-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: test-kitchen
14
+ name: aws-sdk
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.4'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 1.4.1
19
+ version: '3'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: '1.4'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 1.4.1
26
+ version: '3'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: excon
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -59,33 +53,33 @@ dependencies:
59
53
  - !ruby/object:Gem::Version
60
54
  version: '0'
61
55
  - !ruby/object:Gem::Dependency
62
- name: aws-sdk
56
+ name: retryable
63
57
  requirement: !ruby/object:Gem::Requirement
64
58
  requirements:
65
59
  - - "~>"
66
60
  - !ruby/object:Gem::Version
67
- version: '2'
61
+ version: '2.0'
68
62
  type: :runtime
69
63
  prerelease: false
70
64
  version_requirements: !ruby/object:Gem::Requirement
71
65
  requirements:
72
66
  - - "~>"
73
67
  - !ruby/object:Gem::Version
74
- version: '2'
68
+ version: '2.0'
75
69
  - !ruby/object:Gem::Dependency
76
- name: retryable
70
+ name: test-kitchen
77
71
  requirement: !ruby/object:Gem::Requirement
78
72
  requirements:
79
- - - "~>"
73
+ - - ">="
80
74
  - !ruby/object:Gem::Version
81
- version: '2.0'
75
+ version: 1.4.1
82
76
  type: :runtime
83
77
  prerelease: false
84
78
  version_requirements: !ruby/object:Gem::Requirement
85
79
  requirements:
86
- - - "~>"
80
+ - - ">="
87
81
  - !ruby/object:Gem::Version
88
- version: '2.0'
82
+ version: 1.4.1
89
83
  description: A Test Kitchen Driver for Amazon AWS CloudFormation
90
84
  email:
91
85
  - neillwturner@gmail.com
@@ -102,6 +96,7 @@ files:
102
96
  - lib/kitchen/driver/aws/stack_generator.rb
103
97
  - lib/kitchen/driver/cloudformation.rb
104
98
  - lib/kitchen/driver/cloudformation_version.rb
99
+ - lib/kitchen/provisioner/cloudformation.rb
105
100
  homepage: https://github.com/neillturner/kitchen-cloudformation
106
101
  licenses:
107
102
  - Apache-2.0
@@ -121,8 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
116
  - !ruby/object:Gem::Version
122
117
  version: '0'
123
118
  requirements: []
124
- rubyforge_project:
125
- rubygems_version: 2.6.13
119
+ rubygems_version: 3.1.4
126
120
  signing_key:
127
121
  specification_version: 4
128
122
  summary: A Test Kitchen Driver for AWS CloudFormation