Skip to content

Commit 7e3a0aa

Browse files
committed
Merge pull request chef#4611 from chef/smurawski/rfc062-exit-status-chef-client
RFC062 exit status chef client
2 parents e4bca44 + e24b8e2 commit 7e3a0aa

File tree

13 files changed

+770
-35
lines changed

13 files changed

+770
-35
lines changed

Diff for: lib/chef/application.rb

+14-9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
require "mixlib/cli"
2828
require "tmpdir"
2929
require "rbconfig"
30+
require "chef/application/exit_code"
3031

3132
class Chef
3233
class Application
@@ -60,11 +61,11 @@ def run
6061

6162
def setup_signal_handlers
6263
trap("INT") do
63-
Chef::Application.fatal!("SIGINT received, stopping", 2)
64+
Chef::Application.fatal!("SIGINT received, stopping", Chef::Exceptions::SigInt.new)
6465
end
6566

6667
trap("TERM") do
67-
Chef::Application.fatal!("SIGTERM received, stopping", 3)
68+
Chef::Application.fatal!("SIGTERM received, stopping", Chef::Exceptions::SigTerm.new)
6869
end
6970

7071
unless Chef::Platform.windows?
@@ -149,7 +150,7 @@ def configure_logging
149150
Chef::Log.level = resolve_log_level
150151
rescue StandardError => error
151152
Chef::Log.fatal("Failed to open or create log file at #{Chef::Config[:log_location]}: #{error.class} (#{error.message})")
152-
Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", 2)
153+
Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", error)
153154
end
154155

155156
# Turn `log_location :syslog` and `log_location :win_evt` into the
@@ -285,7 +286,7 @@ def fork_chef_client
285286
@chef_client.run
286287
rescue Exception => e
287288
Chef::Log.error(e.to_s)
288-
exit 1
289+
exit Chef::Application.normalize_exit_code(e)
289290
else
290291
exit 0
291292
end
@@ -314,7 +315,7 @@ def apply_config(config_content, config_file_path)
314315
Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
315316
filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
316317
filtered_trace.each { |line| Chef::Log.fatal(" " + line ) }
317-
Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
318+
Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", error)
318319
end
319320

320321
# This is a hook for testing
@@ -341,15 +342,19 @@ def debug_stacktrace(e)
341342
true
342343
end
343344

345+
def normalize_exit_code(exit_code)
346+
Chef::Application::ExitCode.normalize_exit_code(exit_code)
347+
end
348+
344349
# Log a fatal error message to both STDERR and the Logger, exit the application
345-
def fatal!(msg, err = -1)
350+
def fatal!(msg, err = nil)
346351
Chef::Log.fatal(msg)
347-
Process.exit err
352+
Process.exit(normalize_exit_code(err))
348353
end
349354

350-
def exit!(msg, err = -1)
355+
def exit!(msg, err = nil)
351356
Chef::Log.debug(msg)
352-
Process.exit err
357+
Process.exit(normalize_exit_code(err))
353358
end
354359
end
355360

Diff for: lib/chef/application/apply.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ def parse_json
137137

138138
def read_recipe_file(file_name)
139139
if file_name.nil?
140-
Chef::Application.fatal!("No recipe file was provided", 1)
140+
Chef::Application.fatal!("No recipe file was provided", Chef::Exceptions::RecipeNotFound.new)
141141
else
142142
recipe_path = File.expand_path(file_name)
143143
unless File.exist?(recipe_path)
144-
Chef::Application.fatal!("No file exists at #{recipe_path}", 1)
144+
Chef::Application.fatal!("No file exists at #{recipe_path}", Chef::Exceptions::RecipeNotFound.new)
145145
end
146146
recipe_fh = open(recipe_path)
147147
recipe_text = recipe_fh.read
@@ -183,7 +183,7 @@ def run_chef_recipe
183183
else
184184
if !ARGV[0]
185185
puts opt_parser
186-
Chef::Application.exit! "No recipe file provided", 1
186+
Chef::Application.exit! "No recipe file provided", Chef::Exceptions::RecipeNotFound.new
187187
end
188188
@recipe_filename = ARGV[0]
189189
@recipe_text, @recipe_fh = read_recipe_file @recipe_filename
@@ -208,7 +208,7 @@ def run_application
208208
raise
209209
rescue Exception => e
210210
Chef::Application.debug_stacktrace(e)
211-
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
211+
Chef::Application.fatal!("#{e.class}: #{e.message}", e)
212212
end
213213
end
214214

Diff for: lib/chef/application/client.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def reconfigure
324324

325325
if Chef::Config[:recipe_url]
326326
if !Chef::Config.local_mode
327-
Chef::Application.fatal!("chef-client recipe-url can be used only in local-mode", 1)
327+
Chef::Application.fatal!("chef-client recipe-url can be used only in local-mode")
328328
else
329329
if Chef::Config[:delete_entire_chef_repo]
330330
Chef::Log.debug "Cleanup path #{Chef::Config.chef_repo_path} before extract recipes into it"
@@ -420,7 +420,7 @@ def run_application
420420
rescue SystemExit
421421
raise
422422
rescue Exception => e
423-
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
423+
Chef::Application.fatal!("#{e.class}: #{e.message}", e)
424424
end
425425
else
426426
interval_run_chef_client
@@ -463,7 +463,7 @@ def sleep_then_run_chef_client(sleep_sec)
463463
retry
464464
end
465465

466-
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
466+
Chef::Application.fatal!("#{e.class}: #{e.message}", e)
467467
end
468468

469469
def test_signal

Diff for: lib/chef/application/exit_code.rb

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
#
2+
# Author:: Steven Murawski (<[email protected]>)
3+
# Copyright:: Copyright 2016, Chef Software, Inc.
4+
# License:: Apache License, Version 2.0
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
class Chef
20+
class Application
21+
22+
# These are the exit codes defined in Chef RFC 062
23+
# https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md
24+
class ExitCode
25+
26+
# -1 is defined as DEPRECATED_FAILURE in RFC 062, so it is
27+
# not enumerated in an active constant.
28+
#
29+
VALID_RFC_062_EXIT_CODES = {
30+
SUCCESS: 0,
31+
GENERIC_FAILURE: 1,
32+
SIGINT_RECEIVED: 2,
33+
SIGTERM_RECEIVED: 3,
34+
REBOOT_SCHEDULED: 35,
35+
REBOOT_NEEDED: 37,
36+
REBOOT_FAILED: 41,
37+
AUDIT_MODE_FAILURE: 42,
38+
}
39+
40+
DEPRECATED_RFC_062_EXIT_CODES = {
41+
DEPRECATED_FAILURE: -1,
42+
}
43+
44+
class << self
45+
46+
def normalize_exit_code(exit_code = nil)
47+
if normalization_not_configured?
48+
normalize_legacy_exit_code_with_warning(exit_code)
49+
elsif normalization_disabled?
50+
normalize_legacy_exit_code(exit_code)
51+
else
52+
normalize_exit_code_to_rfc(exit_code)
53+
end
54+
end
55+
56+
def enforce_rfc_062_exit_codes?
57+
!normalization_disabled? && !normalization_not_configured?
58+
end
59+
60+
def notify_reboot_exit_code_deprecation
61+
return if normalization_disabled?
62+
notify_on_deprecation(reboot_deprecation_warning)
63+
end
64+
65+
def notify_deprecated_exit_code
66+
return if normalization_disabled?
67+
notify_on_deprecation(deprecation_warning)
68+
end
69+
70+
private
71+
72+
def normalization_disabled?
73+
Chef::Config[:exit_status] == :disabled
74+
end
75+
76+
def normalization_not_configured?
77+
Chef::Config[:exit_status].nil?
78+
end
79+
80+
def normalize_legacy_exit_code_with_warning(exit_code)
81+
normalized_exit_code = normalize_legacy_exit_code(exit_code)
82+
unless valid_exit_codes.include? normalized_exit_code
83+
notify_on_deprecation(deprecation_warning)
84+
end
85+
normalized_exit_code
86+
end
87+
88+
def normalize_legacy_exit_code(exit_code)
89+
case exit_code
90+
when Fixnum
91+
exit_code
92+
when Exception
93+
lookup_exit_code_by_exception(exit_code)
94+
else
95+
default_exit_code
96+
end
97+
end
98+
99+
def normalize_exit_code_to_rfc(exit_code)
100+
normalized_exit_code = normalize_legacy_exit_code_with_warning(exit_code)
101+
if valid_exit_codes.include? normalized_exit_code
102+
normalized_exit_code
103+
else
104+
VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
105+
end
106+
end
107+
108+
def lookup_exit_code_by_exception(exception)
109+
if sigint_received?(exception)
110+
VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED]
111+
elsif sigterm_received?(exception)
112+
VALID_RFC_062_EXIT_CODES[:SIGTERM_RECEIVED]
113+
elsif normalization_disabled? || normalization_not_configured?
114+
if legacy_exit_code?(exception)
115+
# We have lots of "Chef::Application.fatal!('', 2)
116+
# This maintains that behavior at initial introduction
117+
# and when the RFC exit_status compliance is disabled.
118+
VALID_RFC_062_EXIT_CODES[:SIGINT_RECEIVED]
119+
else
120+
VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
121+
end
122+
elsif reboot_scheduled?(exception)
123+
VALID_RFC_062_EXIT_CODES[:REBOOT_SCHEDULED]
124+
elsif reboot_needed?(exception)
125+
VALID_RFC_062_EXIT_CODES[:REBOOT_NEEDED]
126+
elsif reboot_failed?(exception)
127+
VALID_RFC_062_EXIT_CODES[:REBOOT_FAILED]
128+
elsif audit_failure?(exception)
129+
VALID_RFC_062_EXIT_CODES[:AUDIT_MODE_FAILURE]
130+
else
131+
VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
132+
end
133+
end
134+
135+
def legacy_exit_code?(exception)
136+
resolve_exception_array(exception).any? do |e|
137+
e.is_a? Chef::Exceptions::DeprecatedExitCode
138+
end
139+
end
140+
141+
def reboot_scheduled?(exception)
142+
resolve_exception_array(exception).any? do |e|
143+
e.is_a? Chef::Exceptions::Reboot
144+
end
145+
end
146+
147+
def reboot_needed?(exception)
148+
resolve_exception_array(exception).any? do |e|
149+
e.is_a? Chef::Exceptions::RebootPending
150+
end
151+
end
152+
153+
def reboot_failed?(exception)
154+
resolve_exception_array(exception).any? do |e|
155+
e.is_a? Chef::Exceptions::RebootFailed
156+
end
157+
end
158+
159+
def audit_failure?(exception)
160+
resolve_exception_array(exception).any? do |e|
161+
e.is_a? Chef::Exceptions::AuditError
162+
end
163+
end
164+
165+
def sigint_received?(exception)
166+
resolve_exception_array(exception).any? do |e|
167+
e.is_a? Chef::Exceptions::SigInt
168+
end
169+
end
170+
171+
def sigterm_received?(exception)
172+
resolve_exception_array(exception).any? do |e|
173+
e.is_a? Chef::Exceptions::SigTerm
174+
end
175+
end
176+
177+
def resolve_exception_array(exception)
178+
exception_array = [exception]
179+
if exception.respond_to?(:wrapped_errors)
180+
exception.wrapped_errors.each do |e|
181+
exception_array.push e
182+
end
183+
end
184+
exception_array
185+
end
186+
187+
def valid_exit_codes
188+
VALID_RFC_062_EXIT_CODES.values
189+
end
190+
191+
def notify_on_deprecation(message)
192+
begin
193+
Chef.log_deprecation(message)
194+
rescue Chef::Exceptions::DeprecatedFeatureError
195+
# Have to rescue this, otherwise this unhandled error preempts
196+
# the current exit code assignment.
197+
end
198+
end
199+
200+
def deprecation_warning
201+
"Chef RFC 062 (https://github.com/chef/chef-rfc/master/rfc062-exit-status.md) defines the" \
202+
" exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \
203+
" In a future release, non-standard exit codes will be redefined as" \
204+
" GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb."
205+
end
206+
207+
def reboot_deprecation_warning
208+
"Per RFC 062 (https://github.com/chef/chef-rfc/blob/master/rfc062-exit-status.md)" \
209+
", when a reboot is requested Chef Client will exit with an exit code of 35, REBOOT_SCHEDULED." \
210+
" To maintain the current behavior (an exit code of 0), you will need to set `exit_status` to" \
211+
" `:disabled` in your client.rb"
212+
end
213+
214+
def default_exit_code
215+
if normalization_disabled? || normalization_not_configured?
216+
return DEPRECATED_RFC_062_EXIT_CODES[:DEPRECATED_FAILURE]
217+
else
218+
VALID_RFC_062_EXIT_CODES[:GENERIC_FAILURE]
219+
end
220+
end
221+
222+
end
223+
end
224+
225+
end
226+
end

Diff for: lib/chef/application/solo.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ def run_application
285285
rescue SystemExit
286286
raise
287287
rescue Exception => e
288-
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
288+
Chef::Application.fatal!("#{e.class}: #{e.message}", e)
289289
end
290290
else
291291
interval_run_chef_client
@@ -332,7 +332,7 @@ def interval_run_chef_client
332332
Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
333333
retry
334334
else
335-
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
335+
Chef::Application.fatal!("#{e.class}: #{e.message}", e)
336336
end
337337
end
338338
end

Diff for: lib/chef/application/windows_service.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,11 @@ def configure_chef(startup_parameters)
319319

320320
Chef::Config.merge!(config)
321321
rescue SocketError
322-
Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", 2)
322+
Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", Chef::Exceptions::DeprecatedExitCode.new)
323323
rescue Chef::Exceptions::ConfigurationError => error
324-
Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
324+
Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
325325
rescue Exception => error
326-
Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
326+
Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new)
327327
end
328328
end
329329

0 commit comments

Comments
 (0)