test-kitchen 2.7.1 → 2.11.0
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/Gemfile +4 -10
- data/Rakefile +0 -22
- data/lib/kitchen.rb +1 -3
- data/lib/kitchen/chef_utils_wiring.rb +40 -0
- data/lib/kitchen/cli.rb +2 -2
- data/lib/kitchen/command.rb +0 -2
- data/lib/kitchen/command/diagnose.rb +3 -3
- data/lib/kitchen/config.rb +8 -5
- data/lib/kitchen/lifecycle_hook/base.rb +78 -0
- data/lib/kitchen/lifecycle_hook/local.rb +53 -0
- data/lib/kitchen/lifecycle_hook/remote.rb +33 -0
- data/lib/kitchen/lifecycle_hooks.rb +26 -78
- data/lib/kitchen/loader/yaml.rb +1 -0
- data/lib/kitchen/provisioner/base.rb +5 -0
- data/lib/kitchen/provisioner/chef/policyfile.rb +33 -41
- data/lib/kitchen/provisioner/chef_base.rb +1 -1
- data/lib/kitchen/transport/exec.rb +90 -4
- data/lib/kitchen/transport/ssh.rb +8 -10
- data/lib/kitchen/transport/winrm.rb +10 -6
- data/lib/kitchen/util.rb +11 -1
- data/lib/kitchen/verifier/busser.rb +1 -1
- data/lib/kitchen/verifier/shell.rb +1 -0
- data/lib/kitchen/version.rb +1 -1
- data/lib/kitchen/which.rb +26 -0
- data/test-kitchen.gemspec +3 -2
- metadata +23 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36e340a7aefc9e4512f05ded9b43003298a3466de705d7c6c6050dd07f433175
|
4
|
+
data.tar.gz: 310ae63b637cbd62a88cd5beef8effa437f0d1b2dec6bf9098ba3112e621c8ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc3e9bf0e55d519d757e4339b4e643f038216dedb573225aed9cc1ce2972ad121e7819db643e26e4038ebe5fc05e2bc0a57435588d462bf2d2a4a198cbd0b9b1
|
7
|
+
data.tar.gz: 48c9bc01e69691e4096f5ae219638b0d996f069852c3ac14ec143ce583c3f6f610263000e5b24256b3b969314c9903e95b570372c1d3a8d3298d3ba4c65885bd
|
data/Gemfile
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem"s dependencies in test-kitchen.gemspec
|
2
4
|
gemspec
|
3
5
|
|
4
6
|
group :integration do
|
@@ -8,10 +10,6 @@ group :integration do
|
|
8
10
|
gem "kitchen-vagrant"
|
9
11
|
end
|
10
12
|
|
11
|
-
group :changelog do
|
12
|
-
gem "github_changelog_generator", "1.15.2"
|
13
|
-
end
|
14
|
-
|
15
13
|
group :debug do
|
16
14
|
gem "pry"
|
17
15
|
gem "pry-byebug"
|
@@ -19,9 +17,5 @@ group :debug do
|
|
19
17
|
end
|
20
18
|
|
21
19
|
group :chefstyle do
|
22
|
-
gem "chefstyle"
|
23
|
-
end
|
24
|
-
|
25
|
-
group :docs do
|
26
|
-
gem "yard"
|
27
|
-
end
|
20
|
+
gem "chefstyle", "1.6.2"
|
21
|
+
end
|
data/Rakefile
CHANGED
@@ -41,30 +41,8 @@ end
|
|
41
41
|
desc "Run all quality tasks"
|
42
42
|
task quality: %i{style stats}
|
43
43
|
|
44
|
-
begin
|
45
|
-
require "yard" unless defined?(YARD)
|
46
|
-
YARD::Rake::YardocTask.new
|
47
|
-
rescue LoadError
|
48
|
-
puts "yard is not available. (sudo) gem install yard to generate yard documentation."
|
49
|
-
end
|
50
|
-
|
51
44
|
task default: %i{test quality}
|
52
45
|
|
53
|
-
begin
|
54
|
-
require "github_changelog_generator/task"
|
55
|
-
require "kitchen/version"
|
56
|
-
|
57
|
-
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
58
|
-
config.future_release = "v#{Kitchen::VERSION}"
|
59
|
-
config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature,Improvement".split(",")
|
60
|
-
config.bug_labels = "bug,Bug".split(",")
|
61
|
-
config.exclude_labels = %w{Duplicate Question Discussion No_Changelog}
|
62
|
-
end
|
63
|
-
rescue LoadError
|
64
|
-
puts "github_changelog_generator is not available." \
|
65
|
-
" (sudo) gem install github_changelog_generator to generate changelogs"
|
66
|
-
end
|
67
|
-
|
68
46
|
namespace :docs do
|
69
47
|
desc "Deploy docs"
|
70
48
|
task :deploy do
|
data/lib/kitchen.rb
CHANGED
@@ -16,8 +16,6 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
18
|
require "pathname" unless defined?(Pathname)
|
19
|
-
require "thread"
|
20
|
-
|
21
19
|
require_relative "kitchen/errors"
|
22
20
|
require_relative "kitchen/logger"
|
23
21
|
require_relative "kitchen/logging"
|
@@ -67,7 +65,7 @@ module Kitchen
|
|
67
65
|
#
|
68
66
|
# @return [Pathname] root path of gem
|
69
67
|
def source_root
|
70
|
-
@source_root ||= Pathname.new(File.expand_path("
|
68
|
+
@source_root ||= Pathname.new(File.expand_path("..", __dir__))
|
71
69
|
end
|
72
70
|
|
73
71
|
# Returns a default logger which emits on standard output.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
# Common Dependency Injection wiring for ChefUtils-related modules
|
21
|
+
module ChefUtilsWiring
|
22
|
+
private
|
23
|
+
|
24
|
+
def __config
|
25
|
+
# this would need to be some kind of Chef::Config looking thing, which probably requires
|
26
|
+
# a translation object from t-k config to Chef::Config layout if that ever becomes necessary.
|
27
|
+
# this ISNT the t-k config.
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
|
31
|
+
def __log
|
32
|
+
@logger
|
33
|
+
end
|
34
|
+
|
35
|
+
def __transport_connection
|
36
|
+
# this could be wired up to train at some point, but need to be careful because about local vs. remote
|
37
|
+
# uses of helpers with test-kitchen, right now we're using it for local.
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/kitchen/cli.rb
CHANGED
@@ -16,7 +16,7 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
18
|
# CI tests fail without an explicit unconditional require of Thor
|
19
|
-
require "thor" # rubocop:disable
|
19
|
+
require "thor" # rubocop:disable Chef/Ruby/UnlessDefinedRequire
|
20
20
|
|
21
21
|
require_relative "../kitchen"
|
22
22
|
require_relative "generator/init"
|
@@ -162,7 +162,7 @@ module Kitchen
|
|
162
162
|
long_desc <<-DESC
|
163
163
|
The instance states are in order: destroy, create, converge, setup, verify, destroy.
|
164
164
|
Change one or more instances from the current state to the #{action} state. Actions for all
|
165
|
-
intermediate states will be executed. See
|
165
|
+
intermediate states will be executed. See https://kitchen.ci/ for further explanation.
|
166
166
|
DESC
|
167
167
|
method_option :concurrency,
|
168
168
|
aliases: "-c",
|
data/lib/kitchen/command.rb
CHANGED
@@ -18,7 +18,7 @@
|
|
18
18
|
require_relative "../command"
|
19
19
|
require_relative "../diagnostic"
|
20
20
|
|
21
|
-
|
21
|
+
autoload :YAML, "yaml"
|
22
22
|
|
23
23
|
module Kitchen
|
24
24
|
module Command
|
@@ -32,9 +32,9 @@ module Kitchen
|
|
32
32
|
|
33
33
|
loader = record_failure { load_loader }
|
34
34
|
|
35
|
-
puts Kitchen::Diagnostic.new(
|
35
|
+
puts YAML.dump(Kitchen::Diagnostic.new(
|
36
36
|
loader: loader, instances: instances, plugins: plugins?
|
37
|
-
).read
|
37
|
+
).read)
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
data/lib/kitchen/config.rb
CHANGED
@@ -213,7 +213,7 @@ module Kitchen
|
|
213
213
|
provisioner: Provisioner::DEFAULT_PLUGIN,
|
214
214
|
verifier: Verifier::DEFAULT_PLUGIN,
|
215
215
|
transport: lambda do |_suite, platform|
|
216
|
-
|
216
|
+
/^win/i.match?(platform) ? "winrm" : Transport::DEFAULT_PLUGIN
|
217
217
|
end,
|
218
218
|
},
|
219
219
|
kitchen_root: kitchen_root,
|
@@ -244,16 +244,18 @@ module Kitchen
|
|
244
244
|
# @return [Instance] a new Instance object
|
245
245
|
# @api private
|
246
246
|
def new_instance(suite, platform, index)
|
247
|
+
sf = new_state_file(suite, platform)
|
248
|
+
|
247
249
|
Instance.new(
|
248
250
|
driver: new_driver(suite, platform),
|
249
|
-
lifecycle_hooks: new_lifecycle_hooks(suite, platform),
|
251
|
+
lifecycle_hooks: new_lifecycle_hooks(suite, platform, sf),
|
250
252
|
logger: new_instance_logger(suite, platform, index),
|
251
253
|
suite: suite,
|
252
254
|
platform: platform,
|
253
255
|
provisioner: new_provisioner(suite, platform),
|
254
256
|
transport: new_transport(suite, platform),
|
255
257
|
verifier: new_verifier(suite, platform),
|
256
|
-
state_file:
|
258
|
+
state_file: sf
|
257
259
|
)
|
258
260
|
end
|
259
261
|
|
@@ -284,11 +286,12 @@ module Kitchen
|
|
284
286
|
#
|
285
287
|
# @param suite [Suite,#name] a Suite
|
286
288
|
# @param platform [Platform,#name] a Platform
|
289
|
+
# @param state_file [Kitchen::StateFile] a SateFile
|
287
290
|
# @return [LifecycleHooks] a new LifecycleHooks object
|
288
291
|
# @api private
|
289
|
-
def new_lifecycle_hooks(suite, platform)
|
292
|
+
def new_lifecycle_hooks(suite, platform, state_file)
|
290
293
|
lhdata = data.lifecycle_hooks_data_for(suite.name, platform.name)
|
291
|
-
LifecycleHooks.new(lhdata)
|
294
|
+
LifecycleHooks.new(lhdata, state_file)
|
292
295
|
end
|
293
296
|
|
294
297
|
# Builds a newly configured Provisioner object, for a given Suite and
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Kitchen
|
2
|
+
class LifecycleHook
|
3
|
+
class Base
|
4
|
+
# @return [Kitchen::LifecycleHooks]
|
5
|
+
attr_reader :lifecycle_hooks
|
6
|
+
|
7
|
+
# return [String]
|
8
|
+
attr_reader :phase
|
9
|
+
|
10
|
+
# return [Hash]
|
11
|
+
attr_reader :hook
|
12
|
+
|
13
|
+
# @param lifecycle_hooks [Kitchen::LifecycleHooks]
|
14
|
+
# @param phase [String]
|
15
|
+
# @param hook [Hash]
|
16
|
+
def initialize(lifecycle_hooks, phase, hook)
|
17
|
+
@lifecycle_hooks = lifecycle_hooks
|
18
|
+
@phase = phase
|
19
|
+
@hook = hook
|
20
|
+
end
|
21
|
+
|
22
|
+
# return [void]
|
23
|
+
def run
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [TrueClass, FalseClass]
|
28
|
+
def should_run?
|
29
|
+
if !includes.empty?
|
30
|
+
includes.include?(platform_name)
|
31
|
+
elsif !excludes.empty?
|
32
|
+
!excludes.include?(platform_name)
|
33
|
+
else
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Logger] the lifecycle hooks's logger
|
39
|
+
# otherwise
|
40
|
+
# @api private
|
41
|
+
def logger
|
42
|
+
lifecycle_hooks.send(:logger)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [Kitchen::Instance]
|
48
|
+
def instance
|
49
|
+
lifecycle_hooks.instance
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Hash]
|
53
|
+
def config
|
54
|
+
lifecycle_hooks.send(:config)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Kitchen::StateFile]
|
58
|
+
def state_file
|
59
|
+
lifecycle_hooks.state_file
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Array<String>] names of excluded platforms
|
63
|
+
def excludes
|
64
|
+
@excludes ||= hook.fetch(:excludes, [])
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Array<String>] names of only included platforms
|
68
|
+
def includes
|
69
|
+
@includes ||= hook.fetch(:includes, [])
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [String]
|
73
|
+
def platform_name
|
74
|
+
instance.platform.name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require_relative "../shell_out"
|
3
|
+
require_relative "../logging"
|
4
|
+
|
5
|
+
module Kitchen
|
6
|
+
class LifecycleHook
|
7
|
+
class Local < Base
|
8
|
+
include ShellOut
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
# Execute a specific local command hook.
|
12
|
+
#
|
13
|
+
# @return [void]
|
14
|
+
def run
|
15
|
+
state = state_file.read
|
16
|
+
# set up empty user variable
|
17
|
+
user = {}
|
18
|
+
# Set up some environment variables with instance info.
|
19
|
+
environment = {
|
20
|
+
"KITCHEN_INSTANCE_NAME" => instance.name,
|
21
|
+
"KITCHEN_SUITE_NAME" => instance.suite.name,
|
22
|
+
"KITCHEN_PLATFORM_NAME" => instance.platform.name,
|
23
|
+
"KITCHEN_INSTANCE_HOSTNAME" => state[:hostname].to_s,
|
24
|
+
}
|
25
|
+
# If the user specified env vars too, fix them up because symbol keys
|
26
|
+
# make mixlib-shellout sad.
|
27
|
+
hook[:environment]&.each do |k, v|
|
28
|
+
environment[k.to_s] = v.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
# add user to user hash for later merging
|
32
|
+
user[:user] = hook[:user] if hook[:user]
|
33
|
+
|
34
|
+
# Default the cwd to the kitchen root and resolve a relative input cwd against that.
|
35
|
+
cwd = if hook[:cwd]
|
36
|
+
File.expand_path(hook[:cwd], config[:kitchen_root])
|
37
|
+
else
|
38
|
+
config[:kitchen_root]
|
39
|
+
end
|
40
|
+
# Build the options for mixlib-shellout.
|
41
|
+
opts = {}.merge(user).merge(cwd: cwd, environment: environment)
|
42
|
+
run_command(command, opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [String]
|
48
|
+
def command
|
49
|
+
hook.fetch(:local)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require_relative "../errors"
|
3
|
+
|
4
|
+
module Kitchen
|
5
|
+
class LifecycleHook
|
6
|
+
class Remote < Base
|
7
|
+
# Execute a specific remote command hook.
|
8
|
+
#
|
9
|
+
# @return [void]
|
10
|
+
def run
|
11
|
+
# Check if we're in a state that makes sense to even try.
|
12
|
+
unless instance.last_action
|
13
|
+
if hook[:skippable]
|
14
|
+
# Just not even trying.
|
15
|
+
return
|
16
|
+
else
|
17
|
+
raise UserError, "Cannot use remote lifecycle hooks during phases when the instance is not available"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
conn = instance.transport.connection(state_file.read)
|
22
|
+
conn.execute(command)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# return [String]
|
28
|
+
def command
|
29
|
+
hook.fetch(:remote)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -15,8 +15,11 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
|
+
require_relative "configurable"
|
18
19
|
require_relative "errors"
|
19
|
-
require_relative "
|
20
|
+
require_relative "lifecycle_hook/local"
|
21
|
+
require_relative "lifecycle_hook/remote"
|
22
|
+
require_relative "logging"
|
20
23
|
|
21
24
|
module Kitchen
|
22
25
|
# A helper object used by {Instance} to coordinate lifecycle hook calls from
|
@@ -27,10 +30,10 @@ module Kitchen
|
|
27
30
|
class LifecycleHooks
|
28
31
|
include Configurable
|
29
32
|
include Logging
|
30
|
-
include ShellOut
|
31
33
|
|
32
|
-
def initialize(config)
|
34
|
+
def initialize(config, state_file)
|
33
35
|
init_config(config)
|
36
|
+
@state_file = state_file
|
34
37
|
end
|
35
38
|
|
36
39
|
# Run a lifecycle phase with the pre and post hooks.
|
@@ -40,21 +43,22 @@ module Kitchen
|
|
40
43
|
# @param block [Proc] Block of code implementing the lifecycle phase.
|
41
44
|
# @return [void]
|
42
45
|
def run_with_hooks(phase, state_file, &block)
|
43
|
-
run(
|
46
|
+
run(phase, :pre)
|
44
47
|
yield
|
45
|
-
run(
|
48
|
+
run(phase, :post)
|
46
49
|
end
|
47
50
|
|
51
|
+
# @return [Kitchen::StateFile]
|
52
|
+
attr_reader :state_file
|
53
|
+
|
48
54
|
private
|
49
55
|
|
50
56
|
# Execute a specific lifecycle hook.
|
51
57
|
#
|
52
|
-
# @param instance [Instance] The instance object to run against.
|
53
58
|
# @param phase [String] Lifecycle phase which is being executed.
|
54
|
-
# @param state_file [StateFile] Instance state file object.
|
55
59
|
# @param hook_timing [Symbol] `:pre` or `:post` to indicate which hook to run.
|
56
60
|
# @return [void]
|
57
|
-
def run(
|
61
|
+
def run(phase, hook_timing)
|
58
62
|
# Yes this has to be a symbol because of how data munger works.
|
59
63
|
hook_key = :"#{hook_timing}_#{phase}"
|
60
64
|
# No hooks? We're outta here.
|
@@ -65,80 +69,24 @@ module Kitchen
|
|
65
69
|
# Coerce the common case of a bare string to be a local command. This
|
66
70
|
# is to match the behavior of the old `pre_create_command` semi-hook.
|
67
71
|
hook = { local: hook } if hook.is_a?(String)
|
68
|
-
|
69
|
-
|
70
|
-
run_local_hook(instance, state_file, hook)
|
71
|
-
elsif hook.include?(:remote)
|
72
|
-
# Remote command execution on the test instance.
|
73
|
-
run_remote_hook(instance, state_file, hook)
|
74
|
-
else
|
75
|
-
raise UserError, "Unknown lifecycle hook target #{hook.inspect}"
|
76
|
-
end
|
72
|
+
hook = generate_hook(phase, hook)
|
73
|
+
hook.run if hook.should_run?
|
77
74
|
end
|
78
75
|
end
|
79
76
|
|
80
|
-
#
|
81
|
-
#
|
82
|
-
# @
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
environment = {
|
93
|
-
"KITCHEN_INSTANCE_NAME" => instance.name,
|
94
|
-
"KITCHEN_SUITE_NAME" => instance.suite.name,
|
95
|
-
"KITCHEN_PLATFORM_NAME" => instance.platform.name,
|
96
|
-
"KITCHEN_INSTANCE_HOSTNAME" => state[:hostname].to_s,
|
97
|
-
}
|
98
|
-
# If the user specified env vars too, fix them up because symbol keys
|
99
|
-
# make mixlib-shellout sad.
|
100
|
-
if hook[:environment]
|
101
|
-
hook[:environment].each do |k, v|
|
102
|
-
environment[k.to_s] = v.to_s
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# add user to user hash for later merging
|
107
|
-
if hook[:user]
|
108
|
-
user[:user] = hook[:user]
|
77
|
+
# @param phase [String]
|
78
|
+
# @param hook [Hash]
|
79
|
+
# @return [Kitchen::LifecycleHook::Local, Kitchen::LifecycleHook::Remote]
|
80
|
+
def generate_hook(phase, hook)
|
81
|
+
if hook.include?(:local)
|
82
|
+
# Local command execution on the workstation.
|
83
|
+
Kitchen::LifecycleHook::Local.new(self, phase, hook)
|
84
|
+
elsif hook.include?(:remote)
|
85
|
+
# Remote command execution on the test instance.
|
86
|
+
Kitchen::LifecycleHook::Remote.new(self, phase, hook)
|
87
|
+
else
|
88
|
+
raise UserError, "Unknown lifecycle hook target #{hook.inspect}"
|
109
89
|
end
|
110
|
-
|
111
|
-
# Default the cwd to the kitchen root and resolve a relative input cwd against that.
|
112
|
-
cwd = if hook[:cwd]
|
113
|
-
File.expand_path(hook[:cwd], config[:kitchen_root])
|
114
|
-
else
|
115
|
-
config[:kitchen_root]
|
116
|
-
end
|
117
|
-
# Build the options for mixlib-shellout.
|
118
|
-
opts = {}.merge(user).merge(cwd: cwd, environment: environment)
|
119
|
-
run_command(cmd, opts)
|
120
|
-
end
|
121
|
-
|
122
|
-
# Execute a specific remote command hook.
|
123
|
-
#
|
124
|
-
# @param instance [Instance] The instance object to run against.
|
125
|
-
# @param state_file [StateFile] Instance state file object.
|
126
|
-
# @param hook [Hash] Hook configration to use.
|
127
|
-
# @return [void]
|
128
|
-
def run_remote_hook(instance, state_file, hook)
|
129
|
-
# Check if we're in a state that makes sense to even try.
|
130
|
-
unless instance.last_action
|
131
|
-
if hook[:skippable]
|
132
|
-
# Just not even trying.
|
133
|
-
return
|
134
|
-
else
|
135
|
-
raise UserError, "Cannot use remote lifecycle hooks during phases when the instance is not available"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
cmd = hook.fetch(:remote)
|
140
|
-
conn = instance.transport.connection(state_file.read)
|
141
|
-
conn.execute(cmd)
|
142
90
|
end
|
143
91
|
end
|
144
92
|
end
|
data/lib/kitchen/loader/yaml.rb
CHANGED
@@ -51,6 +51,7 @@ module Kitchen
|
|
51
51
|
|
52
52
|
default_config :command_prefix, nil
|
53
53
|
|
54
|
+
default_config :uploads, {}
|
54
55
|
default_config :downloads, {}
|
55
56
|
|
56
57
|
expand_path_for :test_base_path
|
@@ -72,6 +73,10 @@ module Kitchen
|
|
72
73
|
sandbox_dirs = Util.list_directory(sandbox_path)
|
73
74
|
|
74
75
|
instance.transport.connection(state) do |conn|
|
76
|
+
config[:uploads].to_h.each do |locals, remote|
|
77
|
+
debug("Uploading #{Array(locals).join(", ")} to #{remote}")
|
78
|
+
conn.upload(locals.to_s, remote)
|
79
|
+
end
|
75
80
|
conn.execute(install_command)
|
76
81
|
conn.execute(init_command)
|
77
82
|
info("Transferring files to #{instance.to_str}")
|
@@ -21,6 +21,7 @@ require "rbconfig" unless defined?(RbConfig)
|
|
21
21
|
require_relative "../../errors"
|
22
22
|
require_relative "../../logging"
|
23
23
|
require_relative "../../shell_out"
|
24
|
+
require_relative "../../which"
|
24
25
|
|
25
26
|
module Kitchen
|
26
27
|
module Provisioner
|
@@ -31,6 +32,7 @@ module Kitchen
|
|
31
32
|
class Policyfile
|
32
33
|
include Logging
|
33
34
|
include ShellOut
|
35
|
+
include Which
|
34
36
|
|
35
37
|
# Creates a new cookbook resolver.
|
36
38
|
#
|
@@ -40,9 +42,9 @@ module Kitchen
|
|
40
42
|
# @param logger [Kitchen::Logger] a logger to use for output, defaults
|
41
43
|
# to `Kitchen.logger`
|
42
44
|
def initialize(policyfile, path, logger: Kitchen.logger, always_update: false)
|
43
|
-
@policyfile
|
44
|
-
@path
|
45
|
-
@logger
|
45
|
+
@policyfile = policyfile
|
46
|
+
@path = path
|
47
|
+
@logger = logger
|
46
48
|
@always_update = always_update
|
47
49
|
end
|
48
50
|
|
@@ -51,30 +53,29 @@ module Kitchen
|
|
51
53
|
# @param logger [Kitchen::Logger] a logger to use for output, defaults
|
52
54
|
# to `Kitchen.logger`
|
53
55
|
def self.load!(logger: Kitchen.logger)
|
54
|
-
|
56
|
+
# intentionally left blank
|
55
57
|
end
|
56
58
|
|
57
59
|
# Performs the cookbook resolution and vendors the resulting cookbooks
|
58
60
|
# in the desired path.
|
59
61
|
def resolve
|
60
|
-
info("Exporting cookbook dependencies from Policyfile #{path}
|
61
|
-
run_command("
|
62
|
+
info("Exporting cookbook dependencies from Policyfile #{path} using `#{cli_path} export`...")
|
63
|
+
run_command("#{cli_path} export #{escape_path(policyfile)} #{escape_path(path)} --force")
|
62
64
|
end
|
63
65
|
|
64
66
|
# Runs `chef install` to determine the correct cookbook set and
|
65
67
|
# generate the policyfile lock.
|
66
68
|
def compile
|
67
69
|
if File.exist?(lockfile)
|
68
|
-
info("Installing cookbooks for Policyfile #{policyfile} using
|
70
|
+
info("Installing cookbooks for Policyfile #{policyfile} using `#{cli_path} install`")
|
69
71
|
else
|
70
|
-
info("Policy lock file doesn't exist, running
|
71
|
-
"Policyfile #{policyfile}...")
|
72
|
+
info("Policy lock file doesn't exist, running `#{cli_path} install` for Policyfile #{policyfile}...")
|
72
73
|
end
|
73
|
-
run_command("
|
74
|
+
run_command("#{cli_path} install #{escape_path(policyfile)}")
|
74
75
|
|
75
76
|
if always_update
|
76
|
-
info("Updating policy lock using
|
77
|
-
run_command("
|
77
|
+
info("Updating policy lock using `#{cli_path} update`")
|
78
|
+
run_command("#{cli_path} update #{escape_path(policyfile)}")
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
@@ -110,13 +111,13 @@ module Kitchen
|
|
110
111
|
# @return [String]
|
111
112
|
# @api private
|
112
113
|
def escape_path(path)
|
113
|
-
if RbConfig::CONFIG["host_os"]
|
114
|
+
if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
|
114
115
|
# I know what you're thinking: "just use Shellwords.escape". That
|
115
116
|
# method produces incorrect results on Windows with certain input
|
116
117
|
# which would be a metacharacter in Sh but is not for one or more of
|
117
118
|
# Windows command line parsing libraries. This covers the 99% case of
|
118
119
|
# spaces in the path without breaking other stuff.
|
119
|
-
if
|
120
|
+
if /[ \t\n\v"]/.match?(path)
|
120
121
|
"\"#{path.gsub(/[ \t\n\v\"\\]/) { |m| '\\' + m[0] }}\""
|
121
122
|
else
|
122
123
|
path
|
@@ -126,33 +127,24 @@ module Kitchen
|
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
148
|
-
logger.fatal("The `chef` executable cannot be found in your " \
|
149
|
-
"PATH. Ensure you have installed ChefDK or Chef Workstation " \
|
150
|
-
"from https://downloads.chef.io and that your PATH " \
|
151
|
-
"setting includes the path to the `chef` command.")
|
152
|
-
raise UserError,
|
153
|
-
"Could not find the chef executable in your PATH."
|
154
|
-
end
|
155
|
-
end
|
130
|
+
# Find the `chef` or `chef-cli` commands in the path or raise `chef` is present in
|
131
|
+
# ChefDK / Workstation releases, but is no longer shipped in any gems now that we
|
132
|
+
# use a Go based wrapper for the `chef` command in Workstation. The Ruby CLI has been
|
133
|
+
# renamed `chef-cli` under the hood and is shipped in the `chef-cli` gem.
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
# @returns [String]
|
137
|
+
def cli_path
|
138
|
+
@cli_path ||= which("chef-cli") || which("chef") || no_cli_found_error
|
139
|
+
end
|
140
|
+
|
141
|
+
# @api private
|
142
|
+
def no_cli_found_error
|
143
|
+
@logger.fatal("The `chef` or `chef-cli` executables cannot be found in your " \
|
144
|
+
"PATH. Ensure you have installed Chef Workstation " \
|
145
|
+
"from https://downloads.chef.io and that your PATH " \
|
146
|
+
"setting includes the path to the `chef` or `chef-cli` commands.")
|
147
|
+
raise UserError, "Could not find the chef or chef-cli executables in your PATH."
|
156
148
|
end
|
157
149
|
|
158
150
|
end
|
@@ -156,7 +156,7 @@ module Kitchen
|
|
156
156
|
product_name: <chef or chef-workstation>
|
157
157
|
install_strategy: skip
|
158
158
|
MSG
|
159
|
-
when provisioner[:require_chef_omnibus].to_s.match(/\d/)
|
159
|
+
when provisioner[:require_chef_omnibus].to_s.match?(/\d/)
|
160
160
|
Util.outdent!(<<-MSG)
|
161
161
|
The 'require_chef_omnibus' attribute with version values will change
|
162
162
|
to use the new 'product_version' attribute.
|
@@ -28,7 +28,7 @@ module Kitchen
|
|
28
28
|
plugin_version Kitchen::VERSION
|
29
29
|
|
30
30
|
def connection(state, &block)
|
31
|
-
options = config.to_hash.merge(state)
|
31
|
+
options = connection_options(config.to_hash.merge(state))
|
32
32
|
Kitchen::Transport::Exec::Connection.new(options, &block)
|
33
33
|
end
|
34
34
|
|
@@ -40,19 +40,105 @@ module Kitchen
|
|
40
40
|
def execute(command)
|
41
41
|
return if command.nil?
|
42
42
|
|
43
|
-
|
43
|
+
if host_os_windows?
|
44
|
+
run_command(run_from_file_command(command))
|
45
|
+
close
|
46
|
+
else
|
47
|
+
run_command(command)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
if host_os_windows?
|
53
|
+
FileUtils.remove(exec_script_file)
|
54
|
+
end
|
44
55
|
end
|
45
56
|
|
46
57
|
# "Upload" the files by copying them locally.
|
47
58
|
#
|
48
59
|
# @see Base#upload
|
49
60
|
def upload(locals, remote)
|
50
|
-
|
61
|
+
# evaluate $env:temp on Windows
|
62
|
+
real_remote = remote.to_s == "\$env:TEMP\\kitchen" ? kitchen_temp : remote
|
63
|
+
FileUtils.mkdir_p(real_remote)
|
51
64
|
Array(locals).each do |local|
|
52
|
-
FileUtils.cp_r(local,
|
65
|
+
FileUtils.cp_r(local, real_remote)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# (see Base#init_options)
|
70
|
+
def init_options(options)
|
71
|
+
super
|
72
|
+
@instance_name = @options.delete(:instance_name)
|
73
|
+
@kitchen_root = @options.delete(:kitchen_root)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @return [String] display name for the associated instance
|
79
|
+
# @api private
|
80
|
+
attr_reader :instance_name
|
81
|
+
|
82
|
+
# @return [String] local path to the root of the project
|
83
|
+
# @api private
|
84
|
+
attr_reader :kitchen_root
|
85
|
+
|
86
|
+
# Takes a long command and saves it to a file and uploads it to
|
87
|
+
# the test instance. Windows has cli character limits.
|
88
|
+
#
|
89
|
+
# @param command [String] a long command to be saved and uploaded
|
90
|
+
# @return [String] a command that executes the uploaded script
|
91
|
+
# @api private
|
92
|
+
def run_from_file_command(command)
|
93
|
+
if logger.debug?
|
94
|
+
debug("Creating exec script for #{instance_name} (#{exec_script_file})")
|
95
|
+
debug("Executing #{exec_script_file}")
|
96
|
+
end
|
97
|
+
File.open(exec_script_file, "wb") { |file| file.write(command) }
|
98
|
+
%{powershell -file "#{exec_script_file}"}
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [String] evaluated $env:temp variable
|
102
|
+
# @api private
|
103
|
+
def kitchen_temp
|
104
|
+
"#{ENV["temp"]}/kitchen"
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [String] name of script using instance name
|
108
|
+
# @api private
|
109
|
+
def exec_script_name
|
110
|
+
"#{instance_name}-exec-script.ps1"
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [String] file path for exec script to be run
|
114
|
+
# @api private
|
115
|
+
def exec_script_file
|
116
|
+
File.join(kitchen_root, ".kitchen", exec_script_name)
|
117
|
+
end
|
118
|
+
|
119
|
+
def host_os_windows?
|
120
|
+
case RbConfig::CONFIG["host_os"]
|
121
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
122
|
+
true
|
123
|
+
else
|
124
|
+
false
|
53
125
|
end
|
54
126
|
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
55
130
|
|
131
|
+
# Builds the hash of options needed by the Connection object on construction.
|
132
|
+
#
|
133
|
+
# @param data [Hash] merged configuration and mutable state data
|
134
|
+
# @return [Hash] hash of connection options
|
135
|
+
# @api private
|
136
|
+
def connection_options(data)
|
137
|
+
opts = {
|
138
|
+
instance_name: instance.name,
|
139
|
+
kitchen_root: Dir.pwd,
|
140
|
+
}
|
141
|
+
opts
|
56
142
|
end
|
57
143
|
end
|
58
144
|
end
|
@@ -195,18 +195,16 @@ module Kitchen
|
|
195
195
|
FileUtils.mkdir_p(File.dirname(local))
|
196
196
|
|
197
197
|
Array(remotes).each do |file|
|
198
|
+
logger.debug("Attempting to download '#{file}' as file")
|
199
|
+
session.scp.download!(file, local)
|
200
|
+
rescue Net::SCP::Error
|
198
201
|
begin
|
199
|
-
logger.debug("Attempting to download '#{file}' as
|
200
|
-
session.scp.download!(file, local)
|
202
|
+
logger.debug("Attempting to download '#{file}' as directory")
|
203
|
+
session.scp.download!(file, local, recursive: true)
|
201
204
|
rescue Net::SCP::Error
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
rescue Net::SCP::Error
|
206
|
-
logger.warn(
|
207
|
-
"SCP download failed for file or directory '#{file}', perhaps it does not exist?"
|
208
|
-
)
|
209
|
-
end
|
205
|
+
logger.warn(
|
206
|
+
"SCP download failed for file or directory '#{file}', perhaps it does not exist?"
|
207
|
+
)
|
210
208
|
end
|
211
209
|
end
|
212
210
|
rescue Net::SSH::Exception => ex
|
@@ -308,16 +308,20 @@ module Kitchen
|
|
308
308
|
|
309
309
|
# Builds a `LoginCommand` for use by Linux-based platforms.
|
310
310
|
#
|
311
|
-
# TODO: determine whether or not `desktop` exists
|
312
|
-
#
|
313
311
|
# @return [LoginCommand] a login command
|
314
312
|
# @api private
|
315
313
|
def login_command_for_linux
|
316
|
-
|
317
|
-
|
318
|
-
|
314
|
+
xfreerdp = Util.command_exists? "xfreerdp"
|
315
|
+
unless xfreerdp
|
316
|
+
raise WinrmFailed, "xfreerdp binary not found. Please install freerdp2-x11 on Debian-based systems or freerdp on Redhat-based systems."
|
317
|
+
end
|
318
|
+
|
319
|
+
args = %W{/u:#{options[:user]}}
|
320
|
+
args += %W{/p:#{options[:password]}} if options.key?(:password)
|
321
|
+
args += %W{/v:#{URI.parse(options[:endpoint]).host}:#{rdp_port}}
|
322
|
+
args += %W{/cert-tofu} # always accept certificate
|
319
323
|
|
320
|
-
LoginCommand.new(
|
324
|
+
LoginCommand.new(xfreerdp, args)
|
321
325
|
end
|
322
326
|
|
323
327
|
# Builds a `LoginCommand` for use by Mac-based platforms.
|
data/lib/kitchen/util.rb
CHANGED
@@ -110,7 +110,7 @@ module Kitchen
|
|
110
110
|
def self.wrap_command(cmd)
|
111
111
|
cmd = "false" if cmd.nil?
|
112
112
|
cmd = "true" if cmd.to_s.empty?
|
113
|
-
cmd = cmd.sub(/\n\Z/, "") if
|
113
|
+
cmd = cmd.sub(/\n\Z/, "") if /\n\Z/.match?(cmd)
|
114
114
|
|
115
115
|
"sh -c '\n#{cmd}\n'"
|
116
116
|
end
|
@@ -215,5 +215,15 @@ module Kitchen
|
|
215
215
|
def self.snake_case(a_string)
|
216
216
|
Thor::Util.snake_case(a_string)
|
217
217
|
end
|
218
|
+
|
219
|
+
# Check if a cmd exists on the PATH
|
220
|
+
def self.command_exists?(cmd)
|
221
|
+
paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ]
|
222
|
+
paths.each do |path|
|
223
|
+
filename = File.join(path, cmd)
|
224
|
+
return filename if File.executable?(filename)
|
225
|
+
end
|
226
|
+
false
|
227
|
+
end
|
218
228
|
end
|
219
229
|
end
|
@@ -88,6 +88,7 @@ module Kitchen
|
|
88
88
|
env_state[:environment]["KITCHEN_INSTANCE"] = instance.name
|
89
89
|
env_state[:environment]["KITCHEN_PLATFORM"] = instance.platform.name
|
90
90
|
env_state[:environment]["KITCHEN_SUITE"] = instance.suite.name
|
91
|
+
env_state[:environment]["KITCHEN_USERNAME"] = instance.transport[:username] if instance.respond_to?(:transport)
|
91
92
|
state.each_pair do |key, value|
|
92
93
|
env_state[:environment]["KITCHEN_" + key.to_s.upcase] = value.to_s
|
93
94
|
end
|
data/lib/kitchen/version.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require "chef-utils/dsl/which" unless defined?(ChefUtils::DSL::Which)
|
19
|
+
require_relative "chef_utils_wiring" unless defined?(Ohai::Mixin::ChefUtilsWiring)
|
20
|
+
|
21
|
+
module Kitchen
|
22
|
+
module Which
|
23
|
+
include ChefUtils::DSL::Which
|
24
|
+
include ChefUtilsWiring
|
25
|
+
end
|
26
|
+
end
|
data/test-kitchen.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
lib = File.expand_path("
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
3
|
require "kitchen/version"
|
4
4
|
require "English"
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.executables = %w{kitchen}
|
21
21
|
gem.require_paths = ["lib"]
|
22
22
|
|
23
|
-
gem.required_ruby_version = ">= 2.
|
23
|
+
gem.required_ruby_version = ">= 2.5"
|
24
24
|
|
25
25
|
gem.add_dependency "mixlib-shellout", ">= 1.2", "< 4.0"
|
26
26
|
gem.add_dependency "net-scp", ">= 1.1", "< 4.0" # pinning until we can confirm 4+ works
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |gem|
|
|
33
33
|
gem.add_dependency "winrm", "~> 2.0"
|
34
34
|
gem.add_dependency "winrm-elevated", "~> 1.0"
|
35
35
|
gem.add_dependency "winrm-fs", "~> 1.1"
|
36
|
+
gem.add_dependency "chef-utils", ">= 16.4.35"
|
36
37
|
# Required to run the Chef provisioner local license check for remote systems
|
37
38
|
# TK is not under Chef EULA
|
38
39
|
gem.add_dependency "license-acceptance", ">= 1.0.11", "< 3.0" # pinning until we can confirm 3+ works
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test-kitchen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fletcher Nichol
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mixlib-shellout
|
@@ -194,6 +194,20 @@ dependencies:
|
|
194
194
|
- - "~>"
|
195
195
|
- !ruby/object:Gem::Version
|
196
196
|
version: '1.1'
|
197
|
+
- !ruby/object:Gem::Dependency
|
198
|
+
name: chef-utils
|
199
|
+
requirement: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: 16.4.35
|
204
|
+
type: :runtime
|
205
|
+
prerelease: false
|
206
|
+
version_requirements: !ruby/object:Gem::Requirement
|
207
|
+
requirements:
|
208
|
+
- - ">="
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
version: 16.4.35
|
197
211
|
- !ruby/object:Gem::Dependency
|
198
212
|
name: license-acceptance
|
199
213
|
requirement: !ruby/object:Gem::Requirement
|
@@ -387,6 +401,7 @@ files:
|
|
387
401
|
- bin/kitchen
|
388
402
|
- lib/kitchen.rb
|
389
403
|
- lib/kitchen/base64_stream.rb
|
404
|
+
- lib/kitchen/chef_utils_wiring.rb
|
390
405
|
- lib/kitchen/cli.rb
|
391
406
|
- lib/kitchen/collection.rb
|
392
407
|
- lib/kitchen/color.rb
|
@@ -415,6 +430,9 @@ files:
|
|
415
430
|
- lib/kitchen/generator/init.rb
|
416
431
|
- lib/kitchen/instance.rb
|
417
432
|
- lib/kitchen/lazy_hash.rb
|
433
|
+
- lib/kitchen/lifecycle_hook/base.rb
|
434
|
+
- lib/kitchen/lifecycle_hook/local.rb
|
435
|
+
- lib/kitchen/lifecycle_hook/remote.rb
|
418
436
|
- lib/kitchen/lifecycle_hooks.rb
|
419
437
|
- lib/kitchen/loader/yaml.rb
|
420
438
|
- lib/kitchen/logger.rb
|
@@ -454,6 +472,7 @@ files:
|
|
454
472
|
- lib/kitchen/verifier/dummy.rb
|
455
473
|
- lib/kitchen/verifier/shell.rb
|
456
474
|
- lib/kitchen/version.rb
|
475
|
+
- lib/kitchen/which.rb
|
457
476
|
- lib/vendor/hash_recursive_merge.rb
|
458
477
|
- support/busser_install_command.ps1
|
459
478
|
- support/busser_install_command.sh
|
@@ -493,14 +512,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
493
512
|
requirements:
|
494
513
|
- - ">="
|
495
514
|
- !ruby/object:Gem::Version
|
496
|
-
version: '2.
|
515
|
+
version: '2.5'
|
497
516
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
498
517
|
requirements:
|
499
518
|
- - ">="
|
500
519
|
- !ruby/object:Gem::Version
|
501
520
|
version: '0'
|
502
521
|
requirements: []
|
503
|
-
rubygems_version: 3.1.
|
522
|
+
rubygems_version: 3.1.4
|
504
523
|
signing_key:
|
505
524
|
specification_version: 4
|
506
525
|
summary: Test Kitchen is an integration tool for developing and testing infrastructure
|