kitchen-cloudformation 0.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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