test-kitchen 4.0.0 → 4.1.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 +6 -4
- data/lib/kitchen/cli.rb +33 -8
- data/lib/kitchen/command/list.rb +49 -9
- data/lib/kitchen/command/logs.rb +185 -0
- data/lib/kitchen/command/test.rb +2 -1
- data/lib/kitchen/command.rb +22 -9
- data/lib/kitchen/config.rb +8 -0
- data/lib/kitchen/data_munger.rb +47 -12
- data/lib/kitchen/driver/base.rb +19 -0
- data/lib/kitchen/driver/dummy.rb +27 -0
- data/lib/kitchen/errors.rb +1 -0
- data/lib/kitchen/instance.rb +251 -88
- data/lib/kitchen/lifecycle_hook/remote.rb +5 -2
- data/lib/kitchen/logger.rb +407 -74
- data/lib/kitchen/logging.rb +2 -2
- data/lib/kitchen/provisioner/base.rb +27 -17
- data/lib/kitchen/provisioner/external.rb +420 -0
- data/lib/kitchen/shell_out.rb +1 -1
- data/lib/kitchen/state_file.rb +12 -2
- data/lib/kitchen/transport/base.rb +1 -1
- data/lib/kitchen/transport/ssh.rb +5 -5
- data/lib/kitchen/verifier/base.rb +17 -5
- data/lib/kitchen/version.rb +1 -1
- data/lib/kitchen.rb +21 -2
- data/test-kitchen.gemspec +1 -1
- metadata +12 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37110df4f18f03d75c627d0095a7cfee87e12add45de4265ddbcb3afe5d207da
|
|
4
|
+
data.tar.gz: 94c3173ff1268904ec591d0de22f15635e1e1a453ce9f3167a4e10c26f6eed48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30070f63b980cdf6ee9139232b013092c08ee838c6534b4af11736beeab7bf0aab1e3bba152665bbc07ac6a9723cee89e1c0cf6251dc4054a8c281c2866274d8
|
|
7
|
+
data.tar.gz: 1e1ab92e31f6b83b97df09df37e6e853be9f982b78aeabbf96045a1b0ee61fd8012704ad45fdcf2ff249fb489211e470ab5eed8a2db49209597fa05ae473a121
|
data/Gemfile
CHANGED
|
@@ -7,7 +7,7 @@ group :test do
|
|
|
7
7
|
gem "rb-readline"
|
|
8
8
|
gem "aruba", ">= 0.11", "< 3.0"
|
|
9
9
|
gem "countloc", "~> 0.4"
|
|
10
|
-
gem "cucumber", ">= 9.2", "<
|
|
10
|
+
gem "cucumber", ">= 9.2", "< 12"
|
|
11
11
|
gem "fakefs", "~> 3.0"
|
|
12
12
|
gem "maruku", "~> 0.7"
|
|
13
13
|
gem "minitest", "~> 6.0", "< 6.1"
|
|
@@ -15,11 +15,13 @@ group :test do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
group :integration do
|
|
18
|
-
gem "
|
|
18
|
+
gem "cinc-auditor-bin", source: "https://rubygems.cinc.sh"
|
|
19
|
+
gem "kitchen-cinc"
|
|
20
|
+
gem "kitchen-cinc-auditor",
|
|
21
|
+
git: "https://github.com/test-kitchen/kitchen-cinc-auditor.git",
|
|
22
|
+
ref: "3d0b89eaa13f12da08a8761970e39c0f564c24c6"
|
|
19
23
|
gem "kitchen-dokken"
|
|
20
24
|
gem "kitchen-vagrant"
|
|
21
|
-
gem "kitchen-inspec"
|
|
22
|
-
gem "kitchen-omnibus-chef", ">= 1.0"
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
group :linting do
|
data/lib/kitchen/cli.rb
CHANGED
|
@@ -110,13 +110,20 @@ module Kitchen
|
|
|
110
110
|
aliases: "-j",
|
|
111
111
|
type: :boolean,
|
|
112
112
|
desc: "Print data as JSON"
|
|
113
|
+
method_option :live,
|
|
114
|
+
type: :boolean,
|
|
115
|
+
desc: "Include driver-reported live instance status"
|
|
116
|
+
method_option :probe,
|
|
117
|
+
type: :boolean,
|
|
118
|
+
desc: "Probe transport connectivity in addition to driver status"
|
|
113
119
|
method_option :debug,
|
|
114
120
|
aliases: "-d",
|
|
115
121
|
type: :boolean,
|
|
116
122
|
desc: "[Deprecated] Please use `kitchen diagnose'"
|
|
117
123
|
log_options
|
|
118
124
|
def list(*args)
|
|
119
|
-
|
|
125
|
+
log_overwrite = options[:log_overwrite].nil? ? false : options[:log_overwrite]
|
|
126
|
+
update_config!(log_overwrite:)
|
|
120
127
|
perform("list", "list", args)
|
|
121
128
|
end
|
|
122
129
|
map status: :list
|
|
@@ -148,8 +155,7 @@ module Kitchen
|
|
|
148
155
|
converge: "Change instance state to converge. " \
|
|
149
156
|
"Use a provisioner to configure one or more instances",
|
|
150
157
|
setup: "Change instance state to setup. " \
|
|
151
|
-
"Prepare
|
|
152
|
-
"Install busser and related gems on one or more instances",
|
|
158
|
+
"Prepare one or more instances for verification",
|
|
153
159
|
verify: "Change instance state to verify. " \
|
|
154
160
|
"Run automated tests on one or more instances",
|
|
155
161
|
destroy: "Change instance state to destroy. " \
|
|
@@ -257,6 +263,27 @@ module Kitchen
|
|
|
257
263
|
perform("login", "login", args)
|
|
258
264
|
end
|
|
259
265
|
|
|
266
|
+
desc "logs [INSTANCE|REGEXP|all]", "Show structured logs"
|
|
267
|
+
method_option :level,
|
|
268
|
+
desc: "Minimum log level to print (debug, info, warn, error, fatal)"
|
|
269
|
+
method_option :session_id,
|
|
270
|
+
desc: "Only print logs for the given instance session id"
|
|
271
|
+
method_option :all_sessions,
|
|
272
|
+
type: :boolean,
|
|
273
|
+
desc: "Print logs from all instance sessions; defaults to all instances"
|
|
274
|
+
method_option :format,
|
|
275
|
+
default: "text",
|
|
276
|
+
desc: "Output format (ndjson, text)"
|
|
277
|
+
method_option :follow,
|
|
278
|
+
aliases: "-f",
|
|
279
|
+
type: :boolean,
|
|
280
|
+
desc: "Follow the structured log file"
|
|
281
|
+
log_options
|
|
282
|
+
def logs(*args)
|
|
283
|
+
update_config!(log_overwrite: false)
|
|
284
|
+
perform("logs", "logs", args)
|
|
285
|
+
end
|
|
286
|
+
|
|
260
287
|
desc "package INSTANCE|REGEXP", "package an instance"
|
|
261
288
|
log_options
|
|
262
289
|
def package(*args)
|
|
@@ -334,12 +361,10 @@ module Kitchen
|
|
|
334
361
|
# Update and finalize options for logging, concurrency, and other concerns.
|
|
335
362
|
#
|
|
336
363
|
# @api private
|
|
337
|
-
def update_config!
|
|
364
|
+
def update_config!(log_overwrite: options[:log_overwrite])
|
|
338
365
|
@config.log_level = log_level if log_level
|
|
339
366
|
|
|
340
|
-
unless
|
|
341
|
-
@config.log_overwrite = options[:log_overwrite]
|
|
342
|
-
end
|
|
367
|
+
@config.log_overwrite = log_overwrite unless log_overwrite.nil?
|
|
343
368
|
@config.colorize = options[:color] unless options[:color].nil?
|
|
344
369
|
|
|
345
370
|
if options[:test_base_path]
|
|
@@ -352,7 +377,7 @@ module Kitchen
|
|
|
352
377
|
# Now that we have required configs, lets create our file logger
|
|
353
378
|
Kitchen.logger = Kitchen.default_file_logger(
|
|
354
379
|
log_level,
|
|
355
|
-
|
|
380
|
+
log_overwrite
|
|
356
381
|
)
|
|
357
382
|
|
|
358
383
|
update_parallel!
|
data/lib/kitchen/command/list.rb
CHANGED
|
@@ -57,7 +57,7 @@ module Kitchen
|
|
|
57
57
|
# @return [Array<String>]
|
|
58
58
|
# @api private
|
|
59
59
|
def display_instance(instance)
|
|
60
|
-
[
|
|
60
|
+
row = [
|
|
61
61
|
color_pad(instance.name),
|
|
62
62
|
color_pad(instance.driver.name),
|
|
63
63
|
color_pad(instance.provisioner.name),
|
|
@@ -66,6 +66,8 @@ module Kitchen
|
|
|
66
66
|
format_last_action(instance.last_action),
|
|
67
67
|
format_last_error(instance.last_error),
|
|
68
68
|
]
|
|
69
|
+
row << format_live_status(instance_status(instance)) if status_requested?
|
|
70
|
+
row
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
# Format and color the given last action.
|
|
@@ -96,19 +98,39 @@ module Kitchen
|
|
|
96
98
|
end
|
|
97
99
|
end
|
|
98
100
|
|
|
101
|
+
# Format and color live status.
|
|
102
|
+
#
|
|
103
|
+
# @param status [Hash] normalized status data
|
|
104
|
+
# @return [String] formatted live status
|
|
105
|
+
# @api private
|
|
106
|
+
def format_live_status(status)
|
|
107
|
+
probe = status[:transport_probe]
|
|
108
|
+
if probe && probe[:reachable] == false
|
|
109
|
+
return colorize("#{status[:state]} (#{probe[:state]})", :red)
|
|
110
|
+
elsif probe && probe[:reachable] == true
|
|
111
|
+
return colorize("#{status[:state]} (#{probe[:state]})", :green)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
case status[:live]
|
|
115
|
+
when true then colorize(status[:state], :green)
|
|
116
|
+
when false then colorize(status[:state], :red)
|
|
117
|
+
else colorize(status[:state], :white)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
99
121
|
# Constructs a list display table and output it to the screen.
|
|
100
122
|
#
|
|
101
123
|
# @param result [Array<Instance>] an array of instances
|
|
102
124
|
# @api private
|
|
103
125
|
def list_table(result)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
colorize("Last Error", :green)
|
|
110
|
-
],
|
|
126
|
+
headings = [
|
|
127
|
+
colorize("Instance", :green), colorize("Driver", :green),
|
|
128
|
+
colorize("Provisioner", :green), colorize("Verifier", :green),
|
|
129
|
+
colorize("Transport", :green), colorize("Last Action", :green),
|
|
130
|
+
colorize("Last Error", :green)
|
|
111
131
|
]
|
|
132
|
+
headings << colorize("Live Status", :green) if status_requested?
|
|
133
|
+
table = [headings]
|
|
112
134
|
table += Array(result).map { |i| display_instance(i) }
|
|
113
135
|
print_table(table)
|
|
114
136
|
end
|
|
@@ -118,7 +140,7 @@ module Kitchen
|
|
|
118
140
|
# @param result [Hash{Symbol => String}] hash of a single instance
|
|
119
141
|
# @api private
|
|
120
142
|
def to_hash(result)
|
|
121
|
-
{
|
|
143
|
+
data = {
|
|
122
144
|
instance: result.name,
|
|
123
145
|
driver: result.driver.name,
|
|
124
146
|
provisioner: result.provisioner.name,
|
|
@@ -127,6 +149,24 @@ module Kitchen
|
|
|
127
149
|
last_action: result.last_action,
|
|
128
150
|
last_error: result.last_error,
|
|
129
151
|
}
|
|
152
|
+
if status_requested?
|
|
153
|
+
data.merge!(
|
|
154
|
+
log_path: result.log_path,
|
|
155
|
+
structured_log_path: result.structured_log_path,
|
|
156
|
+
state_path: result.state_path,
|
|
157
|
+
instance_session_id: result.current_session_id,
|
|
158
|
+
status: instance_status(result)
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
data
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def status_requested?
|
|
165
|
+
options[:live] || options[:probe]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def instance_status(instance)
|
|
169
|
+
options[:probe] ? instance.status(probe: true) : instance.status
|
|
130
170
|
end
|
|
131
171
|
|
|
132
172
|
# Outputs a formatted display table.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
require_relative "../command"
|
|
15
|
+
|
|
16
|
+
require "json" unless defined?(JSON)
|
|
17
|
+
|
|
18
|
+
module Kitchen
|
|
19
|
+
module Command
|
|
20
|
+
# Command to print structured logs for one instance.
|
|
21
|
+
class Logs < Kitchen::Command::Base
|
|
22
|
+
LEVELS = %w{debug info warn error fatal unknown}.freeze
|
|
23
|
+
|
|
24
|
+
# Invoke the command.
|
|
25
|
+
def call
|
|
26
|
+
validate_format!
|
|
27
|
+
validate_level!
|
|
28
|
+
if options[:all_sessions]
|
|
29
|
+
emit_all_session_logs
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
target_instances = instances
|
|
34
|
+
validate_follow_target!(target_instances)
|
|
35
|
+
|
|
36
|
+
target_instances.each do |instance|
|
|
37
|
+
session_id = selected_session_id(instance)
|
|
38
|
+
die "No structured log file found at #{instance.structured_log_path}" unless
|
|
39
|
+
File.file?(instance.structured_log_path)
|
|
40
|
+
|
|
41
|
+
emit_file(instance.structured_log_path, session_id)
|
|
42
|
+
follow_file(instance.structured_log_path, session_id) if options[:follow]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def emit_all_session_logs
|
|
49
|
+
log_paths = all_session_log_paths
|
|
50
|
+
validate_follow_target!(log_paths)
|
|
51
|
+
|
|
52
|
+
log_paths.each do |path|
|
|
53
|
+
emit_file(path, nil)
|
|
54
|
+
follow_file(path, nil) if options[:follow]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def all_session_log_paths
|
|
59
|
+
paths = structured_log_paths(args.first)
|
|
60
|
+
return paths unless paths.empty?
|
|
61
|
+
|
|
62
|
+
die "No structured log files found in #{Kitchen::DEFAULT_LOG_DIR}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def structured_log_paths(arg)
|
|
66
|
+
paths = all_structured_log_paths
|
|
67
|
+
return paths if arg.nil? || arg == "all"
|
|
68
|
+
|
|
69
|
+
exact_path = File.join(Kitchen::DEFAULT_LOG_DIR, "#{arg}.ndjson")
|
|
70
|
+
return [exact_path] if File.file?(exact_path)
|
|
71
|
+
|
|
72
|
+
regexp = Regexp.new(arg)
|
|
73
|
+
paths.select { |path| File.basename(path, ".ndjson") =~ regexp }
|
|
74
|
+
rescue RegexpError => e
|
|
75
|
+
die "Invalid Ruby regular expression, " \
|
|
76
|
+
"you may need to single quote the argument. " \
|
|
77
|
+
"Please try again or consult https://rubular.com/ (#{e.message})"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def all_structured_log_paths
|
|
81
|
+
Dir[File.join(Kitchen::DEFAULT_LOG_DIR, "*.ndjson")]
|
|
82
|
+
.reject { |path| File.basename(path) == "kitchen.ndjson" }
|
|
83
|
+
.sort
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def validate_follow_target!(targets)
|
|
87
|
+
return unless options[:follow] && targets.size > 1
|
|
88
|
+
|
|
89
|
+
die "Following multiple instance logs is not supported; choose one instance"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def instances
|
|
93
|
+
results = parse_subcommand(args.first)
|
|
94
|
+
if results.size > 1 && !options[:all_sessions]
|
|
95
|
+
die "Argument `#{args.first}' returned multiple results:\n" +
|
|
96
|
+
results.map { |i| " * #{i.name}" }.join("\n")
|
|
97
|
+
end
|
|
98
|
+
results
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def selected_session_id(instance)
|
|
102
|
+
return if options[:all_sessions]
|
|
103
|
+
return options[:session_id] if options[:session_id]
|
|
104
|
+
return instance.current_session_id if instance.current_session_id
|
|
105
|
+
|
|
106
|
+
if File.file?(instance.structured_log_path)
|
|
107
|
+
session_id = latest_session_id(instance.structured_log_path)
|
|
108
|
+
return session_id if session_id
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
die "Instance #{instance.to_str} has no current session id; use --all-sessions"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def latest_session_id(path)
|
|
115
|
+
File.foreach(path).filter_map do |line|
|
|
116
|
+
JSON.parse(line)["instance_session_id"]
|
|
117
|
+
rescue JSON::ParserError
|
|
118
|
+
nil
|
|
119
|
+
end.last
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def emit_file(path, session_id)
|
|
123
|
+
File.foreach(path) { |line| emit_line(line, session_id) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def follow_file(path, session_id)
|
|
127
|
+
File.open(path, "r") do |file|
|
|
128
|
+
file.seek(0, IO::SEEK_END)
|
|
129
|
+
loop do
|
|
130
|
+
line = file.gets
|
|
131
|
+
if line
|
|
132
|
+
emit_line(line, session_id)
|
|
133
|
+
else
|
|
134
|
+
sleep 1
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def emit_line(line, session_id)
|
|
141
|
+
event = JSON.parse(line)
|
|
142
|
+
return unless session_match?(event, session_id)
|
|
143
|
+
return unless level_match?(event)
|
|
144
|
+
|
|
145
|
+
puts format_event(event)
|
|
146
|
+
rescue JSON::ParserError
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def format_event(event)
|
|
151
|
+
case options[:format]
|
|
152
|
+
when "ndjson" then JSON.generate(event)
|
|
153
|
+
when nil, "text" then event["message"].to_s
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def session_match?(event, session_id)
|
|
158
|
+
session_id.nil? || event["instance_session_id"] == session_id
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def level_match?(event)
|
|
162
|
+
return true unless options[:level]
|
|
163
|
+
|
|
164
|
+
level_index(event["level"]) >= level_index(options[:level])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def level_index(level)
|
|
168
|
+
LEVELS.index(level.to_s) || LEVELS.index("unknown")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def validate_format!
|
|
172
|
+
return if [nil, "ndjson", "text"].include?(options[:format])
|
|
173
|
+
|
|
174
|
+
die "Invalid logs format `#{options[:format]}'; use ndjson or text"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def validate_level!
|
|
178
|
+
return unless options[:level]
|
|
179
|
+
return if LEVELS.include?(options[:level])
|
|
180
|
+
|
|
181
|
+
die "Invalid log level `#{options[:level]}'; use debug, info, warn, error, or fatal"
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
data/lib/kitchen/command/test.rb
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
|
|
18
18
|
require_relative "../command"
|
|
19
|
+
require_relative "../errors"
|
|
19
20
|
|
|
20
21
|
require "benchmark" unless defined?(Benchmark)
|
|
21
22
|
|
|
@@ -30,7 +31,7 @@ module Kitchen
|
|
|
30
31
|
# Invoke the command.
|
|
31
32
|
def call
|
|
32
33
|
unless %w{passing always never}.include?(options[:destroy])
|
|
33
|
-
raise
|
|
34
|
+
raise UserError, "Destroy mode must be passing, always, or never."
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
banner "Starting Test Kitchen (v#{Kitchen::VERSION})"
|
data/lib/kitchen/command.rb
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
# See the License for the specific language governing permissions and
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
|
|
18
|
+
require_relative "errors"
|
|
19
|
+
require_relative "logging"
|
|
20
|
+
|
|
18
21
|
module Kitchen
|
|
19
22
|
module Command
|
|
20
23
|
# Base class for CLI commands.
|
|
@@ -160,16 +163,22 @@ module Kitchen
|
|
|
160
163
|
|
|
161
164
|
threads = []
|
|
162
165
|
@action_errors = []
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
@action_errors_mutex = Mutex.new
|
|
167
|
+
previous_abort_on_exception = Thread.abort_on_exception
|
|
168
|
+
begin
|
|
169
|
+
Thread.abort_on_exception = true if options[:fail_fast]
|
|
170
|
+
concurrency.times do
|
|
171
|
+
threads << Thread.new do
|
|
172
|
+
while (instance = queue.pop)
|
|
173
|
+
run_action_in_thread(action, instance, *args)
|
|
174
|
+
end
|
|
167
175
|
end
|
|
168
176
|
end
|
|
177
|
+
threads.map(&:join)
|
|
178
|
+
report_errors
|
|
179
|
+
ensure
|
|
180
|
+
Thread.abort_on_exception = previous_abort_on_exception
|
|
169
181
|
end
|
|
170
|
-
Thread.abort_on_exception = true if options[:fail_fast]
|
|
171
|
-
threads.map(&:join)
|
|
172
|
-
report_errors
|
|
173
182
|
end
|
|
174
183
|
|
|
175
184
|
# private
|
|
@@ -194,14 +203,18 @@ module Kitchen
|
|
|
194
203
|
def run_action_in_thread(action, instance, *args)
|
|
195
204
|
instance.public_send(action, *args)
|
|
196
205
|
rescue Kitchen::InstanceFailure => e
|
|
197
|
-
|
|
206
|
+
record_action_error(e)
|
|
198
207
|
rescue Kitchen::ActionFailed => e
|
|
199
208
|
new_error = Kitchen::ActionFailed.new("#{e.message} on #{instance.name}")
|
|
200
209
|
new_error.set_backtrace(e.backtrace)
|
|
201
|
-
|
|
210
|
+
record_action_error(new_error)
|
|
202
211
|
ensure
|
|
203
212
|
instance.cleanup!
|
|
204
213
|
end
|
|
214
|
+
|
|
215
|
+
def record_action_error(error)
|
|
216
|
+
@action_errors_mutex.synchronize { @action_errors << error }
|
|
217
|
+
end
|
|
205
218
|
end
|
|
206
219
|
end
|
|
207
220
|
end
|
data/lib/kitchen/config.rb
CHANGED
|
@@ -270,13 +270,21 @@ module Kitchen
|
|
|
270
270
|
def new_instance_logger(suite, platform, index)
|
|
271
271
|
name = instance_name(suite, platform)
|
|
272
272
|
log_location = File.join(log_root, "#{name}.log").to_s
|
|
273
|
+
structured_log_location = File.join(log_root, "#{name}.ndjson").to_s
|
|
273
274
|
Logger.new(
|
|
274
275
|
stdout: STDOUT,
|
|
275
276
|
color: Color::COLORS[index % Color::COLORS.size].to_sym,
|
|
276
277
|
logdev: log_location,
|
|
278
|
+
structured_logdev: structured_log_location,
|
|
277
279
|
level: Util.to_logger_level(log_level),
|
|
278
280
|
log_overwrite:,
|
|
279
281
|
progname: name,
|
|
282
|
+
metadata: {
|
|
283
|
+
kitchen_run_id: Kitchen.run_id,
|
|
284
|
+
instance: name,
|
|
285
|
+
suite: suite.name,
|
|
286
|
+
platform: platform.name,
|
|
287
|
+
},
|
|
280
288
|
colorize: @colorize
|
|
281
289
|
)
|
|
282
290
|
end
|
data/lib/kitchen/data_munger.rb
CHANGED
|
@@ -38,13 +38,7 @@ module Kitchen
|
|
|
38
38
|
def initialize(data, kitchen_config = {})
|
|
39
39
|
@data = data
|
|
40
40
|
@kitchen_config = kitchen_config
|
|
41
|
-
|
|
42
|
-
convert_legacy_chef_paths_format!
|
|
43
|
-
convert_legacy_require_chef_omnibus_format!
|
|
44
|
-
convert_legacy_busser_format!
|
|
45
|
-
convert_legacy_driver_http_proxy_format!
|
|
46
|
-
move_chef_data_to_provisioner!
|
|
47
|
-
convert_legacy_pre_create_command!
|
|
41
|
+
LegacyConfigNormalizer.new(self).normalize!
|
|
48
42
|
end
|
|
49
43
|
|
|
50
44
|
# Generate a new Hash of configuration data that can be used to construct
|
|
@@ -651,12 +645,53 @@ module Kitchen
|
|
|
651
645
|
# @return [Hash] a new merged Hash
|
|
652
646
|
# @api private
|
|
653
647
|
def merged_data_for(key, suite, platform, default_key = :name)
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
648
|
+
MergeResolver.new(self).merged_data_for(key, suite, platform, default_key)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
# Runs all legacy configuration conversions while keeping DataMunger as the
|
|
652
|
+
# public facade for destructive normalization.
|
|
653
|
+
class LegacyConfigNormalizer
|
|
654
|
+
STEPS = %i{
|
|
655
|
+
convert_legacy_driver_format!
|
|
656
|
+
convert_legacy_chef_paths_format!
|
|
657
|
+
convert_legacy_require_chef_omnibus_format!
|
|
658
|
+
convert_legacy_busser_format!
|
|
659
|
+
convert_legacy_driver_http_proxy_format!
|
|
660
|
+
move_chef_data_to_provisioner!
|
|
661
|
+
convert_legacy_pre_create_command!
|
|
662
|
+
}.freeze
|
|
663
|
+
|
|
664
|
+
def initialize(data_munger)
|
|
665
|
+
@data_munger = data_munger
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def normalize!
|
|
669
|
+
STEPS.each { |step| data_munger.send(step) }
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
private
|
|
673
|
+
|
|
674
|
+
attr_reader :data_munger
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Resolves the default/common/platform/suite layers for one plugin key.
|
|
678
|
+
class MergeResolver
|
|
679
|
+
def initialize(data_munger)
|
|
680
|
+
@data_munger = data_munger
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def merged_data_for(key, suite, platform, default_key)
|
|
684
|
+
ddata = data_munger.send(:normalized_default_data, key, default_key, suite, platform)
|
|
685
|
+
cdata = data_munger.send(:normalized_common_data, key, default_key)
|
|
686
|
+
pdata = data_munger.send(:normalized_platform_data, key, default_key, platform)
|
|
687
|
+
sdata = data_munger.send(:normalized_suite_data, key, default_key, suite)
|
|
688
|
+
|
|
689
|
+
ddata.rmerge(cdata.rmerge(pdata.rmerge(sdata)))
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
private
|
|
658
693
|
|
|
659
|
-
|
|
694
|
+
attr_reader :data_munger
|
|
660
695
|
end
|
|
661
696
|
|
|
662
697
|
# Destructively moves key Chef configuration key/value pairs from being
|
data/lib/kitchen/driver/base.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative "../lazy_hash"
|
|
|
16
16
|
require_relative "../logging"
|
|
17
17
|
require_relative "../plugin_base"
|
|
18
18
|
require_relative "../shell_out"
|
|
19
|
+
require "time" unless defined?(Time)
|
|
19
20
|
|
|
20
21
|
module Kitchen
|
|
21
22
|
module Driver
|
|
@@ -63,6 +64,24 @@ module Kitchen
|
|
|
63
64
|
false
|
|
64
65
|
end
|
|
65
66
|
|
|
67
|
+
# Reports whether the backing instance is known to be live.
|
|
68
|
+
#
|
|
69
|
+
# Drivers that can ask their provider should override this method.
|
|
70
|
+
# Existing drivers remain compatible by inheriting an unknown status.
|
|
71
|
+
#
|
|
72
|
+
# @param state [Hash] mutable instance and driver state
|
|
73
|
+
# @return [Hash] normalized status data
|
|
74
|
+
def status(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
75
|
+
{
|
|
76
|
+
live: nil,
|
|
77
|
+
state: "unknown",
|
|
78
|
+
source: "driver",
|
|
79
|
+
resource_id: nil,
|
|
80
|
+
message: "#{self.class} does not support status checks",
|
|
81
|
+
checked_at: Time.now.utc.iso8601,
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
66
85
|
# Sets the API version for this driver. If the driver does not set this
|
|
67
86
|
# value, then `nil` will be used and reported.
|
|
68
87
|
#
|
data/lib/kitchen/driver/dummy.rb
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# limitations under the License.
|
|
12
12
|
|
|
13
13
|
require_relative "../../kitchen"
|
|
14
|
+
require "time" unless defined?(Time)
|
|
14
15
|
|
|
15
16
|
module Kitchen
|
|
16
17
|
module Driver
|
|
@@ -51,6 +52,32 @@ module Kitchen
|
|
|
51
52
|
state.delete(:my_id)
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# Reports whether the dummy instance has a generated resource id.
|
|
56
|
+
#
|
|
57
|
+
# @param state [Hash] mutable instance and driver state
|
|
58
|
+
# @return [Hash] normalized status data
|
|
59
|
+
def status(state)
|
|
60
|
+
if state[:my_id]
|
|
61
|
+
{
|
|
62
|
+
live: true,
|
|
63
|
+
state: "running",
|
|
64
|
+
source: "driver",
|
|
65
|
+
resource_id: state[:my_id],
|
|
66
|
+
message: "Dummy instance exists",
|
|
67
|
+
checked_at: Time.now.utc.iso8601,
|
|
68
|
+
}
|
|
69
|
+
else
|
|
70
|
+
{
|
|
71
|
+
live: false,
|
|
72
|
+
state: "not_created",
|
|
73
|
+
source: "driver",
|
|
74
|
+
resource_id: nil,
|
|
75
|
+
message: "Dummy instance has no resource id",
|
|
76
|
+
checked_at: Time.now.utc.iso8601,
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
54
81
|
private
|
|
55
82
|
|
|
56
83
|
# Report what action is taking place, sleeping if so configured, and
|
data/lib/kitchen/errors.rb
CHANGED