Skip to content

Commit cdbf21c

Browse files
committed
[chefctl] Add a new hook: skip_run?
This adds a new hook point (and sample plugin usage) that allows the Chef run to be skipped based on some local criteria. Example usage might be: - Device is on battery - Device is not connected to VPN/backhaul/etc. - Some global service meant to disable runs during an emergency Previously I did this in pre_run or pre_start, but the problem with that is that the only way is to force `exit`, which causes the logs to get messed up because we never update the links. This provides a clean way to skip the run but still update the chef.{cur,last} links so that it's clear what has happened. Sample output: ``` $ sudo chefctl -iv [2025-11-13 18:27:44 +1000] DEBUG chefctl: Loading plugin at /etc/cinc/chefctl_hooks.rb. [2025-11-13 18:27:44 +1000] DEBUG chefctl: Including registered plugin KrHook [2025-11-13 18:27:44 +1000] DEBUG chefctl: Trying lock /var/lock/subsys/chefctl [2025-11-13 18:27:44 +1000] DEBUG chefctl: Lock acquired: /var/lock/subsys/chefctl [2025-11-13 18:27:44 +1000] INFO chefctl: taste-tester mode ends in < 1 hour, extending back to 1 hour [2025-11-13 18:27:44 +1000] DEBUG chefctl: Skippinbg battery check due to --immediate flag ``` and ``` $ sudo chefctl [2025-11-13 18:27:22 +1000] INFO chefctl: taste-tester mode ends in < 1 hour, extending back to 1 hour [2025-11-13 18:27:22 +1000] WARN chefctl: Running on battery power, skipping Chef run [2025-11-13 18:27:22 +1000] INFO chefctl: Plugin requested skipping chef run. ``` Signed-off-by: Phil Dibowitz <phil@ipom.com>
1 parent ee5c31f commit cdbf21c

File tree

2 files changed

+85
-5
lines changed

2 files changed

+85
-5
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2025-present Facebook
2+
# Copyright 2025-present Phil Dibowitz
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# This hook file is located at '/etc/chef/chefctl_hooks.rb'
17+
# You can change this location by passing `-p/--plugin-path` to `chefctl`,
18+
# or by setting `plugin_path` in `chefctl-config.rb`
19+
20+
# Below is a sample hook file that will skip runs when the device is on
21+
# battery.
22+
#
23+
# Hook descriptions and empty hooks have been removed. Refer to the hook
24+
# documentation for descriptions of each method.
25+
26+
module SkipOnBattery
27+
def skip_run?
28+
# If a human is requesting the run, they want the run, so don't worry
29+
# about the battery check.
30+
#
31+
# Another option would be to use the cli_options hook to add an
32+
# override flag, if desired.
33+
if Chefctl::Config.immediate
34+
Chefctl.logger.debug('Skipping battery check due to --immediate flag')
35+
return false
36+
end
37+
38+
if File.exist?('/sys/class/power_supply/BAT0/status')
39+
bat_status = File.read(
40+
'/sys/class/power_supply/BAT0/status',
41+
).strip
42+
# NOTE: the logger level is not actually set at this point,
43+
# annoyingly, though it should be, so this message won't be
44+
# seen, but we're keeping it here for correctness sake
45+
Chefctl.logger.debug("Battery status: #{bat_status}")
46+
if bat_status == 'Discharging'
47+
Chefctl.logger.warn('Running on battery power, skipping Chef run')
48+
return true
49+
end
50+
end
51+
false
52+
end
53+
end

chefctl/src/chefctl.rb

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,25 @@ def generate_certs
269269
end
270270
end
271271

272+
# Called after the lock is acquired, before pre_run (and this before
273+
# the chef run is started). This hook is intended to allow suppressing
274+
# Chef runs under specific conditions. Examples might include:
275+
#
276+
# - Device is on battery
277+
# - Device is not connected to VPN/backhaul/etc.
278+
# - Some global service meant to disable runs during an emergency
279+
#
280+
# Defaults, of course, to false. Should be used with care. While we _will_
281+
# log that the chef run was skipped at the request of the plugin, the plugin
282+
# should log _why_ it was skipped so that it appears in the log.
283+
#
284+
# Note that if the return value is an Integer, that integer is used as
285+
# the exit value of chefctl. If it's `true`, the exit value is 0, and
286+
# if it is `false`, chef runs normally.
287+
def skip_run?
288+
false
289+
end
290+
272291
# Called after the lock is acquired, before the chef run is started.
273292
# Parameters:
274293
# - output is the path to the log file for the chef run
@@ -885,11 +904,19 @@ def chef_run
885904

886905
symlink_output(:chef_cur)
887906

888-
do_splay unless Chefctl::Config.immediate
889-
890-
plugin.pre_run(@paths[:out])
891-
892-
retval = do_chef_runs
907+
ret = plugin.skip_run?
908+
if ret.is_a?(FalseClass)
909+
do_splay unless Chefctl::Config.immediate
910+
plugin.pre_run(@paths[:out])
911+
retval = do_chef_runs
912+
else
913+
Chefctl.logger.info('Plugin requested skipping chef run.')
914+
# if it's an integer, use that as the exit code, otherwise
915+
# we keep it 0, indicating success
916+
if ret.is_a?(Integer)
917+
retval = ret
918+
end
919+
end
893920

894921
plugin.post_run(@paths[:out], retval)
895922

0 commit comments

Comments
 (0)