kitchen-cloudformation 0.0.1

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