diff options
| author | Steven Murawski <steven.murawski@gmail.com> | 2016-02-18 14:58:32 -0600 |
|---|---|---|
| committer | Steven Murawski <steven.murawski@gmail.com> | 2016-05-23 13:32:49 -0500 |
| commit | e24b8e2cd67aef572fde184e8b07d48333184b29 (patch) | |
| tree | 2b1f6507104149182791436be30561be3be1aef2 /lib/chef/application/exit_code.rb | |
| parent | e4bca443a3ddca6381275a1a252de04f5e4883c4 (diff) | |
| download | chef-e24b8e2cd67aef572fde184e8b07d48333184b29.tar.gz | |
* define exit codes
* exit code functional specs
* audit exit codes
* reboot now/reboot failed/reboot pending exit codes
* Deal with forked and unforked process and get the right exit code
* Reboot Now should really be reboot scheduled
* pass exception rather than exit code
* updated with sigint and sigterm
* support legacy fatal!("", 2) behavior
* fixup all fatal! and exit! calls
Diffstat (limited to 'lib/chef/application/exit_code.rb')
| -rw-r--r-- | lib/chef/application/exit_code.rb | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/lib/chef/application/exit_code.rb b/lib/chef/application/exit_code.rb new file mode 100644 index 0000000000..753f1a0d80 --- /dev/null +++ b/lib/chef/application/exit_code.rb @@ -0,0 +1,226 @@ +# +# Author:: Steven Murawski (<smurawski@chef.io>) +# Copyright:: Copyright 2016, 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 + + # -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, + AUDIT_MODE_FAILURE: 42, + } + + DEPRECATED_RFC_062_EXIT_CODES = { + DEPRECATED_FAILURE: -1, + } + + class << self + + def normalize_exit_code(exit_code = nil) + if normalization_not_configured? + normalize_legacy_exit_code_with_warning(exit_code) + elsif normalization_disabled? + normalize_legacy_exit_code(exit_code) + else + normalize_exit_code_to_rfc(exit_code) + end + end + + def enforce_rfc_062_exit_codes? + !normalization_disabled? && !normalization_not_configured? + end + + def notify_reboot_exit_code_deprecation + return if normalization_disabled? + notify_on_deprecation(reboot_deprecation_warning) + end + + def notify_deprecated_exit_code + return if normalization_disabled? + notify_on_deprecation(deprecation_warning) + end + + private + + def normalization_disabled? + Chef::Config[:exit_status] == :disabled + end + + def normalization_not_configured? + Chef::Config[:exit_status].nil? + end + + def normalize_legacy_exit_code_with_warning(exit_code) + normalized_exit_code = normalize_legacy_exit_code(exit_code) + unless valid_exit_codes.include? normalized_exit_code + notify_on_deprecation(deprecation_warning) + end + normalized_exit_code + end + + def normalize_legacy_exit_code(exit_code) + case exit_code + when Fixnum + exit_code + when Exception + lookup_exit_code_by_exception(exit_code) + else + default_exit_code + end + end + + def normalize_exit_code_to_rfc(exit_code) + normalized_exit_code = normalize_legacy_exit_code_with_warning(exit_code) + if valid_exit_codes.include? normalized_exit_code + normalized_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 normalization_disabled? || normalization_not_configured? + if legacy_exit_code?(exception) + # We have lots of "Chef::Application.fatal!('', 2) + # This maintains that behavior at initial introduction + # and when the RFC exit_status compliance is disabled. + VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED] + else + VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] + end + 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 audit_failure?(exception) + VALID_RFC_062_EXIT_CODES[:AUDIT_MODE_FAILURE] + else + VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] + end + end + + def legacy_exit_code?(exception) + resolve_exception_array(exception).any? do |e| + e.is_a? Chef::Exceptions::DeprecatedExitCode + 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 audit_failure?(exception) + resolve_exception_array(exception).any? do |e| + e.is_a? Chef::Exceptions::AuditError + 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) + begin + Chef.log_deprecation(message) + rescue Chef::Exceptions::DeprecatedFeatureError + # Have to rescue this, otherwise this unhandled error preempts + # the current exit code assignment. + end + end + + def deprecation_warning + "Chef RFC 062 (https://github.com/chef/chef-rfc/master/rfc062-exit-status.md) defines the" \ + " exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \ + " In a future release, non-standard exit codes will be redefined as" \ + " GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb." + end + + def reboot_deprecation_warning + "Per RFC 062 (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md)" \ + ", when a reboot is requested Chef Client will exit with an exit code of 35, REBOOT_SCHEDULED." \ + " To maintain the current behavior (an exit code of 0), you will need to set `exit_status` to" \ + " `:disabled` in your client.rb" + end + + def default_exit_code + if normalization_disabled? || normalization_not_configured? + return DEPRECATED_RFC_062_EXIT_CODES[:DEPRECATED_FAILURE] + else + VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE] + end + end + + end + end + + end +end |
