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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6891d8c196ba95d50531fbb357b0202854d4b63e
4
- data.tar.gz: aebccfc91fa225fad525b43639a5ddb850fd6d17
3
+ metadata.gz: f659bed0985a19bcd7e5eba0dbe15caba286f37f
4
+ data.tar.gz: 7ccbe3c1e21ff7185f7d3d2da75ebb52f10b4926
5
5
  SHA512:
6
- metadata.gz: 1322158e69d5b0c67d1adad03e1ec6d6017c4cf3b37e842a6b1563a8af74daf63179767ad3b6bae417777d6accf8789995f29a72bb66166a1fda30c75089f6e1
7
- data.tar.gz: 4d70a45467995a1885d8c212c13386adac17e5ed09de73a33f6ffbc1037eaa46867beb44aecb4d06222d7312ec046fcec5af228b67283284ee12ba21ea4b43c9
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: chef_zero
125
+ name: Cloudformation
111
126
 
112
127
  platforms:
113
- - name: centos-6.4
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 = 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.4.0'.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,14 +1,14 @@
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.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-09-21 00:00:00.000000000 Z
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