test-kitchen 1.0.0.beta.4 → 1.0.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -1
- data/Gemfile +1 -1
- data/README.md +18 -7
- data/Rakefile +8 -1
- data/features/kitchen_init_command.feature +90 -11
- data/features/step_definitions/git_steps.rb +3 -0
- data/lib/kitchen/busser.rb +79 -45
- data/lib/kitchen/cli.rb +14 -13
- data/lib/kitchen/config.rb +79 -138
- data/lib/kitchen/data_munger.rb +224 -0
- data/lib/kitchen/driver/base.rb +4 -31
- data/lib/kitchen/driver/ssh_base.rb +6 -16
- data/lib/kitchen/driver.rb +4 -0
- data/lib/kitchen/generator/init.rb +20 -9
- data/lib/kitchen/instance.rb +53 -58
- data/lib/kitchen/lazy_hash.rb +50 -0
- data/lib/kitchen/platform.rb +2 -31
- data/lib/kitchen/provisioner/base.rb +55 -9
- data/lib/kitchen/provisioner/chef/berkshelf.rb +76 -0
- data/lib/kitchen/provisioner/chef/librarian.rb +72 -0
- data/lib/kitchen/provisioner/chef_base.rb +159 -78
- data/lib/kitchen/provisioner/chef_solo.rb +6 -36
- data/lib/kitchen/provisioner/chef_zero.rb +70 -59
- data/lib/kitchen/provisioner/dummy.rb +28 -0
- data/lib/kitchen/provisioner.rb +6 -4
- data/lib/kitchen/shell_out.rb +2 -5
- data/lib/kitchen/ssh.rb +1 -1
- data/lib/kitchen/suite.rb +10 -79
- data/lib/kitchen/util.rb +2 -2
- data/lib/kitchen/version.rb +2 -2
- data/lib/kitchen.rb +5 -0
- data/spec/kitchen/config_spec.rb +84 -123
- data/spec/kitchen/data_munger_spec.rb +1412 -0
- data/spec/kitchen/driver/base_spec.rb +30 -0
- data/spec/kitchen/instance_spec.rb +868 -86
- data/spec/kitchen/lazy_hash_spec.rb +63 -0
- data/spec/kitchen/platform_spec.rb +0 -22
- data/spec/kitchen/provisioner/base_spec.rb +210 -0
- data/spec/kitchen/provisioner_spec.rb +70 -0
- data/spec/kitchen/suite_spec.rb +25 -38
- data/spec/spec_helper.rb +1 -0
- data/support/chef-client-zero.rb +51 -35
- data/support/dummy-validation.pem +27 -0
- data/templates/init/kitchen.yml.erb +10 -22
- data/test-kitchen.gemspec +1 -2
- metadata +20 -18
data/lib/kitchen/driver/base.rb
CHANGED
@@ -18,6 +18,8 @@
|
|
18
18
|
|
19
19
|
require 'thor/util'
|
20
20
|
|
21
|
+
require 'kitchen/lazy_hash'
|
22
|
+
|
21
23
|
module Kitchen
|
22
24
|
|
23
25
|
module Driver
|
@@ -37,7 +39,7 @@ module Kitchen
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def initialize(config = {})
|
40
|
-
@config =
|
42
|
+
@config = LazyHash.new(config, self)
|
41
43
|
self.class.defaults.each do |attr, value|
|
42
44
|
@config[attr] = value unless @config.has_key?(attr)
|
43
45
|
end
|
@@ -162,8 +164,7 @@ module Kitchen
|
|
162
164
|
def busser
|
163
165
|
@busser ||= begin
|
164
166
|
raise ClientError, "Instance must be set for Driver" if instance.nil?
|
165
|
-
|
166
|
-
Busser.new(instance.suite.name, config)
|
167
|
+
instance.busser
|
167
168
|
end
|
168
169
|
end
|
169
170
|
|
@@ -213,34 +214,6 @@ module Kitchen
|
|
213
214
|
@serial_actions ||= []
|
214
215
|
@serial_actions += methods
|
215
216
|
end
|
216
|
-
|
217
|
-
# A modifed Hash object that may contain procs as a value which must be
|
218
|
-
# executed in the context of a Driver object.
|
219
|
-
#
|
220
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
221
|
-
class LazyDriverHash < SimpleDelegator
|
222
|
-
|
223
|
-
def initialize(obj, driver)
|
224
|
-
@driver = driver
|
225
|
-
super(obj)
|
226
|
-
end
|
227
|
-
|
228
|
-
def [](key)
|
229
|
-
proc_or_val = __getobj__[key]
|
230
|
-
|
231
|
-
if proc_or_val.respond_to?(:call)
|
232
|
-
proc_or_val.call(@driver)
|
233
|
-
else
|
234
|
-
proc_or_val
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def to_hash
|
239
|
-
hash = Hash.new
|
240
|
-
__getobj__.keys.each { |key| hash[key] = self[key] }
|
241
|
-
hash
|
242
|
-
end
|
243
|
-
end
|
244
217
|
end
|
245
218
|
end
|
246
219
|
end
|
@@ -36,12 +36,13 @@ module Kitchen
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def converge(state)
|
39
|
-
provisioner =
|
39
|
+
provisioner = instance.provisioner
|
40
|
+
sandbox_dirs = Dir.glob("#{provisioner.create_sandbox}/*")
|
40
41
|
|
41
42
|
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
|
42
43
|
run_remote(provisioner.install_command, conn)
|
43
44
|
run_remote(provisioner.init_command, conn)
|
44
|
-
transfer_path(
|
45
|
+
transfer_path(sandbox_dirs, provisioner[:root_path], conn)
|
45
46
|
run_remote(provisioner.prepare_command, conn)
|
46
47
|
run_remote(provisioner.run_command, conn)
|
47
48
|
end
|
@@ -78,12 +79,6 @@ module Kitchen
|
|
78
79
|
|
79
80
|
protected
|
80
81
|
|
81
|
-
def new_provisioner
|
82
|
-
combined = config.dup
|
83
|
-
combined[:log_level] = Util.from_logger_level(logger.level)
|
84
|
-
Provisioner.for_plugin(combined[:provisioner], instance, combined)
|
85
|
-
end
|
86
|
-
|
87
82
|
def build_ssh_args(state)
|
88
83
|
combined = config.to_hash.merge(state)
|
89
84
|
|
@@ -104,11 +99,6 @@ module Kitchen
|
|
104
99
|
env << " http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
|
105
100
|
env << " https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
|
106
101
|
|
107
|
-
additional_paths = []
|
108
|
-
additional_paths << config[:ruby_binpath] if config[:ruby_binpath]
|
109
|
-
additional_paths << config[:path] if config[:path]
|
110
|
-
env << " PATH=$PATH:#{additional_paths.join(':')}" if additional_paths.any?
|
111
|
-
|
112
102
|
env == "env" ? cmd : "#{env} #{cmd}"
|
113
103
|
end
|
114
104
|
|
@@ -120,10 +110,10 @@ module Kitchen
|
|
120
110
|
raise ActionFailed, ex.message
|
121
111
|
end
|
122
112
|
|
123
|
-
def transfer_path(
|
124
|
-
return if
|
113
|
+
def transfer_path(locals, remote, connection)
|
114
|
+
return if locals.nil? || Array(locals).empty?
|
125
115
|
|
126
|
-
connection.upload_path!(local, remote)
|
116
|
+
locals.each { |local| connection.upload_path!(local, remote) }
|
127
117
|
rescue SSHFailed, Net::SSH::Exception => ex
|
128
118
|
raise ActionFailed, ex.message
|
129
119
|
end
|
data/lib/kitchen/driver.rb
CHANGED
@@ -26,9 +26,13 @@ module Kitchen
|
|
26
26
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
27
27
|
module Driver
|
28
28
|
|
29
|
+
# Default driver plugin to use
|
30
|
+
DEFAULT_PLUGIN = "dummy".freeze
|
31
|
+
|
29
32
|
# Returns an instance of a driver given a plugin type string.
|
30
33
|
#
|
31
34
|
# @param plugin [String] a driver plugin type, which will be constantized
|
35
|
+
# @param config [Hash] a configuration hash to initialize the driver
|
32
36
|
# @return [Driver::Base] a driver instance
|
33
37
|
# @raise [ClientError] if a driver instance could not be created
|
34
38
|
# @raise [UserError] if the driver's dependencies could not be met
|
@@ -37,6 +37,12 @@ module Kitchen
|
|
37
37
|
Gemfile
|
38
38
|
D
|
39
39
|
|
40
|
+
class_option :provisioner, :type => :string, :aliases => "-P",
|
41
|
+
:default => "chef_solo",
|
42
|
+
:desc => <<-D.gsub(/^\s+/, '').gsub(/\n/, ' ')
|
43
|
+
The default Kitchen Provisioner to use
|
44
|
+
D
|
45
|
+
|
40
46
|
class_option :create_gemfile, :type => :boolean, :default => false,
|
41
47
|
:desc => <<-D.gsub(/^\s+/, '').gsub(/\n/, ' ')
|
42
48
|
Whether or not to create a Gemfile if one does not exist.
|
@@ -50,8 +56,10 @@ module Kitchen
|
|
50
56
|
prepare_rakefile if init_rakefile?
|
51
57
|
prepare_thorfile if init_thorfile?
|
52
58
|
empty_directory "test/integration/default" if init_test_dir?
|
53
|
-
|
54
|
-
|
59
|
+
if init_git?
|
60
|
+
append_to_gitignore(".kitchen/")
|
61
|
+
append_to_gitignore(".kitchen.local.yml")
|
62
|
+
end
|
55
63
|
prepare_gemfile if init_gemfile?
|
56
64
|
add_drivers
|
57
65
|
|
@@ -68,11 +76,12 @@ module Kitchen
|
|
68
76
|
else
|
69
77
|
nil
|
70
78
|
end
|
71
|
-
run_list = cookbook_name ? "recipe[#{cookbook_name}]" : nil
|
79
|
+
run_list = cookbook_name ? "recipe[#{cookbook_name}::default]" : nil
|
72
80
|
driver_plugin = Array(options[:driver]).first || 'dummy'
|
73
81
|
|
74
82
|
template("kitchen.yml.erb", ".kitchen.yml", {
|
75
83
|
:driver_plugin => driver_plugin.sub(/^kitchen-/, ''),
|
84
|
+
:provisioner => options[:provisioner],
|
76
85
|
:run_list => Array(run_list)
|
77
86
|
})
|
78
87
|
end
|
@@ -96,6 +105,10 @@ module Kitchen
|
|
96
105
|
Dir.glob("test/integration/*").select { |d| File.directory?(d) }.empty?
|
97
106
|
end
|
98
107
|
|
108
|
+
def init_git?
|
109
|
+
File.directory?(File.join(destination_root, '.git'))
|
110
|
+
end
|
111
|
+
|
99
112
|
def prepare_rakefile
|
100
113
|
rakedoc = <<-RAKE.gsub(/^ {10}/, '')
|
101
114
|
|
@@ -142,9 +155,8 @@ module Kitchen
|
|
142
155
|
end
|
143
156
|
|
144
157
|
def add_gem_to_gemfile
|
145
|
-
if not_in_file?("Gemfile", %r{gem 'test-kitchen'})
|
146
|
-
append_to_file("Gemfile",
|
147
|
-
%{gem 'test-kitchen', :group => :integration\n})
|
158
|
+
if not_in_file?("Gemfile", %r{gem ('|")test-kitchen('|")})
|
159
|
+
append_to_file("Gemfile", %{gem 'test-kitchen'\n})
|
148
160
|
@display_bundle_msg = true
|
149
161
|
end
|
150
162
|
end
|
@@ -163,9 +175,8 @@ module Kitchen
|
|
163
175
|
end
|
164
176
|
|
165
177
|
def add_driver_to_gemfile(driver_gem)
|
166
|
-
if not_in_file?("Gemfile", %r{gem '#{driver_gem}'})
|
167
|
-
append_to_file("Gemfile",
|
168
|
-
%{gem '#{driver_gem}', :group => :integration\n})
|
178
|
+
if not_in_file?("Gemfile", %r{gem ('|")#{driver_gem}('|")})
|
179
|
+
append_to_file("Gemfile", %{gem '#{driver_gem}'\n})
|
169
180
|
@display_bundle_msg = true
|
170
181
|
end
|
171
182
|
end
|
data/lib/kitchen/instance.rb
CHANGED
@@ -18,7 +18,6 @@
|
|
18
18
|
|
19
19
|
require 'benchmark'
|
20
20
|
require 'fileutils'
|
21
|
-
require 'vendor/hash_recursive_merge'
|
22
21
|
|
23
22
|
module Kitchen
|
24
23
|
|
@@ -33,6 +32,10 @@ module Kitchen
|
|
33
32
|
|
34
33
|
class << self
|
35
34
|
attr_accessor :mutexes
|
35
|
+
|
36
|
+
def name_for(suite, platform)
|
37
|
+
"#{suite.name}-#{platform.name}".gsub(/_/, '-').gsub(/\./, '')
|
38
|
+
end
|
36
39
|
end
|
37
40
|
|
38
41
|
# @return [Suite] the test suite configuration
|
@@ -41,38 +44,53 @@ module Kitchen
|
|
41
44
|
# @return [Platform] the target platform configuration
|
42
45
|
attr_reader :platform
|
43
46
|
|
47
|
+
# @return [String] name of this instance
|
48
|
+
attr_reader :name
|
49
|
+
|
44
50
|
# @return [Driver::Base] driver object which will manage this instance's
|
45
51
|
# lifecycle actions
|
46
52
|
attr_reader :driver
|
47
53
|
|
54
|
+
# @return [Provisioner::Base] provisioner object which will the setup
|
55
|
+
# and invocation instructions for configuration management and other
|
56
|
+
# automation tools
|
57
|
+
attr_reader :provisioner
|
58
|
+
|
59
|
+
# @return [Busser] busser object for instance to manage the busser
|
60
|
+
# installation on this instance
|
61
|
+
attr_reader :busser
|
62
|
+
|
48
63
|
# @return [Logger] the logger for this instance
|
49
64
|
attr_reader :logger
|
50
65
|
|
51
66
|
# Creates a new instance, given a suite and a platform.
|
52
67
|
#
|
53
68
|
# @param [Hash] options configuration for a new suite
|
54
|
-
# @option options [Suite] :suite the suite
|
55
|
-
# @option options [Platform] :platform the platform
|
56
|
-
# @option options [Driver::Base] :driver the driver
|
69
|
+
# @option options [Suite] :suite the suite (**Required)
|
70
|
+
# @option options [Platform] :platform the platform (**Required)
|
71
|
+
# @option options [Driver::Base] :driver the driver (**Required)
|
72
|
+
# @option options [Provisioner::Base] :provisioner the provisioner
|
73
|
+
# (**Required)
|
74
|
+
# @option options [Busser] :busser the busser logger (**Required**)
|
57
75
|
# @option options [Logger] :logger the instance logger
|
76
|
+
# (default: Kitchen.logger)
|
77
|
+
# @option options [StateFile] :state_file the state file object to use
|
78
|
+
# when tracking instance state (**Required**)
|
79
|
+
# @raise [ClientError] if one or more required options are omitted
|
58
80
|
def initialize(options = {})
|
59
|
-
options = { :logger => Kitchen.logger }.merge(options)
|
60
81
|
validate_options(options)
|
61
|
-
logger = options[:logger]
|
62
|
-
|
63
|
-
@suite = options[:suite]
|
64
|
-
@platform = options[:platform]
|
65
|
-
@driver = options[:driver]
|
66
|
-
@logger = logger.is_a?(Proc) ? logger.call(name) : logger
|
67
82
|
|
68
|
-
@
|
69
|
-
@
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
83
|
+
@suite = options.fetch(:suite)
|
84
|
+
@platform = options.fetch(:platform)
|
85
|
+
@name = self.class.name_for(@suite, @platform)
|
86
|
+
@driver = options.fetch(:driver)
|
87
|
+
@provisioner = options.fetch(:provisioner)
|
88
|
+
@busser = options.fetch(:busser)
|
89
|
+
@logger = options.fetch(:logger) { Kitchen.logger }
|
90
|
+
@state_file = options.fetch(:state_file)
|
91
|
+
|
92
|
+
setup_driver
|
93
|
+
setup_provisioner
|
76
94
|
end
|
77
95
|
|
78
96
|
def to_str
|
@@ -179,45 +197,22 @@ module Kitchen
|
|
179
197
|
state_file.read[:last_action]
|
180
198
|
end
|
181
199
|
|
182
|
-
|
183
|
-
# run list, node attributes, etc.
|
184
|
-
module Cheflike
|
185
|
-
|
186
|
-
# Returns a combined run_list starting with the platform's run_list
|
187
|
-
# followed by the suite's run_list.
|
188
|
-
#
|
189
|
-
# @return [Array] combined run_list from suite and platform
|
190
|
-
def run_list
|
191
|
-
Array(platform.run_list) + Array(suite.run_list)
|
192
|
-
end
|
193
|
-
|
194
|
-
# Returns a merged hash of Chef node attributes with values from the
|
195
|
-
# suite overriding values from the platform.
|
196
|
-
#
|
197
|
-
# @return [Hash] merged hash of Chef node attributes
|
198
|
-
def attributes
|
199
|
-
platform.attributes.rmerge(suite.attributes)
|
200
|
-
end
|
201
|
-
|
202
|
-
def dna
|
203
|
-
attributes.rmerge({ :run_list => run_list })
|
204
|
-
end
|
205
|
-
end
|
200
|
+
private
|
206
201
|
|
207
|
-
|
208
|
-
# node attributes, etc.
|
209
|
-
module Puppetlike
|
202
|
+
attr_reader :state_file
|
210
203
|
|
211
|
-
|
204
|
+
def validate_options(options)
|
205
|
+
[:suite, :platform, :driver, :provisioner, :busser, :state_file].each do |k|
|
206
|
+
if !options.has_key?(k)
|
207
|
+
raise ClientError, "Instance#new requires option :#{k}"
|
208
|
+
end
|
212
209
|
end
|
213
210
|
end
|
214
211
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
raise ClientError, "Instance#new requires option :#{k}" if opts[k].nil?
|
220
|
-
end
|
212
|
+
def setup_driver
|
213
|
+
@driver.instance = self
|
214
|
+
@driver.validate_config!
|
215
|
+
setup_driver_mutex
|
221
216
|
end
|
222
217
|
|
223
218
|
def setup_driver_mutex
|
@@ -229,6 +224,10 @@ module Kitchen
|
|
229
224
|
end
|
230
225
|
end
|
231
226
|
|
227
|
+
def setup_provisioner
|
228
|
+
@provisioner.instance = self
|
229
|
+
end
|
230
|
+
|
232
231
|
def transition_to(desired)
|
233
232
|
result = nil
|
234
233
|
FSM.actions(last_action, desired).each do |transition|
|
@@ -258,7 +257,7 @@ module Kitchen
|
|
258
257
|
end
|
259
258
|
|
260
259
|
def perform_action(verb, output_verb)
|
261
|
-
banner "#{output_verb} #{to_str}"
|
260
|
+
banner "#{output_verb} #{to_str}..."
|
262
261
|
elapsed = action(verb) { |state| driver.public_send(verb, state) }
|
263
262
|
info("Finished #{output_verb.downcase} #{to_str}" +
|
264
263
|
" #{Util.duration(elapsed.real)}.")
|
@@ -298,10 +297,6 @@ module Kitchen
|
|
298
297
|
end
|
299
298
|
end
|
300
299
|
|
301
|
-
def state_file
|
302
|
-
@state_file ||= StateFile.new(driver[:kitchen_root], name)
|
303
|
-
end
|
304
|
-
|
305
300
|
def banner(*args)
|
306
301
|
Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args)
|
307
302
|
super
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013, Fletcher Nichol
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require 'delegate'
|
20
|
+
|
21
|
+
module Kitchen
|
22
|
+
|
23
|
+
# A modifed Hash object that may contain procs as a value which must be
|
24
|
+
# executed in the context of another object.
|
25
|
+
#
|
26
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
27
|
+
class LazyHash < SimpleDelegator
|
28
|
+
|
29
|
+
def initialize(obj, context)
|
30
|
+
@context = context
|
31
|
+
super(obj)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
proc_or_val = __getobj__[key]
|
36
|
+
|
37
|
+
if proc_or_val.respond_to?(:call)
|
38
|
+
proc_or_val.call(@context)
|
39
|
+
else
|
40
|
+
proc_or_val
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_hash
|
45
|
+
hash = Hash.new
|
46
|
+
__getobj__.keys.each { |key| hash[key] = self[key] }
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/kitchen/platform.rb
CHANGED
@@ -34,37 +34,8 @@ module Kitchen
|
|
34
34
|
# @option options [String] :name logical name of this platform
|
35
35
|
# (**Required**)
|
36
36
|
def initialize(options = {})
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@name = options.delete(:name)
|
41
|
-
@data = options.reject do |key, value|
|
42
|
-
[:driver_config, :driver_plugin, :provisioner].include?(key)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Extra platform methods used for accessing Chef data such as a run list,
|
47
|
-
# node attributes, etc.
|
48
|
-
module Cheflike
|
49
|
-
|
50
|
-
# @return [Array] Array of Chef run_list items
|
51
|
-
def run_list
|
52
|
-
Array(data[:run_list])
|
53
|
-
end
|
54
|
-
|
55
|
-
# @return [Hash] Hash of Chef node attributes
|
56
|
-
def attributes
|
57
|
-
data[:attributes] || Hash.new
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
attr_reader :data
|
64
|
-
|
65
|
-
def validate_options(opts)
|
66
|
-
[:name].each do |k|
|
67
|
-
raise ClientError, "Platform#new requires option :#{k}" if opts[k].nil?
|
37
|
+
@name = options.fetch(:name) do
|
38
|
+
raise ClientError, "Platform#new requires option :name"
|
68
39
|
end
|
69
40
|
end
|
70
41
|
end
|