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 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