test-kitchen 3.9.1 → 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 +8 -5
- data/lib/kitchen/cli.rb +33 -8
- data/lib/kitchen/collection.rb +1 -1
- 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/configurable.rb +4 -4
- data/lib/kitchen/data_munger.rb +54 -21
- data/lib/kitchen/driver/base.rb +19 -0
- data/lib/kitchen/driver/dummy.rb +27 -0
- data/lib/kitchen/driver/proxy.rb +11 -6
- data/lib/kitchen/errors.rb +1 -0
- data/lib/kitchen/generator/init.rb +1 -1
- data/lib/kitchen/instance.rb +248 -182
- data/lib/kitchen/lazy_hash.rb +1 -1
- 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/metadata_chopper.rb +1 -1
- data/lib/kitchen/provisioner/base.rb +27 -35
- data/lib/kitchen/provisioner/external.rb +420 -0
- data/lib/kitchen/provisioner.rb +1 -2
- 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/transport/winrm.rb +8 -8
- data/lib/kitchen/util.rb +1 -1
- data/lib/kitchen/verifier/base.rb +17 -5
- data/lib/kitchen/version.rb +1 -1
- data/lib/kitchen.rb +21 -4
- data/lib/vendor/hash_recursive_merge.rb +2 -2
- data/templates/driver/driver.rb.erb +1 -1
- data/test-kitchen.gemspec +5 -7
- metadata +36 -41
- data/lib/kitchen/driver/ssh_base.rb +0 -346
- data/lib/kitchen/provisioner/chef/berkshelf.rb +0 -116
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +0 -352
- data/lib/kitchen/provisioner/chef/policyfile.rb +0 -173
- data/lib/kitchen/provisioner/chef_apply.rb +0 -121
- data/lib/kitchen/provisioner/chef_base.rb +0 -723
- data/lib/kitchen/provisioner/chef_infra.rb +0 -167
- data/lib/kitchen/provisioner/chef_solo.rb +0 -82
- data/lib/kitchen/provisioner/chef_target.rb +0 -130
- data/lib/kitchen/provisioner/chef_zero.rb +0 -12
- data/lib/kitchen/ssh.rb +0 -296
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/driver/proxy.rb
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# limitations under the License.
|
|
12
12
|
#
|
|
13
13
|
|
|
14
|
-
require_relative "
|
|
14
|
+
require_relative "base"
|
|
15
15
|
require_relative "../version"
|
|
16
16
|
|
|
17
17
|
module Kitchen
|
|
@@ -23,7 +23,7 @@ module Kitchen
|
|
|
23
23
|
# the driver was created.
|
|
24
24
|
#
|
|
25
25
|
# @author Seth Chisamore <schisamo@opscode.com>
|
|
26
|
-
class Proxy < Kitchen::Driver::
|
|
26
|
+
class Proxy < Kitchen::Driver::Base
|
|
27
27
|
plugin_version Kitchen::VERSION
|
|
28
28
|
|
|
29
29
|
required_config :host
|
|
@@ -33,8 +33,11 @@ module Kitchen
|
|
|
33
33
|
|
|
34
34
|
# (see Base#create)
|
|
35
35
|
def create(state)
|
|
36
|
-
|
|
36
|
+
super
|
|
37
37
|
state[:hostname] = config[:host]
|
|
38
|
+
state[:port] = config[:port] if config[:port]
|
|
39
|
+
state[:username] = config[:username] if config[:username]
|
|
40
|
+
state[:password] = config[:password] if config[:password]
|
|
38
41
|
reset_instance(state)
|
|
39
42
|
end
|
|
40
43
|
|
|
@@ -48,15 +51,17 @@ module Kitchen
|
|
|
48
51
|
|
|
49
52
|
private
|
|
50
53
|
|
|
51
|
-
# Resets the non-Kitchen managed instance
|
|
52
|
-
# over
|
|
54
|
+
# Resets the non-Kitchen managed instance by issuing a command
|
|
55
|
+
# over the transport.
|
|
53
56
|
#
|
|
54
57
|
# @param state [Hash] the state hash
|
|
55
58
|
# @api private
|
|
56
59
|
def reset_instance(state)
|
|
57
60
|
if (cmd = config[:reset_command])
|
|
58
61
|
info("Resetting instance state with command: #{cmd}")
|
|
59
|
-
|
|
62
|
+
instance.transport.connection(state) do |conn|
|
|
63
|
+
conn.execute(cmd)
|
|
64
|
+
end
|
|
60
65
|
end
|
|
61
66
|
end
|
|
62
67
|
end
|
data/lib/kitchen/errors.rb
CHANGED
data/lib/kitchen/instance.rb
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
require "benchmark" unless defined?(Benchmark)
|
|
19
19
|
require "fileutils" unless defined?(FileUtils)
|
|
20
|
+
require "securerandom" unless defined?(SecureRandom)
|
|
21
|
+
require "time" unless defined?(Time)
|
|
20
22
|
|
|
21
23
|
module Kitchen
|
|
22
24
|
# An instance of a suite running on a platform. A created instance may be a
|
|
@@ -210,11 +212,7 @@ module Kitchen
|
|
|
210
212
|
raise UserError, "Instance #{to_str} has not yet been created"
|
|
211
213
|
end
|
|
212
214
|
|
|
213
|
-
lc =
|
|
214
|
-
legacy_ssh_base_login(state)
|
|
215
|
-
else
|
|
216
|
-
transport.connection(state).login_command
|
|
217
|
-
end
|
|
215
|
+
lc = transport.connection(state).login_command
|
|
218
216
|
|
|
219
217
|
debug(%{Login command: #{lc.command} #{lc.arguments.join(" ")} } \
|
|
220
218
|
"(Options: #{lc.options})")
|
|
@@ -291,6 +289,46 @@ module Kitchen
|
|
|
291
289
|
state_file.read[:last_error]
|
|
292
290
|
end
|
|
293
291
|
|
|
292
|
+
# Returns the current instance session identifier, if one has been
|
|
293
|
+
# established.
|
|
294
|
+
#
|
|
295
|
+
# @return [String,nil] the current instance session id
|
|
296
|
+
def current_session_id
|
|
297
|
+
state_file.read[:instance_session_id]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Returns the path to the text log file for this instance.
|
|
301
|
+
#
|
|
302
|
+
# @return [String,nil] the instance text log path
|
|
303
|
+
def log_path
|
|
304
|
+
logger.logdev_path
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Returns the path to the structured log file for this instance.
|
|
308
|
+
#
|
|
309
|
+
# @return [String,nil] the instance structured log path
|
|
310
|
+
def structured_log_path
|
|
311
|
+
logger.structured_logdev_path
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Returns the path to the state file for this instance.
|
|
315
|
+
#
|
|
316
|
+
# @return [String] the instance state file path
|
|
317
|
+
def state_path
|
|
318
|
+
state_file.path
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Returns normalized liveness status for this instance.
|
|
322
|
+
#
|
|
323
|
+
# @param probe [Boolean] whether to probe the transport as well
|
|
324
|
+
# @return [Hash] normalized status data
|
|
325
|
+
def status(probe: false)
|
|
326
|
+
state = state_file.read
|
|
327
|
+
result = driver_status(state)
|
|
328
|
+
result[:transport_probe] = transport_probe(state) if probe
|
|
329
|
+
result
|
|
330
|
+
end
|
|
331
|
+
|
|
294
332
|
# Clean up any per-instance resources before exiting.
|
|
295
333
|
#
|
|
296
334
|
# @return [void]
|
|
@@ -410,17 +448,14 @@ module Kitchen
|
|
|
410
448
|
# @return [self] this instance, used to chain actions
|
|
411
449
|
# @api private
|
|
412
450
|
def converge_action
|
|
413
|
-
banner "Converging #{to_str}..."
|
|
414
451
|
elapsed = action(:converge) do |state|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
end
|
|
452
|
+
banner "Converging #{to_str}..."
|
|
453
|
+
provisioner.check_license
|
|
454
|
+
provisioner.call(state)
|
|
455
|
+
end
|
|
456
|
+
with_current_action_metadata(:converge) do
|
|
457
|
+
info("Finished converging #{to_str} #{Util.duration(elapsed.real)}.")
|
|
422
458
|
end
|
|
423
|
-
info("Finished converging #{to_str} #{Util.duration(elapsed.real)}.")
|
|
424
459
|
self
|
|
425
460
|
end
|
|
426
461
|
|
|
@@ -430,47 +465,28 @@ module Kitchen
|
|
|
430
465
|
# @return [self] this instance, used to chain actions
|
|
431
466
|
# @api private
|
|
432
467
|
def setup_action
|
|
433
|
-
banner "Setting up #{to_str}..."
|
|
434
468
|
elapsed = action(:setup) do |state|
|
|
435
|
-
|
|
469
|
+
banner "Setting up #{to_str}..."
|
|
470
|
+
end
|
|
471
|
+
with_current_action_metadata(:setup) do
|
|
472
|
+
info("Finished setting up #{to_str} #{Util.duration(elapsed.real)}.")
|
|
436
473
|
end
|
|
437
|
-
info("Finished setting up #{to_str} #{Util.duration(elapsed.real)}.")
|
|
438
474
|
self
|
|
439
475
|
end
|
|
440
476
|
|
|
441
|
-
# returns true, if the verifier is busser
|
|
442
|
-
def verifier_busser?(verifier)
|
|
443
|
-
!defined?(Kitchen::Verifier::Busser).nil? && verifier.is_a?(Kitchen::Verifier::Busser)
|
|
444
|
-
end
|
|
445
|
-
|
|
446
|
-
# returns true, if the verifier is dummy
|
|
447
|
-
def verifier_dummy?(verifier)
|
|
448
|
-
!defined?(Kitchen::Verifier::Dummy).nil? && verifier.is_a?(Kitchen::Verifier::Dummy)
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
def use_legacy_ssh_verifier?(verifier)
|
|
452
|
-
verifier_busser?(verifier) || verifier_dummy?(verifier)
|
|
453
|
-
end
|
|
454
|
-
|
|
455
477
|
# Perform the verify action.
|
|
456
478
|
#
|
|
457
479
|
# @see Driver::Base#verify
|
|
458
480
|
# @return [self] this instance, used to chain actions
|
|
459
481
|
# @api private
|
|
460
482
|
def verify_action
|
|
461
|
-
banner "Verifying #{to_str}..."
|
|
462
483
|
elapsed = action(:verify) do |state|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
verifier.call(driver.legacy_state(state))
|
|
469
|
-
else
|
|
470
|
-
verifier.call(state)
|
|
471
|
-
end
|
|
484
|
+
banner "Verifying #{to_str}..."
|
|
485
|
+
verifier.call(state)
|
|
486
|
+
end
|
|
487
|
+
with_current_action_metadata(:verify) do
|
|
488
|
+
info("Finished verifying #{to_str} #{Util.duration(elapsed.real)}.")
|
|
472
489
|
end
|
|
473
|
-
info("Finished verifying #{to_str} #{Util.duration(elapsed.real)}.")
|
|
474
490
|
self
|
|
475
491
|
end
|
|
476
492
|
|
|
@@ -488,14 +504,18 @@ module Kitchen
|
|
|
488
504
|
# @param verb [Symbol] the action to be performed
|
|
489
505
|
# @param output_verb [String] a verb representing the action, suitable for
|
|
490
506
|
# use in output logging
|
|
491
|
-
# @yield perform optional work just after action has
|
|
507
|
+
# @yield perform optional work just after action has completed
|
|
492
508
|
# @return [self] this instance, used to chain actions
|
|
493
509
|
# @api private
|
|
494
510
|
def perform_action(verb, output_verb)
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
511
|
+
elapsed = action(verb) do |state|
|
|
512
|
+
banner "#{output_verb} #{to_str}..."
|
|
513
|
+
driver.public_send(verb, state)
|
|
514
|
+
end
|
|
515
|
+
with_current_action_metadata(verb) do
|
|
516
|
+
info("Finished #{output_verb.downcase} #{to_str}" \
|
|
517
|
+
" #{Util.duration(elapsed.real)}.")
|
|
518
|
+
end
|
|
499
519
|
yield if block_given?
|
|
500
520
|
self
|
|
501
521
|
end
|
|
@@ -520,64 +540,7 @@ module Kitchen
|
|
|
520
540
|
# occurred, etc.
|
|
521
541
|
# @api private
|
|
522
542
|
def action(what, &block)
|
|
523
|
-
|
|
524
|
-
elapsed = Benchmark.measure do
|
|
525
|
-
synchronize_or_call(what, state, &block)
|
|
526
|
-
end
|
|
527
|
-
state[:last_action] = what.to_s
|
|
528
|
-
state[:last_error] = nil
|
|
529
|
-
elapsed
|
|
530
|
-
rescue ActionFailed => e
|
|
531
|
-
log_failure(what, e)
|
|
532
|
-
state[:last_error] = e.class.name
|
|
533
|
-
raise(InstanceFailure, failure_message(what) +
|
|
534
|
-
" Please see .kitchen/logs/#{name}.log for more details",
|
|
535
|
-
e.backtrace)
|
|
536
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
537
|
-
log_failure(what, e)
|
|
538
|
-
state[:last_error] = e.class.name
|
|
539
|
-
raise ActionFailed,
|
|
540
|
-
"Failed to complete ##{what} action: [#{e.message}]", e.backtrace
|
|
541
|
-
ensure
|
|
542
|
-
state_file.write(state)
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
# Runs a given action block through a common driver mutex if required or
|
|
546
|
-
# runs it directly otherwise. If a driver class' `.serial_actions` array
|
|
547
|
-
# includes the desired action, then the action must be run with a muxtex
|
|
548
|
-
# lock. Otherwise, it is assumed that the action can happen concurrently,
|
|
549
|
-
# or fully in parallel.
|
|
550
|
-
#
|
|
551
|
-
# @param what [Symbol] the action to be performed
|
|
552
|
-
# @param state [Hash] a mutable state hash for this instance
|
|
553
|
-
# @param block [Proc] a block to be called
|
|
554
|
-
# @api private
|
|
555
|
-
def synchronize_or_call(what, state)
|
|
556
|
-
plugin_class = plugin_class_for_action(what)
|
|
557
|
-
if Array(plugin_class.serial_actions).include?(what)
|
|
558
|
-
debug("#{to_str} is synchronizing on #{plugin_class}##{what}")
|
|
559
|
-
self.class.mutexes[plugin_class].synchronize do
|
|
560
|
-
debug("#{to_str} is messaging #{plugin_class}##{what}")
|
|
561
|
-
yield(state)
|
|
562
|
-
end
|
|
563
|
-
else
|
|
564
|
-
yield(state)
|
|
565
|
-
end
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
# Maps the given action to the plugin class associated with the action
|
|
569
|
-
#
|
|
570
|
-
# @param what[Symbol] action
|
|
571
|
-
# @return [Class] Kitchen::Plugin::Base
|
|
572
|
-
# @api private
|
|
573
|
-
def plugin_class_for_action(what)
|
|
574
|
-
{
|
|
575
|
-
create: driver,
|
|
576
|
-
setup: transport,
|
|
577
|
-
converge: provisioner,
|
|
578
|
-
verify: verifier,
|
|
579
|
-
destroy: driver,
|
|
580
|
-
}[what].class
|
|
543
|
+
action_runner.call(what, &block)
|
|
581
544
|
end
|
|
582
545
|
|
|
583
546
|
# Writes a high level message for logging and/or output.
|
|
@@ -589,97 +552,200 @@ module Kitchen
|
|
|
589
552
|
# @api private
|
|
590
553
|
def banner(*args)
|
|
591
554
|
Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args)
|
|
555
|
+
if Kitchen.logger.structured_logdev
|
|
556
|
+
Kitchen.logger.with_metadata(logger.metadata) do
|
|
557
|
+
Kitchen.logger.structured_logdev.banner(*args)
|
|
558
|
+
end
|
|
559
|
+
end
|
|
592
560
|
super
|
|
593
561
|
end
|
|
594
562
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
# console output in the default case (i.e. running kitchen with :info
|
|
598
|
-
# level debugging).
|
|
599
|
-
#
|
|
600
|
-
# @param what [String] an action
|
|
601
|
-
# @param e [Exception] an exception
|
|
602
|
-
# @api private
|
|
603
|
-
def log_failure(what, e)
|
|
604
|
-
return if logger.logdev.nil?
|
|
605
|
-
|
|
606
|
-
logger.logdev.error(failure_message(what))
|
|
607
|
-
Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
|
|
563
|
+
def action_runner
|
|
564
|
+
@action_runner ||= ActionRunner.new(self, state_file)
|
|
608
565
|
end
|
|
609
566
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
567
|
+
def with_current_action_metadata(what)
|
|
568
|
+
state = state_file.read
|
|
569
|
+
logger.with_metadata(
|
|
570
|
+
action: what.to_s,
|
|
571
|
+
action_id: state[:last_action_id],
|
|
572
|
+
instance_session_id: state[:instance_session_id],
|
|
573
|
+
kitchen_run_id: state[:last_kitchen_run_id] || Kitchen.run_id
|
|
574
|
+
) do
|
|
575
|
+
yield
|
|
576
|
+
end
|
|
618
577
|
end
|
|
619
578
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
# warn("Driver authors: please read http://example.com for more details.")
|
|
631
|
-
driver.converge(state)
|
|
579
|
+
def driver_status(state)
|
|
580
|
+
if driver.method(:status).arity == 0
|
|
581
|
+
return unknown_driver_status(
|
|
582
|
+
"Driver status method does not accept Kitchen state"
|
|
583
|
+
)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
normalize_driver_status(driver.status(state))
|
|
587
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
588
|
+
unknown_driver_status(e.message, e.class.name)
|
|
632
589
|
end
|
|
633
590
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
591
|
+
def normalize_driver_status(status)
|
|
592
|
+
status = Util.symbolized_hash(status || {})
|
|
593
|
+
{
|
|
594
|
+
live: status[:live],
|
|
595
|
+
state: status[:state] || "unknown",
|
|
596
|
+
source: status[:source] || "driver",
|
|
597
|
+
resource_id: status[:resource_id],
|
|
598
|
+
message: status[:message],
|
|
599
|
+
checked_at: status[:checked_at] || Time.now.utc.iso8601,
|
|
600
|
+
}.compact
|
|
641
601
|
end
|
|
642
602
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
# TODO: Document upgrade path and provide link
|
|
653
|
-
# warn("Driver authors: please read http://example.com for more details.")
|
|
654
|
-
driver.login_command(state)
|
|
603
|
+
def unknown_driver_status(message, error = nil)
|
|
604
|
+
{
|
|
605
|
+
live: nil,
|
|
606
|
+
state: "unknown",
|
|
607
|
+
source: "driver",
|
|
608
|
+
error:,
|
|
609
|
+
message:,
|
|
610
|
+
checked_at: Time.now.utc.iso8601,
|
|
611
|
+
}.compact
|
|
655
612
|
end
|
|
656
613
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
614
|
+
def transport_probe(state)
|
|
615
|
+
return not_created_transport_probe if state[:last_action].nil?
|
|
616
|
+
|
|
617
|
+
transport.connection(state, &:wait_until_ready)
|
|
618
|
+
{
|
|
619
|
+
reachable: true,
|
|
620
|
+
state: "reachable",
|
|
621
|
+
source: "transport",
|
|
622
|
+
message: "Transport connection succeeded",
|
|
623
|
+
checked_at: Time.now.utc.iso8601,
|
|
624
|
+
}
|
|
625
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
626
|
+
{
|
|
627
|
+
reachable: false,
|
|
628
|
+
state: "unreachable",
|
|
629
|
+
source: "transport",
|
|
630
|
+
error: e.class.name,
|
|
631
|
+
message: e.message,
|
|
632
|
+
checked_at: Time.now.utc.iso8601,
|
|
633
|
+
}
|
|
634
|
+
ensure
|
|
635
|
+
transport.cleanup! if transport
|
|
669
636
|
end
|
|
670
637
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
638
|
+
def not_created_transport_probe
|
|
639
|
+
{
|
|
640
|
+
reachable: false,
|
|
641
|
+
state: "not_created",
|
|
642
|
+
source: "transport",
|
|
643
|
+
message: "Instance has not yet been created",
|
|
644
|
+
checked_at: Time.now.utc.iso8601,
|
|
645
|
+
}
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# Coordinates one instance action, including state persistence, plugin
|
|
649
|
+
# serialization, and failure wrapping.
|
|
650
|
+
class ActionRunner
|
|
651
|
+
def initialize(instance, state_file)
|
|
652
|
+
@instance = instance
|
|
653
|
+
@state_file = state_file
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
def call(what, &block)
|
|
657
|
+
state = nil
|
|
658
|
+
begin
|
|
659
|
+
state = state_file.read
|
|
660
|
+
rescue ::StandardError => e
|
|
661
|
+
log_failure(what, e)
|
|
662
|
+
raise ActionFailed,
|
|
663
|
+
"Failed to complete ##{what} action: [#{e.message}]", e.backtrace
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
action_id = new_id
|
|
667
|
+
session_id = state[:instance_session_id] || new_id
|
|
668
|
+
instance.logger.with_metadata(
|
|
669
|
+
action: what.to_s,
|
|
670
|
+
action_id:,
|
|
671
|
+
instance_session_id: session_id,
|
|
672
|
+
kitchen_run_id: Kitchen.run_id
|
|
673
|
+
) do
|
|
674
|
+
elapsed = Benchmark.measure do
|
|
675
|
+
synchronize_or_call(what, state, &block)
|
|
676
|
+
end
|
|
677
|
+
record_attempt(state, what, action_id, session_id)
|
|
678
|
+
state[:last_action] = what.to_s
|
|
679
|
+
state[:last_error] = nil
|
|
680
|
+
elapsed
|
|
681
|
+
rescue ActionFailed => e
|
|
682
|
+
log_failure(what, e)
|
|
683
|
+
record_attempt(state, what, action_id, session_id)
|
|
684
|
+
state[:last_error] = e.class.name
|
|
685
|
+
raise(InstanceFailure, failure_message(what) +
|
|
686
|
+
" Please see .kitchen/logs/#{instance.name}.log for more details",
|
|
687
|
+
e.backtrace)
|
|
688
|
+
rescue ::StandardError => e
|
|
689
|
+
log_failure(what, e)
|
|
690
|
+
record_attempt(state, what, action_id, session_id)
|
|
691
|
+
state[:last_error] = e.class.name
|
|
692
|
+
raise ActionFailed,
|
|
693
|
+
"Failed to complete ##{what} action: [#{e.message}]", e.backtrace
|
|
694
|
+
ensure
|
|
695
|
+
state_file.write(state)
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
private
|
|
700
|
+
|
|
701
|
+
attr_reader :instance, :state_file
|
|
702
|
+
|
|
703
|
+
def synchronize_or_call(what, state)
|
|
704
|
+
plugin_class = plugin_class_for_action(what)
|
|
705
|
+
if Array(plugin_class.serial_actions).include?(what)
|
|
706
|
+
instance.debug("#{instance.to_str} is synchronizing on #{plugin_class}##{what}")
|
|
707
|
+
instance.class.mutexes[plugin_class].synchronize do
|
|
708
|
+
instance.debug("#{instance.to_str} is messaging #{plugin_class}##{what}")
|
|
709
|
+
yield(state)
|
|
710
|
+
end
|
|
711
|
+
else
|
|
712
|
+
yield(state)
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def plugin_class_for_action(what)
|
|
717
|
+
{
|
|
718
|
+
create: instance.driver,
|
|
719
|
+
setup: instance.transport,
|
|
720
|
+
converge: instance.provisioner,
|
|
721
|
+
verify: instance.verifier,
|
|
722
|
+
destroy: instance.driver,
|
|
723
|
+
}[what].class
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def log_failure(what, e)
|
|
727
|
+
return if instance.logger.logdev.nil? && instance.logger.structured_logdev.nil?
|
|
728
|
+
|
|
729
|
+
[instance.logger.logdev, instance.logger.structured_logdev].compact.each do |logdev|
|
|
730
|
+
logdev.error(failure_message(what))
|
|
731
|
+
Error.formatted_trace(e).each { |line| logdev.error(line) }
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def failure_message(what)
|
|
736
|
+
"#{what.capitalize} failed on instance #{instance.to_str}."
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def record_attempt(state, what, action_id, session_id)
|
|
740
|
+
state[:instance_session_id] = session_id
|
|
741
|
+
state[:last_attempted_action] = what.to_s
|
|
742
|
+
state[:last_action_id] = action_id
|
|
743
|
+
state[:last_kitchen_run_id] = Kitchen.run_id
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def new_id
|
|
747
|
+
SecureRandom.uuid
|
|
748
|
+
end
|
|
683
749
|
end
|
|
684
750
|
|
|
685
751
|
# The simplest finite state machine pseudo-implementation needed to manage
|
|
@@ -689,7 +755,7 @@ module Kitchen
|
|
|
689
755
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
690
756
|
class FSM
|
|
691
757
|
# Returns an Array of all transitions to bring an Instance from its last
|
|
692
|
-
# reported
|
|
758
|
+
# reported transitioned state into the desired transitioned state.
|
|
693
759
|
#
|
|
694
760
|
# @param last [String,Symbol,nil] the last known transitioned state of
|
|
695
761
|
# the Instance, defaulting to `nil` (for unknown or no history)
|
data/lib/kitchen/lazy_hash.rb
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
require "delegate"
|
|
19
19
|
|
|
20
20
|
module Kitchen
|
|
21
|
-
# A
|
|
21
|
+
# A modified Hash object that may contain callables as a value which must be
|
|
22
22
|
# executed in the context of another object. This allows for delayed
|
|
23
23
|
# evaluation of a hash value while still looking and largely feeling like a
|
|
24
24
|
# normal Ruby Hash.
|