kitchen-cloudformation 0.0.1

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