test-kitchen 1.0.0.beta.4 → 1.0.0.rc.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.
- 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
|