# # Author:: Steven Murawski () # Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # 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. # class Chef class Application # These are the exit codes defined in Chef RFC 062 # https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md class ExitCode require_relative "../dist" # -1 is defined as DEPRECATED_FAILURE in RFC 062, so it is # not enumerated in an active constant. # VALID_RFC_062_EXIT_CODES = { SUCCESS: 0, GENERIC_FAILURE: 1, SIGINT_RECEIVED: 2, SIGTERM_RECEIVED: 3, REBOOT_SCHEDULED: 35, REBOOT_NEEDED: 37, REBOOT_FAILED: 41, # 42 was used by audit mode and should not be reused CLIENT_UPGRADED: 213, }.freeze DEPRECATED_RFC_062_EXIT_CODES = { DEPRECATED_FAILURE: -1, }.freeze class << self def normalize_exit_code(exit_code = nil) normalized_exit_code = normalize_legacy_exit_code(exit_code) if valid_exit_codes.include? normalized_exit_code normalized_exit_code else Chef::Log.warn(non_standard_exit_code_warning(normalized_exit_code)) VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] end end private def normalize_legacy_exit_code(exit_code) case exit_code when Integer exit_code when Exception lookup_exit_code_by_exception(exit_code) else VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] end end def lookup_exit_code_by_exception(exception) if sigint_received?(exception) VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED] elsif sigterm_received?(exception) VALID_RFC_062_EXIT_CODES[:SIGTERM_RECEIVED] elsif reboot_scheduled?(exception) VALID_RFC_062_EXIT_CODES[:REBOOT_SCHEDULED] elsif reboot_needed?(exception) VALID_RFC_062_EXIT_CODES[:REBOOT_NEEDED] elsif reboot_failed?(exception) VALID_RFC_062_EXIT_CODES[:REBOOT_FAILED] elsif client_upgraded?(exception) VALID_RFC_062_EXIT_CODES[:CLIENT_UPGRADED] else VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] end end def reboot_scheduled?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::Reboot end end def reboot_needed?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::RebootPending end end def reboot_failed?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::RebootFailed end end def client_upgraded?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::ClientUpgraded end end def sigint_received?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::SigInt end end def sigterm_received?(exception) resolve_exception_array(exception).any? do |e| e.is_a? Chef::Exceptions::SigTerm end end def resolve_exception_array(exception) exception_array = [exception] if exception.respond_to?(:wrapped_errors) exception.wrapped_errors.each do |e| exception_array.push e end end exception_array end def valid_exit_codes VALID_RFC_062_EXIT_CODES.values end def notify_on_deprecation(message) Chef.deprecated(:exit_code, message) rescue Chef::Exceptions::DeprecatedFeatureError # Have to rescue this, otherwise this unhandled error preempts # the current exit code assignment. end def non_standard_exit_code_warning(exit_code) "#{Chef::Dist::CLIENT} attempted to exit with a non-standard exit code of #{exit_code}." \ " The #{Chef::Dist::PRODUCT} Exit Codes design document (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md)" \ " defines the exit codes that should be used with #{Chef::Dist::CLIENT}. Chef::Application::ExitCode defines" \ " valid exit codes Non-standard exit codes are redefined as GENERIC_FAILURE." end end end end end