kitchen-cloudformation 0.0.2 → 1.0.3

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.
@@ -1,24 +1,24 @@
1
- # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'kitchen/driver/cloudformation_version.rb'
5
-
6
- Gem::Specification.new do |gem|
7
- gem.name = 'kitchen-cloudformation'
8
- gem.version = Kitchen::Driver::CLOUDFORMATION_VERSION
9
- gem.license = 'Apache 2.0'
10
- gem.authors = ['Neill Turner']
11
- gem.email = ['neillwturner@gmail.com']
12
- gem.description = 'A Test Kitchen Driver for Amazon AWS CloudFormation'
13
- gem.summary = gem.description
14
- gem.homepage = 'https://github.com/neillturner/kitchen-cloudformation'
15
- candidates = Dir.glob('{lib}/**/*') + ['README.md', 'CHANGELOG.md', 'LICENSE', 'ca-bundle.crt', 'kitchen-cloudformation.gemspec']
16
- gem.files = candidates.sort
17
- gem.executables = []
18
- gem.require_paths = ['lib']
19
- gem.add_dependency 'test-kitchen', '~> 1.4'
20
- gem.add_dependency 'excon'
21
- gem.add_dependency 'multi_json'
22
- gem.add_dependency 'aws-sdk-v1', '~> 1.59.0'
23
- gem.add_dependency 'aws-sdk', '~> 2'
24
- end
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kitchen/driver/cloudformation_version.rb'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'kitchen-cloudformation'
8
+ gem.version = Kitchen::Driver::CLOUDFORMATION_VERSION
9
+ gem.license = 'Apache 2.0'
10
+ gem.authors = ['Neill Turner']
11
+ gem.email = ['neillwturner@gmail.com']
12
+ gem.description = 'A Test Kitchen Driver for Amazon AWS CloudFormation'
13
+ gem.summary = gem.description
14
+ gem.homepage = 'https://github.com/neillturner/kitchen-cloudformation'
15
+ candidates = Dir.glob('{lib}/**/*') + ['README.md', 'CHANGELOG.md', 'LICENSE', 'ca-bundle.crt', 'kitchen-cloudformation.gemspec']
16
+ gem.files = candidates.sort
17
+ gem.executables = []
18
+ gem.require_paths = ['lib']
19
+ gem.add_dependency 'test-kitchen', '~> 1.4'
20
+ gem.add_dependency 'excon'
21
+ gem.add_dependency 'multi_json'
22
+ gem.add_dependency 'aws-sdk-v1', '~> 1.59.0'
23
+ gem.add_dependency 'aws-sdk', '~> 2'
24
+ end
@@ -1,101 +1,101 @@
1
- # -*- encoding: utf-8 -*-
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 'aws-sdk'
17
- require 'aws-sdk-core/credentials'
18
- require 'aws-sdk-core/shared_credentials'
19
- require 'aws-sdk-core/instance_profile_credentials'
20
-
21
- module Kitchen
22
- module Driver
23
- class Aws
24
- #
25
- # A class for creating and managing the Cloud Formation client connection
26
- #
27
- class CfClient
28
- def initialize(
29
- region,
30
- profile_name = nil,
31
- ssl_cert_file = nil,
32
- aws_key = {},
33
- session_token = nil
34
- )
35
- access_key_id = aws_key[:access_key_id]
36
- secret_access_key = aws_key[:secret_access_key]
37
- creds = self.class.get_credentials(
38
- profile_name, access_key_id, secret_access_key, session_token
39
- )
40
-
41
- ::AWS.config(
42
- region: region,
43
- credentials: creds,
44
- ssl_ca_bundle: ssl_cert_file
45
- )
46
- end
47
-
48
- # Try and get the credentials from an ordered list of locations
49
- # http://docs.aws.amazon.com/sdkforruby/api/index.html#Configuration
50
- def self.get_credentials(profile_name, access_key_id, secret_access_key, session_token)
51
- shared_creds = ::Aws::SharedCredentials.new(profile_name: profile_name)
52
- if access_key_id && secret_access_key
53
- ::Aws::Credentials.new(access_key_id, secret_access_key, session_token)
54
- # TODO: these are deprecated, remove them in the next major version
55
- elsif ENV['AWS_ACCESS_KEY'] && ENV['AWS_SECRET_KEY']
56
- ::Aws::Credentials.new(
57
- ENV['AWS_ACCESS_KEY'],
58
- ENV['AWS_SECRET_KEY'],
59
- ENV['AWS_TOKEN']
60
- )
61
- elsif ENV['AWS_ACCESS_KEY_ID'] && ENV['AWS_SECRET_ACCESS_KEY']
62
- ::Aws::Credentials.new(
63
- ENV['AWS_ACCESS_KEY_ID'],
64
- ENV['AWS_SECRET_ACCESS_KEY'],
65
- ENV['AWS_SESSION_TOKEN']
66
- )
67
- elsif shared_creds.loadable?
68
- shared_creds
69
- else
70
- ::Aws::InstanceProfileCredentials.new(retries: 1)
71
- end
72
- end
73
-
74
- def create_stack(options)
75
- resource.create_stack(options)
76
- end
77
-
78
- def get_stack(stack_name)
79
- resource.stack(stack_name)
80
- end
81
-
82
- def get_stack_events(stack_name)
83
- client.describe_stack_events(stack_name: stack_name)
84
- end
85
-
86
- def delete_stack(stack_name)
87
- s = resource.stack(stack_name)
88
- s.delete
89
- end
90
-
91
- def client
92
- @client ||= ::Aws::CloudFormation::Client.new
93
- end
94
-
95
- def resource
96
- @resource ||= ::Aws::CloudFormation::Resource.new
97
- end
98
- end
99
- end
100
- end
101
- end
1
+ # -*- encoding: utf-8 -*-
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 'aws-sdk'
17
+ require 'aws-sdk-core/credentials'
18
+ require 'aws-sdk-core/shared_credentials'
19
+ require 'aws-sdk-core/instance_profile_credentials'
20
+
21
+ module Kitchen
22
+ module Driver
23
+ class Aws
24
+ #
25
+ # A class for creating and managing the Cloud Formation client connection
26
+ #
27
+ class CfClient
28
+ def initialize(
29
+ region,
30
+ profile_name = nil,
31
+ ssl_cert_file = nil,
32
+ aws_key = {},
33
+ session_token = nil
34
+ )
35
+ access_key_id = aws_key[:access_key_id]
36
+ secret_access_key = aws_key[:secret_access_key]
37
+ creds = self.class.get_credentials(
38
+ profile_name, access_key_id, secret_access_key, session_token
39
+ )
40
+
41
+ ::AWS.config(
42
+ region: region,
43
+ credentials: creds,
44
+ ssl_ca_bundle: ssl_cert_file
45
+ )
46
+ end
47
+
48
+ # Try and get the credentials from an ordered list of locations
49
+ # http://docs.aws.amazon.com/sdkforruby/api/index.html#Configuration
50
+ def self.get_credentials(profile_name, access_key_id, secret_access_key, session_token)
51
+ shared_creds = ::Aws::SharedCredentials.new(profile_name: profile_name)
52
+ if access_key_id && secret_access_key
53
+ ::Aws::Credentials.new(access_key_id, secret_access_key, session_token)
54
+ # TODO: these are deprecated, remove them in the next major version
55
+ elsif ENV['AWS_ACCESS_KEY'] && ENV['AWS_SECRET_KEY']
56
+ ::Aws::Credentials.new(
57
+ ENV['AWS_ACCESS_KEY'],
58
+ ENV['AWS_SECRET_KEY'],
59
+ ENV['AWS_TOKEN']
60
+ )
61
+ elsif ENV['AWS_ACCESS_KEY_ID'] && ENV['AWS_SECRET_ACCESS_KEY']
62
+ ::Aws::Credentials.new(
63
+ ENV['AWS_ACCESS_KEY_ID'],
64
+ ENV['AWS_SECRET_ACCESS_KEY'],
65
+ ENV['AWS_SESSION_TOKEN']
66
+ )
67
+ elsif shared_creds.loadable?
68
+ shared_creds
69
+ else
70
+ ::Aws::InstanceProfileCredentials.new(retries: 1)
71
+ end
72
+ end
73
+
74
+ def create_stack(options)
75
+ resource.create_stack(options)
76
+ end
77
+
78
+ def get_stack(stack_name)
79
+ resource.stack(stack_name)
80
+ end
81
+
82
+ def get_stack_events(stack_name)
83
+ client.describe_stack_events(stack_name: stack_name)
84
+ end
85
+
86
+ def delete_stack(stack_name)
87
+ s = resource.stack(stack_name)
88
+ s.delete
89
+ end
90
+
91
+ def client
92
+ @client ||= ::Aws::CloudFormation::Client.new
93
+ end
94
+
95
+ def resource
96
+ @resource ||= ::Aws::CloudFormation::Resource.new
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,53 +1,54 @@
1
- # -*- encoding: utf-8 -*-
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/logging'
17
-
18
- module Kitchen
19
- module Driver
20
- class Aws
21
- #
22
- # A class for encapsulating the stack payload logic
23
- #
24
- class StackGenerator
25
- include Logging
26
-
27
- attr_reader :config, :cf
28
-
29
- def initialize(config, cf)
30
- @config = config
31
- @cf = cf
32
- end
33
-
34
- # Transform the provided config into the hash to send to AWS. Some fields
35
- # can be passed in null, others need to be ommitted if they are null
36
- def cf_stack_data
37
- s = { stack_name: config[:stack_name] }
38
- s[:template_url] = config[:template_url] if config[:template_file]
39
- if config[:template_file]
40
- s[:template_body] = File.open(config[:template_file], 'rb') { |file| file.read }
41
- end
42
- s[:timeout_in_minutes] = config[:timeout_in_minutes] if !config[:timeout_in_minutes].nil? && config[:timeout_in_minutes] > 0
43
- s[:disable_rollback] = config[:disable_rollback] if !config[:disable_rollback].nil? && config[:disable_rollback] == true || config[:disable_rollback] == false
44
- s[:parameters] = []
45
- config[:parameters].each do |k, v|
46
- s[:parameters].push(parameter_key: k.to_s, parameter_value: v.to_s)
47
- end
48
- s
49
- end
50
- end
51
- end
52
- end
53
- end
1
+ # -*- encoding: utf-8 -*-
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/logging'
17
+
18
+ module Kitchen
19
+ module Driver
20
+ class Aws
21
+ #
22
+ # A class for encapsulating the stack payload logic
23
+ #
24
+ class StackGenerator
25
+ include Logging
26
+
27
+ attr_reader :config, :cf
28
+
29
+ def initialize(config, cf)
30
+ @config = config
31
+ @cf = cf
32
+ end
33
+
34
+ # Transform the provided config into the hash to send to AWS. Some fields
35
+ # can be passed in null, others need to be ommitted if they are null
36
+ def cf_stack_data
37
+ s = { stack_name: config[:stack_name] }
38
+ s[:template_url] = config[:template_url] if config[:template_file]
39
+ if config[:template_file]
40
+ s[:template_body] = File.open(config[:template_file], 'rb') { |file| file.read }
41
+ end
42
+ s[:capabilities] = config[:capabilities] if !config[:capabilities].nil? && (config[:capabilities].is_a? Array)
43
+ s[:timeout_in_minutes] = config[:timeout_in_minutes] if !config[:timeout_in_minutes].nil? && config[:timeout_in_minutes] > 0
44
+ s[:disable_rollback] = config[:disable_rollback] if !config[:disable_rollback].nil? && config[:disable_rollback] == true || config[:disable_rollback] == false
45
+ s[:parameters] = []
46
+ config[:parameters].each do |k, v|
47
+ s[:parameters].push(parameter_key: k.to_s, parameter_value: v.to_s)
48
+ end
49
+ s
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,172 +1,172 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- require 'benchmark'
16
- require 'json'
17
- require 'aws'
18
- require 'kitchen'
19
- require_relative 'cloudformation_version'
20
- require_relative 'aws/cf_client'
21
- require_relative 'aws/stack_generator'
22
- # require 'aws-sdk-core/waiters/errors'
23
-
24
- module Kitchen
25
- module Driver
26
- #
27
- # Amazon CloudFormation driver for Test Kitchen.
28
- #
29
- class CloudFormation < Kitchen::Driver::Base
30
- kitchen_driver_api_version 2
31
-
32
- plugin_version Kitchen::Driver::CLOUDFORMATION_VERSION
33
-
34
- default_config :region, ENV['AWS_REGION'] || 'us-east-1'
35
- default_config :shared_credentials_profile, nil
36
- default_config :aws_access_key_id, nil
37
- default_config :aws_secret_access_key, nil
38
- default_config :aws_session_token, nil
39
- default_config :ssl_cert_file, ENV['SSL_CERT_FILE']
40
- default_config :stack_name, nil
41
- default_config :template_file, nil
42
- default_config :parameters, {}
43
- default_config :disable_rollback, false
44
- default_config :timeout_in_minutes, 0
45
- default_config :parameters, {}
46
-
47
- default_config :ssh_key, nil
48
- default_config :username, 'root'
49
- default_config :hostname, nil
50
- # default_config :interface, nil
51
-
52
- required_config :ssh_key
53
- required_config :stack_name
54
-
55
- def create(state) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
56
- copy_deprecated_configs(state)
57
- return if state[:stack_name]
58
-
59
- info(Kitchen::Util.outdent!(<<-END))
60
- Creating CloudFormation Stack <#{config[:stack_name]}>...
61
- If you are not using an account that qualifies under the AWS
62
- free-tier, you may be charged to run these suites. The charge
63
- should be minimal, but neither Test Kitchen nor its maintainers
64
- are responsible for your incurred costs.
65
- END
66
- begin
67
- stack = create_stack
68
- rescue # Exception => e
69
- error("CloudFormation #{$ERROR_INFO}.") # e.message
70
- return
71
- end
72
- state[:stack_name] = stack.stack_name
73
- state[:hostname] = config[:hostname]
74
- info("Stack <#{state[:stack_name]}> requested.")
75
- # tag_stack(stack)
76
-
77
- s = cf.get_stack(state[:stack_name])
78
- while s.stack_status == 'CREATE_IN_PROGRESS'
79
- debug_stack_events(state[:stack_name])
80
- info("CloudFormation waiting for stack <#{state[:stack_name]}> to be created.....")
81
- sleep(30)
82
- s = cf.get_stack(state[:stack_name])
83
- end
84
- if s.stack_status == 'CREATE_COMPLETE'
85
- display_stack_events(state[:stack_name])
86
- info("CloudFormation stack <#{state[:stack_name]}> created.")
87
- else
88
- display_stack_events(state[:stack_name])
89
- error("CloudFormation stack <#{stack.stack_name}> failed to create....attempting to delete")
90
- destroy(state)
91
- end
92
- end
93
-
94
- def destroy(state)
95
- stack = cf.get_stack(state[:stack_name])
96
- if stack.nil?
97
- state.delete(:stack_name)
98
- else
99
- cf.delete_stack(state[:stack_name])
100
- begin
101
- stack = cf.get_stack(state[:stack_name])
102
- while stack.stack_status == 'DELETE_IN_PROGRESS'
103
- debug_stack_events(state[:stack_name])
104
- info("CloudFormation waiting for stack <#{state[:stack_name]}> to be deleted.....")
105
- sleep(30)
106
- stack = cf.get_stack(state[:stack_name])
107
- end
108
- rescue # Exception => e
109
- info("CloudFormation stack <#{state[:stack_name]}> deleted.")
110
- state.delete(:stack_name)
111
- return
112
- end
113
- display_stack_events(state[:stack_name])
114
- error("CloudFormation stack <#{stack.stack_name}> failed to deleted.")
115
- end
116
- end
117
-
118
- def cf
119
- @cf ||= Aws::CfClient.new(
120
- config[:region],
121
- config[:shared_credentials_profile],
122
- config[:ssl_cert_file],
123
- { access_key_id: config[:aws_access_key_id], secret_access_key: config[:aws_secret_access_key] },
124
- config[:aws_session_token]
125
- )
126
- end
127
-
128
- def stack_generator
129
- @stack_generator ||= Aws::StackGenerator.new(config, cf)
130
- end
131
-
132
- # This copies transport config from the current config object into the
133
- # state. This relies on logic in the transport that merges the transport
134
- # config with the current state object, so its a bad coupling. But we
135
- # can get rid of this when we get rid of these deprecated configs!
136
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
137
- def copy_deprecated_configs(state)
138
- state[:connection_timeout] = config[:ssh_timeout] if config[:ssh_timeout]
139
- state[:connection_retries] = config[:ssh_retries] if config[:ssh_retries]
140
- state[:username] = config[:username] if config[:username]
141
- # elsif instance.transport[:username] == instance.transport.class.defaults[:username]
142
- # If the transport has the default username, copy it from amis.json
143
- # This duplicated old behavior but I hate amis.json
144
- # ami_username = amis["usernames"][instance.platform.name]
145
- # state[:username] = ami_username if ami_username
146
- # end
147
- state[:ssh_key] = config[:ssh_key] if config[:ssh_key]
148
- end
149
-
150
- def create_stack
151
- stack_data = stack_generator.cf_stack_data
152
- info("Creating CloudFormation Stack #{stack_data[:stack_name]}")
153
- cf.create_stack(stack_data)
154
- end
155
-
156
- def debug_stack_events(stack_name)
157
- return unless logger.debug?
158
- response = cf.get_stack_events(stack_name)
159
- response[:stack_events].each do |r|
160
- debug("#{r[:timestamp]} #{r[:resource_type]} #{r[:logical_resource_id]} #{r[:resource_status]} #{r[:resource_status_reason]}")
161
- end
162
- end
163
-
164
- def display_stack_events(stack_name)
165
- response = cf.get_stack_events(stack_name)
166
- response[:stack_events].each do |r|
167
- info("#{r[:timestamp]} #{r[:resource_type]} #{r[:logical_resource_id]} #{r[:resource_status]} #{r[:resource_status_reason]}")
168
- end
169
- end
170
- end
171
- end
172
- end
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'benchmark'
16
+ require 'json'
17
+ require 'aws'
18
+ require 'kitchen'
19
+ require_relative 'cloudformation_version'
20
+ require_relative 'aws/cf_client'
21
+ require_relative 'aws/stack_generator'
22
+ # require 'aws-sdk-core/waiters/errors'
23
+
24
+ module Kitchen
25
+ module Driver
26
+ #
27
+ # Amazon Cloudformation driver for Test Kitchen.
28
+ #
29
+ class Cloudformation < Kitchen::Driver::Base
30
+ kitchen_driver_api_version 2
31
+
32
+ plugin_version Kitchen::Driver::CLOUDFORMATION_VERSION
33
+
34
+ default_config :region, ENV['AWS_REGION'] || 'us-east-1'
35
+ default_config :shared_credentials_profile, nil
36
+ default_config :aws_access_key_id, nil
37
+ default_config :aws_secret_access_key, nil
38
+ default_config :aws_session_token, nil
39
+ default_config :ssl_cert_file, ENV['SSL_CERT_FILE']
40
+ default_config :stack_name, nil
41
+ default_config :template_file, nil
42
+ default_config :capabilities, nil
43
+ default_config :parameters, {}
44
+ default_config :disable_rollback, false
45
+ default_config :timeout_in_minutes, 0
46
+ default_config :parameters, {}
47
+
48
+ default_config :ssh_key, nil
49
+ default_config :username, 'root'
50
+ default_config :hostname, nil
51
+ # default_config :interface, nil
52
+
53
+ required_config :ssh_key
54
+ required_config :stack_name
55
+
56
+ def create(state)
57
+ copy_deprecated_configs(state)
58
+ return if state[:stack_name]
59
+
60
+ info(Kitchen::Util.outdent!(<<-END))
61
+ Creating CloudFormation Stack <#{config[:stack_name]}>...
62
+ If you are not using an account that qualifies under the AWS
63
+ free-tier, you may be charged to run these suites. The charge
64
+ should be minimal, but neither Test Kitchen nor its maintainers
65
+ are responsible for your incurred costs.
66
+ END
67
+ begin
68
+ stack = create_stack
69
+ rescue # Exception => e
70
+ error("CloudFormation #{$ERROR_INFO}.") # e.message
71
+ return
72
+ end
73
+ state[:stack_name] = stack.stack_name
74
+ state[:hostname] = config[:hostname]
75
+ info("Stack <#{state[:stack_name]}> requested.")
76
+ # tag_stack(stack)
77
+
78
+ s = cf.get_stack(state[:stack_name])
79
+ while s.stack_status == 'CREATE_IN_PROGRESS'
80
+ debug_stack_events(state[:stack_name])
81
+ info("CloudFormation waiting for stack <#{state[:stack_name]}> to be created.....")
82
+ sleep(30)
83
+ s = cf.get_stack(state[:stack_name])
84
+ end
85
+ if s.stack_status == 'CREATE_COMPLETE'
86
+ display_stack_events(state[:stack_name])
87
+ info("CloudFormation stack <#{state[:stack_name]}> created.")
88
+ else
89
+ display_stack_events(state[:stack_name])
90
+ error("CloudFormation stack <#{stack.stack_name}> failed to create....attempting to delete")
91
+ destroy(state)
92
+ end
93
+ end
94
+
95
+ def destroy(state)
96
+ stack = cf.get_stack(state[:stack_name])
97
+ if stack.nil?
98
+ state.delete(:stack_name)
99
+ else
100
+ cf.delete_stack(state[:stack_name])
101
+ begin
102
+ stack = cf.get_stack(state[:stack_name])
103
+ while stack.stack_status == 'DELETE_IN_PROGRESS'
104
+ debug_stack_events(state[:stack_name])
105
+ info("CloudFormation waiting for stack <#{state[:stack_name]}> to be deleted.....")
106
+ sleep(30)
107
+ stack = cf.get_stack(state[:stack_name])
108
+ end
109
+ rescue # Exception => e
110
+ info("CloudFormation stack <#{state[:stack_name]}> deleted.")
111
+ state.delete(:stack_name)
112
+ return
113
+ end
114
+ display_stack_events(state[:stack_name])
115
+ error("CloudFormation stack <#{stack.stack_name}> failed to deleted.")
116
+ end
117
+ end
118
+
119
+ def cf
120
+ @cf ||= Aws::CfClient.new(
121
+ config[:region],
122
+ config[:shared_credentials_profile],
123
+ config[:ssl_cert_file],
124
+ { access_key_id: config[:aws_access_key_id], secret_access_key: config[:aws_secret_access_key] },
125
+ config[:aws_session_token]
126
+ )
127
+ end
128
+
129
+ def stack_generator
130
+ @stack_generator ||= Aws::StackGenerator.new(config, cf)
131
+ end
132
+
133
+ # This copies transport config from the current config object into the
134
+ # state. This relies on logic in the transport that merges the transport
135
+ # config with the current state object, so its a bad coupling. But we
136
+ # can get rid of this when we get rid of these deprecated configs!
137
+ def copy_deprecated_configs(state)
138
+ state[:connection_timeout] = config[:ssh_timeout] if config[:ssh_timeout]
139
+ state[:connection_retries] = config[:ssh_retries] if config[:ssh_retries]
140
+ state[:username] = config[:username] if config[:username]
141
+ # elsif instance.transport[:username] == instance.transport.class.defaults[:username]
142
+ # If the transport has the default username, copy it from amis.json
143
+ # This duplicated old behavior but I hate amis.json
144
+ # ami_username = amis["usernames"][instance.platform.name]
145
+ # state[:username] = ami_username if ami_username
146
+ # end
147
+ state[:ssh_key] = config[:ssh_key] if config[:ssh_key]
148
+ end
149
+
150
+ def create_stack
151
+ stack_data = stack_generator.cf_stack_data
152
+ info("Creating CloudFormation Stack #{stack_data[:stack_name]}")
153
+ cf.create_stack(stack_data)
154
+ end
155
+
156
+ def debug_stack_events(stack_name)
157
+ return unless logger.debug?
158
+ response = cf.get_stack_events(stack_name)
159
+ response[:stack_events].each do |r|
160
+ debug("#{r[:timestamp]} #{r[:resource_type]} #{r[:logical_resource_id]} #{r[:resource_status]} #{r[:resource_status_reason]}")
161
+ end
162
+ end
163
+
164
+ def display_stack_events(stack_name)
165
+ response = cf.get_stack_events(stack_name)
166
+ response[:stack_events].each do |r|
167
+ info("#{r[:timestamp]} #{r[:resource_type]} #{r[:logical_resource_id]} #{r[:resource_status]} #{r[:resource_status_reason]}")
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end