From c5257f31bff9511193ff362419dc4f88922efb66 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Mon, 16 Oct 2017 09:34:35 -0700 Subject: [PATCH 1/2] Initial scaffolding for the `kitchen doctor` command. --- lib/kitchen/cli.rb | 10 +++++++++ lib/kitchen/command/doctor.rb | 39 +++++++++++++++++++++++++++++++++ lib/kitchen/driver/base.rb | 8 +++++++ lib/kitchen/instance.rb | 10 +++++++++ lib/kitchen/provisioner/base.rb | 8 +++++++ lib/kitchen/transport/base.rb | 8 +++++++ lib/kitchen/verifier/base.rb | 8 +++++++ 7 files changed, 91 insertions(+) create mode 100644 lib/kitchen/command/doctor.rb diff --git a/lib/kitchen/cli.rb b/lib/kitchen/cli.rb index 379966597..937d9d146 100644 --- a/lib/kitchen/cli.rb +++ b/lib/kitchen/cli.rb @@ -248,6 +248,16 @@ def package(*args) perform("package", "package", args) end + desc "doctor INSTANCE|REGEXP", "Check for common system problems" + log_options + method_option :all, + aliases: "-a", + desc: "Check all instances" + def doctor(*args) + update_config! + perform("doctor", "doctor", args) + end + desc "exec INSTANCE|REGEXP -c REMOTE_COMMAND", "Execute command on one or more instance" method_option :command, diff --git a/lib/kitchen/command/doctor.rb b/lib/kitchen/command/doctor.rb new file mode 100644 index 000000000..dd196ca48 --- /dev/null +++ b/lib/kitchen/command/doctor.rb @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- +# +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "kitchen/command" + +module Kitchen + module Command + # Check for common system or configuration problems. + # + class Doctor < Kitchen::Command::Base + # Invoke the command. + def call + results = parse_subcommand(args.first) + if results.empty? + error("No instances configured, cannot check configuration. Please check your .kitchen.yml and confirm it has platform and suites sections.") + exit(1) + end + # By default only doctor the first instance to avoid output spam. + results = [results.first] unless options[:all] + results.each do |instance| + debug "Doctor on #{instance.name}." + instance.doctor_action + end + end + end + end +end diff --git a/lib/kitchen/driver/base.rb b/lib/kitchen/driver/base.rb index 6fded7172..29c05a5ed 100644 --- a/lib/kitchen/driver/base.rb +++ b/lib/kitchen/driver/base.rb @@ -56,6 +56,14 @@ def destroy(state) # rubocop:disable Lint/UnusedMethodArgument def package(state) # rubocop:disable Lint/UnusedMethodArgument end + # Check system and configuration for common errors. + # + # @param state [Hash] mutable instance and driver state + # @returns [Boolean] Return true if a problem is found. + def doctor(state) + false + end + class << self # @return [Array] an array of action method names that cannot # be run concurrently and must be run in serial via a shared mutex diff --git a/lib/kitchen/instance.rb b/lib/kitchen/instance.rb index 4001581fd..aa446a29c 100644 --- a/lib/kitchen/instance.rb +++ b/lib/kitchen/instance.rb @@ -233,6 +233,16 @@ def package_action driver.package(state_file.read) end + # Check system and configuration for common errors. + # + def doctor_action + banner "The doctor is in" + failed = [driver, provisioner, transport, verifier].any? do |obj| + obj.doctor(state_file.read) + end + exit(1) if failed + end + # Returns a Hash of configuration and other useful diagnostic information. # # @return [Hash] a diagnostic hash diff --git a/lib/kitchen/provisioner/base.rb b/lib/kitchen/provisioner/base.rb index 1da73605d..97039444a 100644 --- a/lib/kitchen/provisioner/base.rb +++ b/lib/kitchen/provisioner/base.rb @@ -85,6 +85,14 @@ def call(state) cleanup_sandbox end + # Check system and configuration for common errors. + # + # @param state [Hash] mutable instance state + # @returns [Boolean] Return true if a problem is found. + def doctor(state) + false + end + # Generates a command string which will install and configure the # provisioner software on an instance. If no work is required, then `nil` # will be returned. diff --git a/lib/kitchen/transport/base.rb b/lib/kitchen/transport/base.rb index bc5244238..cf793df78 100644 --- a/lib/kitchen/transport/base.rb +++ b/lib/kitchen/transport/base.rb @@ -64,6 +64,14 @@ def connection(state) raise ClientError, "#{self.class}#connection must be implemented" end + # Check system and configuration for common errors. + # + # @param state [Hash] mutable instance state + # @returns [Boolean] Return true if a problem is found. + def doctor(state) + false + end + # Closes the connection, if it is still active. # # @return [void] diff --git a/lib/kitchen/verifier/base.rb b/lib/kitchen/verifier/base.rb index 0698daa14..68e0193d0 100644 --- a/lib/kitchen/verifier/base.rb +++ b/lib/kitchen/verifier/base.rb @@ -82,6 +82,14 @@ def call(state) cleanup_sandbox end + # Check system and configuration for common errors. + # + # @param state [Hash] mutable instance state + # @returns [Boolean] Return true if a problem is found. + def doctor(state) + false + end + # Deletes the sandbox path. Without calling this method, the sandbox path # will persist after the process terminates. In other words, cleanup is # explicit. This method is safe to call multiple times. From 9b75b7a6a852a8b5010d80d80689cdbf44cf6e6b Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Mon, 16 Oct 2017 09:46:52 -0700 Subject: [PATCH 2/2] Fix behavior of doctor --all. --- lib/kitchen/command/doctor.rb | 3 ++- lib/kitchen/instance.rb | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/kitchen/command/doctor.rb b/lib/kitchen/command/doctor.rb index dd196ca48..5e5c0a62e 100644 --- a/lib/kitchen/command/doctor.rb +++ b/lib/kitchen/command/doctor.rb @@ -29,10 +29,11 @@ def call end # By default only doctor the first instance to avoid output spam. results = [results.first] unless options[:all] - results.each do |instance| + failed = results.any? do |instance| debug "Doctor on #{instance.name}." instance.doctor_action end + exit(1) if failed end end end diff --git a/lib/kitchen/instance.rb b/lib/kitchen/instance.rb index aa446a29c..f30c00253 100644 --- a/lib/kitchen/instance.rb +++ b/lib/kitchen/instance.rb @@ -237,10 +237,9 @@ def package_action # def doctor_action banner "The doctor is in" - failed = [driver, provisioner, transport, verifier].any? do |obj| + [driver, provisioner, transport, verifier].any? do |obj| obj.doctor(state_file.read) end - exit(1) if failed end # Returns a Hash of configuration and other useful diagnostic information.