test-kitchen 4.1.3 → 4.1.4
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 +3 -10
- data/Rakefile +45 -0
- data/lib/kitchen/command/doctor.rb +4 -2
- data/lib/kitchen/command.rb +9 -7
- data/lib/kitchen/data_munger.rb +36 -5
- data/lib/kitchen/errors.rb +1 -1
- data/lib/kitchen/generator/init.rb +0 -1
- data/lib/kitchen/instance.rb +7 -4
- data/lib/kitchen/lazy_hash.rb +35 -0
- data/lib/kitchen/logger.rb +65 -24
- data/lib/kitchen/platform_filter.rb +4 -2
- data/lib/kitchen/provisioner/base.rb +5 -1
- data/lib/kitchen/transport/exec.rb +3 -3
- data/lib/kitchen/util.rb +23 -5
- data/lib/kitchen/verifier/base.rb +5 -1
- data/lib/kitchen/verifier/shell.rb +33 -12
- data/lib/kitchen/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c56fc990001b04aa4573652682c08b02ea00508480f76beb1db8ed72e8ade07f
|
|
4
|
+
data.tar.gz: 575f723e4cd3105fdd6e3ec4961f3bcfaa08ab8d2d38bc434dab095c3e9f193c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f629caaf011634872054a8e6fae1643758a50a97c98d28fd4967181d4a5e6f68719965894cdb007d571e9a71ae45eb41cf1de8d9639d917b2b9cab99540d5c3e
|
|
7
|
+
data.tar.gz: e10c5f823385e29b6d3c08fe9f7059d160e815ef02ebca55e81e5dd526bcbb081727b6f3aacdc4f46bcb0b0928f562014fcc0475a4c3fe95c88e6d4e67d898ab
|
data/Gemfile
CHANGED
|
@@ -12,19 +12,12 @@ group :test do
|
|
|
12
12
|
gem "maruku", "~> 0.7"
|
|
13
13
|
gem "minitest", "~> 6.0", "< 6.1"
|
|
14
14
|
gem "mocha", "~> 3.0"
|
|
15
|
+
gem "simplecov", "~> 0.22", require: false
|
|
15
16
|
gem "yard", "~> 0.9"
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
gem "cinc-auditor-bin", source: "https://rubygems.cinc.sh"
|
|
20
|
-
gem "kitchen-cinc"
|
|
21
|
-
gem "kitchen-cinc-auditor",
|
|
22
|
-
git: "https://github.com/test-kitchen/kitchen-cinc-auditor.git",
|
|
23
|
-
ref: "3d0b89eaa13f12da08a8761970e39c0f564c24c6"
|
|
24
|
-
gem "kitchen-dokken"
|
|
25
|
-
gem "kitchen-vagrant"
|
|
26
|
-
end
|
|
19
|
+
# Acceptance-suite gems live in Gemfile.integration.
|
|
27
20
|
|
|
28
21
|
group :linting do
|
|
29
|
-
gem "cookstyle", "~>
|
|
22
|
+
gem "cookstyle", "~> 9.0"
|
|
30
23
|
end
|
data/Rakefile
CHANGED
|
@@ -7,6 +7,48 @@ Rake::TestTask.new(:unit) do |t|
|
|
|
7
7
|
t.verbose = true
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
namespace :unit do
|
|
11
|
+
desc "Run each spec file in its own process to catch cross-file coupling"
|
|
12
|
+
task :isolated do
|
|
13
|
+
require "open3"
|
|
14
|
+
|
|
15
|
+
specs = FileList["spec/**/*_spec.rb"].sort
|
|
16
|
+
# Spec files are independent, and each one spends most of its time in
|
|
17
|
+
# process startup, so run a few at a time.
|
|
18
|
+
workers = (ENV["ISOLATED_WORKERS"] || 4).to_i
|
|
19
|
+
failed = []
|
|
20
|
+
|
|
21
|
+
specs.each_slice(workers) do |batch|
|
|
22
|
+
batch.map do |spec|
|
|
23
|
+
Thread.new do
|
|
24
|
+
out, status = Open3.capture2e(
|
|
25
|
+
Gem.ruby, "-Ilib", "-Ispec", spec
|
|
26
|
+
)
|
|
27
|
+
[spec, status.success?, out]
|
|
28
|
+
end
|
|
29
|
+
end.map(&:value).each do |spec, ok, out|
|
|
30
|
+
puts(ok ? " ok #{spec}" : " FAIL #{spec}\n#{out}")
|
|
31
|
+
failed << spec unless ok
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless failed.empty?
|
|
36
|
+
abort "\n#{failed.length} of #{specs.length} spec files fail on their " \
|
|
37
|
+
"own:\n #{failed.join("\n ")}\n\n" \
|
|
38
|
+
"These pass in `rake unit` only because another spec file loads " \
|
|
39
|
+
"something they need. Add the missing require."
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts "\nAll #{specs.length} spec files pass in isolation."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "Run the unit tests with coverage reporting"
|
|
46
|
+
task :coverage do
|
|
47
|
+
ENV["COVERAGE"] = "1"
|
|
48
|
+
Rake::Task[:unit].invoke
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
10
52
|
begin
|
|
11
53
|
require "cucumber"
|
|
12
54
|
require "cucumber/rake/task"
|
|
@@ -20,6 +62,9 @@ end
|
|
|
20
62
|
desc "Run all test suites"
|
|
21
63
|
task test: %i{unit features}
|
|
22
64
|
|
|
65
|
+
desc "Run the checks that must pass before a change is merged"
|
|
66
|
+
task verify: %i{unit unit:isolated style}
|
|
67
|
+
|
|
23
68
|
desc "Display LOC stats"
|
|
24
69
|
task :stats do
|
|
25
70
|
puts "\n## Production Code Stats"
|
|
@@ -28,10 +28,12 @@ module Kitchen
|
|
|
28
28
|
end
|
|
29
29
|
# By default only doctor the first instance to avoid output spam.
|
|
30
30
|
results = [results.first] unless options[:all]
|
|
31
|
-
|
|
31
|
+
# Doctor every instance before deciding the outcome. #any? would
|
|
32
|
+
# stop at the first instance reporting a problem and skip the rest.
|
|
33
|
+
failed = results.map do |instance|
|
|
32
34
|
debug "Doctor on #{instance.name}."
|
|
33
35
|
instance.doctor_action
|
|
34
|
-
end
|
|
36
|
+
end.any?
|
|
35
37
|
exit(1) if failed
|
|
36
38
|
end
|
|
37
39
|
end
|
data/lib/kitchen/command.rb
CHANGED
|
@@ -195,18 +195,20 @@ module Kitchen
|
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
# Determines how many instances may be acted on at once, clamped to the
|
|
198
|
-
# number of instances available.
|
|
198
|
+
# number of instances available and to at least one worker. Without the
|
|
199
|
+
# lower bound a zero or negative --concurrency would start no worker
|
|
200
|
+
# threads at all, so every instance would be skipped and the run would
|
|
201
|
+
# still report success.
|
|
199
202
|
#
|
|
200
203
|
# @param instances [Array<Instance>] the instances to be acted on
|
|
201
204
|
# @return [Integer] the number of worker threads to start
|
|
202
205
|
# @api private
|
|
203
206
|
def concurrency_setting(instances)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
concurrency
|
|
207
|
+
return 1 unless options[:concurrency]
|
|
208
|
+
|
|
209
|
+
concurrency = options[:concurrency]
|
|
210
|
+
concurrency = instances.size if concurrency > instances.size
|
|
211
|
+
[concurrency, 1].max
|
|
210
212
|
end
|
|
211
213
|
|
|
212
214
|
# Performs an action on a single instance, recording any instance or
|
data/lib/kitchen/data_munger.rb
CHANGED
|
@@ -1037,9 +1037,7 @@ module Kitchen
|
|
|
1037
1037
|
# Hash if not found
|
|
1038
1038
|
# @api private
|
|
1039
1039
|
def platform_data_for(name)
|
|
1040
|
-
|
|
1041
|
-
platform.fetch(:name, nil) == name
|
|
1042
|
-
end
|
|
1040
|
+
platform_index[name] || {}
|
|
1043
1041
|
end
|
|
1044
1042
|
|
|
1045
1043
|
# Destructively sets a base kitchen config key/value pair at the root of
|
|
@@ -1068,8 +1066,41 @@ module Kitchen
|
|
|
1068
1066
|
# Hash if not found
|
|
1069
1067
|
# @api private
|
|
1070
1068
|
def suite_data_for(name)
|
|
1071
|
-
|
|
1072
|
-
|
|
1069
|
+
suite_index[name] || {}
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
# Name-keyed index of the platform data, built once on first use.
|
|
1073
|
+
#
|
|
1074
|
+
# Every plugin lookup for every instance resolves a platform and a suite by
|
|
1075
|
+
# name, so the linear scans this replaces made building a project's
|
|
1076
|
+
# instances quadratic in its size.
|
|
1077
|
+
#
|
|
1078
|
+
# @return [Hash{String => Hash}] platform name to platform data
|
|
1079
|
+
# @api private
|
|
1080
|
+
def platform_index
|
|
1081
|
+
@platform_index ||= index_by_name(data.fetch(:platforms, []))
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
# Name-keyed index of the suite data, built once on first use.
|
|
1085
|
+
#
|
|
1086
|
+
# @return [Hash{String => Hash}] suite name to suite data
|
|
1087
|
+
# @api private
|
|
1088
|
+
def suite_index
|
|
1089
|
+
@suite_index ||= index_by_name(data.fetch(:suites, []))
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
# Indexes a collection of named data hashes by their :name value.
|
|
1093
|
+
#
|
|
1094
|
+
# An earlier entry wins over a later duplicate, matching the first match
|
|
1095
|
+
# that a linear search would have returned.
|
|
1096
|
+
#
|
|
1097
|
+
# @param collection [Enumerable<Hash>] platform or suite data
|
|
1098
|
+
# @return [Hash{String => Hash}] name to entry
|
|
1099
|
+
# @api private
|
|
1100
|
+
def index_by_name(collection)
|
|
1101
|
+
collection.each_with_object({}) do |entry, index|
|
|
1102
|
+
key = entry.fetch(:name, nil)
|
|
1103
|
+
index[key] = entry unless index.key?(key)
|
|
1073
1104
|
end
|
|
1074
1105
|
end
|
|
1075
1106
|
end
|
data/lib/kitchen/errors.rb
CHANGED
data/lib/kitchen/instance.rb
CHANGED
|
@@ -242,9 +242,12 @@ module Kitchen
|
|
|
242
242
|
#
|
|
243
243
|
def doctor_action
|
|
244
244
|
banner "The doctor is in"
|
|
245
|
-
|
|
245
|
+
# Each plugin reports its own problems as it finds them, so run all of
|
|
246
|
+
# them and reduce afterwards. #any? would stop at the first plugin with
|
|
247
|
+
# something to say and hide the rest.
|
|
248
|
+
[driver, provisioner, transport, verifier].map do |obj|
|
|
246
249
|
obj.doctor(state_file.read)
|
|
247
|
-
end
|
|
250
|
+
end.any?
|
|
248
251
|
end
|
|
249
252
|
|
|
250
253
|
# Returns a Hash of configuration and other useful diagnostic information.
|
|
@@ -771,12 +774,12 @@ module Kitchen
|
|
|
771
774
|
# reported transitioned state into the desired transitioned state.
|
|
772
775
|
#
|
|
773
776
|
# @param last [String,Symbol,nil] the last known transitioned state of
|
|
774
|
-
# the Instance,
|
|
777
|
+
# the Instance, or `nil` for unknown or no history
|
|
775
778
|
# @param desired [String,Symbol] the desired transitioned state for the
|
|
776
779
|
# Instance
|
|
777
780
|
# @return [Array<Symbol>] an Array of transition actions to perform
|
|
778
781
|
# @api private
|
|
779
|
-
def self.actions(last
|
|
782
|
+
def self.actions(last, desired)
|
|
780
783
|
last_index = index(last)
|
|
781
784
|
desired_index = index(desired)
|
|
782
785
|
|
data/lib/kitchen/lazy_hash.rb
CHANGED
|
@@ -95,6 +95,41 @@ module Kitchen
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
# Sets the raw value for the given key. Values are stored unrendered, so a
|
|
99
|
+
# callable assigned here is still evaluated lazily on read.
|
|
100
|
+
#
|
|
101
|
+
# SimpleDelegator would forward this through #method_missing; defining it
|
|
102
|
+
# here keeps the hot path off that dispatch, which every plugin walks once
|
|
103
|
+
# per default attribute while applying its defaults.
|
|
104
|
+
#
|
|
105
|
+
# @param key [Object] hash key
|
|
106
|
+
# @param value [Object] the value to store
|
|
107
|
+
# @return [Object] the stored value
|
|
108
|
+
def []=(key, value)
|
|
109
|
+
__getobj__[key] = value
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns whether the given key is present, without rendering its value.
|
|
113
|
+
#
|
|
114
|
+
# Defined for the same reason as {#[]=}: to bypass SimpleDelegator's
|
|
115
|
+
# #method_missing on a hot path.
|
|
116
|
+
#
|
|
117
|
+
# @param key [Object] hash key
|
|
118
|
+
# @return [Boolean] whether the key is present
|
|
119
|
+
def key?(key)
|
|
120
|
+
__getobj__.key?(key)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns the keys of the underlying hash.
|
|
124
|
+
#
|
|
125
|
+
# Defined for the same reason as {#[]=}: to bypass SimpleDelegator's
|
|
126
|
+
# #method_missing on a hot path.
|
|
127
|
+
#
|
|
128
|
+
# @return [Array] the hash keys
|
|
129
|
+
def keys
|
|
130
|
+
__getobj__.keys
|
|
131
|
+
end
|
|
132
|
+
|
|
98
133
|
# Returns a new Hash with all keys and rendered values of the LazyHash.
|
|
99
134
|
#
|
|
100
135
|
# @return [Hash] a new hash
|
data/lib/kitchen/logger.rb
CHANGED
|
@@ -312,8 +312,10 @@ module Kitchen
|
|
|
312
312
|
|
|
313
313
|
# @return [Hash] metadata added to structured log events
|
|
314
314
|
def metadata
|
|
315
|
-
|
|
316
|
-
|
|
315
|
+
# Merge in place into the fresh dup rather than allocating a new hash per
|
|
316
|
+
# stack entry; this runs once per structured log event.
|
|
317
|
+
metadata_stack.each_with_object(@base_metadata.dup) do |values, result|
|
|
318
|
+
result.merge!(values)
|
|
317
319
|
end
|
|
318
320
|
end
|
|
319
321
|
|
|
@@ -449,15 +451,20 @@ module Kitchen
|
|
|
449
451
|
|
|
450
452
|
private
|
|
451
453
|
|
|
454
|
+
# Builds the formatter used for console output.
|
|
455
|
+
#
|
|
456
|
+
# The colour is fixed for the life of the logger, so both escape
|
|
457
|
+
# sequences are resolved once here rather than on every line. An
|
|
458
|
+
# uncolourised logger, and one whose colour did not resolve, share the
|
|
459
|
+
# same uncoloured formatter.
|
|
452
460
|
def stdout_formatter(color, colorize)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
461
|
+
escape = colorize ? Color.escape(color) : ""
|
|
462
|
+
|
|
463
|
+
if escape.empty?
|
|
464
|
+
proc { |_severity, _datetime, _progname, msg| "#{msg}\n" }
|
|
457
465
|
else
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
end
|
|
466
|
+
reset = Color.escape(:reset)
|
|
467
|
+
proc { |_severity, _datetime, _progname, msg| "#{escape}#{msg}#{reset}\n" }
|
|
461
468
|
end
|
|
462
469
|
end
|
|
463
470
|
|
|
@@ -496,22 +503,50 @@ module Kitchen
|
|
|
496
503
|
# @return [void]
|
|
497
504
|
# @api private
|
|
498
505
|
def <<(msg)
|
|
499
|
-
@buffer
|
|
506
|
+
@buffer << msg
|
|
500
507
|
flush_lines
|
|
501
508
|
end
|
|
502
509
|
|
|
503
510
|
private
|
|
504
511
|
|
|
512
|
+
# Emits every complete line in the buffer, then drops the emitted bytes
|
|
513
|
+
# in a single pass. Scanning from a moving offset instead of deleting
|
|
514
|
+
# each line as it is found keeps a chunk of n lines linear rather than
|
|
515
|
+
# quadratic, which matters because one SSH or WinRM read can carry
|
|
516
|
+
# thousands of lines.
|
|
505
517
|
def flush_lines
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
@buffer[
|
|
518
|
+
start = 0
|
|
519
|
+
while (i = @buffer.index("\n", start))
|
|
520
|
+
@line_handler.call(@buffer[start, i - start].chomp)
|
|
521
|
+
start = i + 1
|
|
509
522
|
end
|
|
523
|
+
@buffer.slice!(0, start) unless start == 0
|
|
510
524
|
end
|
|
511
525
|
end
|
|
512
526
|
|
|
513
527
|
# Rewrites already-prefixed stream lines into the matching logger call.
|
|
514
528
|
class StreamLineFormatter
|
|
529
|
+
# Width of every stream prefix in {PREFIXES}. All prefixes are the same
|
|
530
|
+
# width so that a line's severity is a fixed-offset lookup.
|
|
531
|
+
#
|
|
532
|
+
# @return [Integer] the prefix width in characters
|
|
533
|
+
# @api private
|
|
534
|
+
PREFIX_WIDTH = 7
|
|
535
|
+
|
|
536
|
+
# Maps each stream prefix to the severity it denotes and whether the line
|
|
537
|
+
# must be replayed verbatim on sinks that cannot accept stream events.
|
|
538
|
+
#
|
|
539
|
+
# @return [Hash{String => Array}] prefix to [severity, structured] pairs
|
|
540
|
+
# @api private
|
|
541
|
+
PREFIXES = {
|
|
542
|
+
"-----> " => [:banner, false],
|
|
543
|
+
"D " => [:debug, true],
|
|
544
|
+
"$$$$$$ " => [:warn, true],
|
|
545
|
+
">>>>>> " => [:error, false],
|
|
546
|
+
"!!!!!! " => [:fatal, true],
|
|
547
|
+
" " => [:info, false],
|
|
548
|
+
}.freeze
|
|
549
|
+
|
|
515
550
|
def initialize(logger)
|
|
516
551
|
@logger = logger
|
|
517
552
|
end
|
|
@@ -519,18 +554,22 @@ module Kitchen
|
|
|
519
554
|
# Routes an already-prefixed stream line to the logger call matching its
|
|
520
555
|
# prefix, stripping the prefix before logging.
|
|
521
556
|
#
|
|
557
|
+
# A hash lookup on the fixed-width prefix replaces a chain of anchored
|
|
558
|
+
# regexp matches, so an unprefixed line costs one lookup rather than six
|
|
559
|
+
# failed matches.
|
|
560
|
+
#
|
|
522
561
|
# @param line [String] a single line of prefixed stream output
|
|
523
562
|
# @return [void]
|
|
524
563
|
# @api private
|
|
525
564
|
def format(line)
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
565
|
+
severity, structured = PREFIXES[line[0, PREFIX_WIDTH]]
|
|
566
|
+
|
|
567
|
+
if severity.nil?
|
|
568
|
+
log_line(:info, line)
|
|
569
|
+
elsif structured
|
|
570
|
+
structured_line(severity, line[PREFIX_WIDTH..], line)
|
|
571
|
+
else
|
|
572
|
+
log_line(severity, line[PREFIX_WIDTH..])
|
|
534
573
|
end
|
|
535
574
|
end
|
|
536
575
|
|
|
@@ -808,9 +847,11 @@ module Kitchen
|
|
|
808
847
|
message: message.to_s,
|
|
809
848
|
progname:,
|
|
810
849
|
sequence: next_sequence
|
|
811
|
-
)
|
|
812
|
-
|
|
813
|
-
|
|
850
|
+
)
|
|
851
|
+
event.compact!
|
|
852
|
+
# One writev rather than two writes: the log device is opened with
|
|
853
|
+
# sync = true, so each call is a syscall.
|
|
854
|
+
@logdev.write(JSON.generate(event), "\n")
|
|
814
855
|
end
|
|
815
856
|
true
|
|
816
857
|
end
|
|
@@ -32,9 +32,10 @@ module Kitchen
|
|
|
32
32
|
# A string "looks-like" a regexp if it starts with / and ends with / + Regexp options i or x
|
|
33
33
|
#
|
|
34
34
|
# @return [Array] filters with regexp-like string converted to PlatformRegexpFilter
|
|
35
|
+
# @raise [ArgumentError] if a filter is neither a String nor a Regexp
|
|
35
36
|
def self.convert(filters)
|
|
36
37
|
::Kernel.Array(filters).map do |filter|
|
|
37
|
-
if (match = filter.match(REGEXP_LIKE_PATTERN))
|
|
38
|
+
if filter.is_a?(::String) && (match = filter.match(REGEXP_LIKE_PATTERN))
|
|
38
39
|
options = match["options"].include?("i") ? ::Regexp::IGNORECASE : 0
|
|
39
40
|
options |= ::Regexp::EXTENDED if match["options"].include?("x")
|
|
40
41
|
filter = ::Regexp.new(match["pattern"], options)
|
|
@@ -51,7 +52,8 @@ module Kitchen
|
|
|
51
52
|
# @param [Regexp,String] value of the filter
|
|
52
53
|
def initialize(value)
|
|
53
54
|
unless value.is_a?(::Regexp) || value.is_a?(::String)
|
|
54
|
-
raise ::ArgumentError, "PlatformFilter#new requires value to be a String or a Regexp"
|
|
55
|
+
raise ::ArgumentError, "PlatformFilter#new requires value to be a String or a Regexp, " \
|
|
56
|
+
"got #{value.inspect} (#{value.class})"
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
@value = value
|
|
@@ -230,7 +230,11 @@ module Kitchen
|
|
|
230
230
|
# will persist after the process terminates. In other words, cleanup is
|
|
231
231
|
# explicit. This method is safe to call multiple times.
|
|
232
232
|
def cleanup_sandbox
|
|
233
|
-
|
|
233
|
+
# Check the ivar, not #sandbox_path, which raises rather than
|
|
234
|
+
# returning nil when the sandbox was never created. This runs from
|
|
235
|
+
# an ensure block, so raising here would mask the error that kept
|
|
236
|
+
# #create_sandbox from finishing.
|
|
237
|
+
return if @sandbox_path.nil?
|
|
234
238
|
|
|
235
239
|
debug("Cleaning up local sandbox in #{sandbox_path}")
|
|
236
240
|
@install_script_path = nil
|
|
@@ -134,11 +134,11 @@ module Kitchen
|
|
|
134
134
|
# @return [Hash] hash of connection options
|
|
135
135
|
# @api private
|
|
136
136
|
def connection_options(data)
|
|
137
|
-
|
|
137
|
+
{
|
|
138
138
|
instance_name: instance.name,
|
|
139
|
-
kitchen_root: Dir.pwd,
|
|
139
|
+
kitchen_root: data[:kitchen_root] || Dir.pwd,
|
|
140
|
+
logger:,
|
|
140
141
|
}
|
|
141
|
-
opts
|
|
142
142
|
end
|
|
143
143
|
end
|
|
144
144
|
end
|
data/lib/kitchen/util.rb
CHANGED
|
@@ -99,17 +99,35 @@ module Kitchen
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
# The text substituted for a masked value.
|
|
103
|
+
MASK = "******".freeze
|
|
104
|
+
|
|
105
|
+
# A double-quoted string value, allowing for backslash escapes so that a
|
|
106
|
+
# quote inside the value does not end the match early.
|
|
107
|
+
QUOTED_VALUE = /"(?:\\.|[^"\\])*"/.source
|
|
108
|
+
|
|
102
109
|
# Returns a string with masked values for specified parameters.
|
|
103
110
|
#
|
|
111
|
+
# Hashes are commonly interpolated into these strings via Hash#inspect,
|
|
112
|
+
# whose rendering has changed over time, so every spelling a key/value
|
|
113
|
+
# pair has had is matched:
|
|
114
|
+
#
|
|
115
|
+
# :key=>"value" symbol key, Ruby <= 3.3
|
|
116
|
+
# "key"=>"value" string key, Ruby <= 3.3
|
|
117
|
+
# key: "value" symbol key, Ruby >= 3.4
|
|
118
|
+
# "key" => "value" string key, Ruby >= 3.4
|
|
119
|
+
#
|
|
104
120
|
# @param string_to_mask [String] the object whose string representation is parsed
|
|
105
121
|
# @param keys [Array] the list of keys whose values should be masked
|
|
106
|
-
# @return [String]
|
|
122
|
+
# @return [String] a new string with the values of the given keys masked
|
|
107
123
|
def self.mask_values(string_to_mask, keys)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
keys.reduce(string_to_mask.to_s) do |masked, key|
|
|
125
|
+
key = ::Regexp.escape(key.to_s)
|
|
126
|
+
masked.gsub(
|
|
127
|
+
/(?<assignment>(?::#{key}|"#{key}")\s*=>\s*|(?<!\w)#{key}:\s*)#{QUOTED_VALUE}/,
|
|
128
|
+
%{\\k<assignment>"#{MASK}"}
|
|
129
|
+
)
|
|
111
130
|
end
|
|
112
|
-
masked_string
|
|
113
131
|
end
|
|
114
132
|
|
|
115
133
|
# Returns a formatted string representing a duration in seconds.
|
|
@@ -115,7 +115,11 @@ module Kitchen
|
|
|
115
115
|
# will persist after the process terminates. In other words, cleanup is
|
|
116
116
|
# explicit. This method is safe to call multiple times.
|
|
117
117
|
def cleanup_sandbox
|
|
118
|
-
|
|
118
|
+
# Check the ivar, not #sandbox_path, which raises rather than
|
|
119
|
+
# returning nil when the sandbox was never created. This runs from
|
|
120
|
+
# an ensure block, so raising here would mask the error that kept
|
|
121
|
+
# #create_sandbox from finishing.
|
|
122
|
+
return if @sandbox_path.nil?
|
|
119
123
|
|
|
120
124
|
debug("Cleaning up local sandbox in #{sandbox_path}")
|
|
121
125
|
FileUtils.rmtree(sandbox_path)
|
|
@@ -39,13 +39,12 @@ module Kitchen
|
|
|
39
39
|
def call(state)
|
|
40
40
|
info("[#{name}] Verify on instance #{instance.name} with state=#{state}")
|
|
41
41
|
sleep_if_set
|
|
42
|
-
merge_state_to_env(state)
|
|
43
42
|
if config[:remote_exec]
|
|
44
43
|
instance.transport.connection(state) do |conn|
|
|
45
44
|
conn.execute(config[:command])
|
|
46
45
|
end
|
|
47
46
|
else
|
|
48
|
-
shellout
|
|
47
|
+
shellout(state)
|
|
49
48
|
end
|
|
50
49
|
debug("[#{name}] Verify completed.")
|
|
51
50
|
end
|
|
@@ -72,8 +71,8 @@ module Kitchen
|
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
def shellout
|
|
76
|
-
cmd = Mixlib::ShellOut.new(config[:command],
|
|
74
|
+
def shellout(state = {})
|
|
75
|
+
cmd = Mixlib::ShellOut.new(config[:command], shellout_opts(state))
|
|
77
76
|
cmd.live_stream = config[:live_stream]
|
|
78
77
|
cmd.run_command
|
|
79
78
|
begin
|
|
@@ -83,16 +82,38 @@ module Kitchen
|
|
|
83
82
|
end
|
|
84
83
|
end
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
# Returns the options to run the command with: whatever the user
|
|
86
|
+
# configured, with the KITCHEN_* variables merged into their environment
|
|
87
|
+
# rather than replacing it.
|
|
88
|
+
#
|
|
89
|
+
# This builds a new Hash instead of writing back to config. Nested config
|
|
90
|
+
# hashes are shared between every instance, so mutating :shellout_opts
|
|
91
|
+
# would leak one instance's environment into the others.
|
|
92
|
+
#
|
|
93
|
+
# @param state [Hash] mutable instance state
|
|
94
|
+
# @return [Hash] options for Mixlib::ShellOut
|
|
95
|
+
# @api private
|
|
96
|
+
def shellout_opts(state)
|
|
97
|
+
opts = config[:shellout_opts].to_h
|
|
98
|
+
opts.merge(
|
|
99
|
+
environment: opts.fetch(:environment, {}).to_h.merge(kitchen_environment(state))
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @param state [Hash] mutable instance state
|
|
104
|
+
# @return [Hash] the KITCHEN_* environment variables for this instance
|
|
105
|
+
# @api private
|
|
106
|
+
def kitchen_environment(state)
|
|
107
|
+
env = {
|
|
108
|
+
"KITCHEN_INSTANCE" => instance.name,
|
|
109
|
+
"KITCHEN_PLATFORM" => instance.platform.name,
|
|
110
|
+
"KITCHEN_SUITE" => instance.suite.name,
|
|
111
|
+
}
|
|
112
|
+
env["KITCHEN_USERNAME"] = instance.transport[:username] if instance.respond_to?(:transport)
|
|
92
113
|
state.each_pair do |key, value|
|
|
93
|
-
|
|
114
|
+
env["KITCHEN_" + key.to_s.upcase] = value.to_s
|
|
94
115
|
end
|
|
95
|
-
|
|
116
|
+
env
|
|
96
117
|
end
|
|
97
118
|
end
|
|
98
119
|
end
|
data/lib/kitchen/version.rb
CHANGED
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: 4.1.
|
|
4
|
+
version: 4.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fletcher Nichol
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bcrypt_pbkdf
|