Skip to content

Commit 7890004

Browse files
George HoltGeorge Holt
authored andcommitted
[chef-client] Enable chef-client scheduled task to behave like cron, with predictable splay and start time
Signed-off-by: George Holt <[email protected]>
1 parent 8556de0 commit 7890004

File tree

14 files changed

+182
-35
lines changed

14 files changed

+182
-35
lines changed

.github/workflows/kitchen.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ jobs:
3636
- name: Check out cookbook code
3737
uses: actions/checkout@master
3838
- name: Install Chef Infra Client
39-
uses: actionshub/chef-install@master
39+
uses: actionshub/chef-install@main
4040
- name: Dokken
41-
uses: actionshub/kitchen-dokken@master
41+
uses: actionshub/kitchen-dokken@main
4242
env:
4343
CHEF_LICENSE: accept-no-persist
4444
KITCHEN_LOCAL_YAML: kitchen.dokken.yml

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ The chef_client_scheduled_task resource setups up Chef Infra Client to run as a
5454
- `frequency_modifier` Numeric value to go with the scheduled task frequency - default: '30'
5555
- `start_time` The start time for the task in HH:mm format (ex: 14:00). If the `frequency` is `minute` default start time will be `Time.now` plus the `frequency_modifier` number of minutes.
5656
- `start_date` - The start date for the task in `m:d:Y` format (ex: 12/17/2017). nil by default and isn't necessary if you're running a regular interval.
57-
- `splay` - A random number of seconds between 0 and X to add to interval. default: '300'
57+
- `splay` - A random number of seconds between 0 and X to add to interval. Note splay is applied differently when use_consistent_splay is set to true. default: '300'
5858
- `config_directory` - The path to the Chef config directory. default: 'C:/chef'
5959
- `log_file_name` - The name of the log file. default: 'client.log'
6060
- `log_directory` - The path to the Chef log directory. default: 'CONFIG_DIRECTORY/log'
6161
- `chef_binary_path` - The path to the chef-client binary. default: 'C:/opscode/chef/bin/chef-client'
6262
- `daemon_options` - An optional array of extra options to pass to the chef-client
6363
- `task_name` - The name of the scheduled task. This allows for multiple chef_client_scheduled_task resources when it is used directly like in a wrapper cookbook. default: 'chef-client'
64+
- `use_consistent_splay` - Indicates that the randomly computed splay should remain consistent for a given node, similar to how it functions in cron resource. default: false
65+
- `snap_time_to_frequency` - Indicates that the start day and time for the task should be snapped to start at the next frequency cycle after the previous top of the hour. For example if the current time is 14:07 and the frequency_modifier is 30, the next task start time should be 14:30. Only applicable when frequency = 'minute'. default: false
6466

6567
### chef_client_cron
6668

@@ -244,7 +246,7 @@ Use this recipe to run chef-client as a cron job rather than as a service. The c
244246

245247
### task
246248

247-
Use this recipe to run chef-client on Windows nodes as a scheduled task. Without modifying attributes the scheduled task will run 30 minutes after the recipe runs, with each chef run rescheduling the run 30 minutes in the future. By default the job runs as the system user. The time period between runs can be modified with the `default['chef_client']['task']['frequency_modifier']` attribute and the user can be changed with the `default['chef_client']['task']['user']` and `default['chef_client']['task']['password']` attributes.
249+
Use this recipe to run chef-client on Windows nodes as a scheduled task. Without modifying attributes the scheduled task will run 30 minutes after the recipe runs, with each chef run rescheduling the run 30 minutes in the future. By default the job runs as the system user. The time period between runs can be modified with the `default['chef_client']['task']['frequency_modifier']` attribute and the user can be changed with the `default['chef_client']['task']['user']` and `default['chef_client']['task']['password']` attributes. For a scheduled task that behaves more like the chef-client cron job, the snap_time_to_frequency and use_consistent_splay properties can be set to true.
248250

249251
## Usage
250252

attributes/default.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
'weekday' => '*',
5454
'path' => nil,
5555
'environment_variables' => nil,
56-
'log_directory' => nil,
57-
'log_file' => '/dev/null',
56+
'log_directory' => '/dev',
57+
'log_file' => 'null',
5858
'append_log' => false,
5959
'use_cron_d' => false,
6060
'mailto' => nil,
@@ -79,6 +79,8 @@
7979
default['chef_client']['task']['start_time'] = nil
8080
default['chef_client']['task']['start_date'] = nil
8181
default['chef_client']['task']['name'] = 'chef-client'
82+
default['chef_client']['task']['use_consistent_splay'] = false
83+
default['chef_client']['task']['snap_time_to_frequency'] = false
8284

8385
default['chef_client']['load_gems'] = {}
8486

kitchen.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,8 @@ suites:
118118
run_list:
119119
- recipe[test::task]
120120
includes: ["windows-2012r2-13", "windows-2012r2-14", "windows-2016", "windows-2019"]
121+
122+
- name: emulate-cron-task
123+
run_list:
124+
- recipe[test::emulate_cron_task]
125+
includes: ["windows-2012r2-13", "windows-2012r2-14", "windows-2016", "windows-2019"]

libraries/helpers.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ module Helpers
2424
include Chef::Mixin::Which
2525
require 'digest/md5'
2626

27+
#
28+
# Snaps the start time of the scheduled task to the next start of the frequency cycle, where the frequency cycle begins
29+
# at the top of the hour and increments by frequency_modifier in minutes
30+
#
31+
# @param [Integer] frequency_modifier - The frequency modifier in minutes
32+
# @return [Time] The next snap-to time on the frequency cycle computed from top of the previous hour
33+
#
34+
def snap_time(frequency_modifier)
35+
ref_time = Time.now
36+
snap = Time.new(ref_time.year, ref_time.mon, ref_time.day, ref_time.hour, 0, 0)
37+
minutes = (ref_time.min + frequency_modifier) - (ref_time.min % frequency_modifier)
38+
snap + (minutes * 60)
39+
end
40+
2741
def wmi_property_from_query(wmi_property, wmi_query)
2842
@wmi = ::WIN32OLE.connect('winmgmts://')
2943
result = @wmi.ExecQuery(wmi_query)

recipes/task.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class ::Chef::Recipe
4141
chef_binary_path node['chef_client']['bin']
4242
daemon_options node['chef_client']['daemon_options']
4343
task_name node['chef_client']['task']['name']
44+
use_consistent_splay node['chef_client']['task']['use_consistent_splay']
45+
snap_time_to_frequency node['chef_client']['task']['snap_time_to_frequency']
4446
end
4547

4648
windows_service 'chef-client' do

resources/cron.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
chef_version_for_provides '< 16.0' if respond_to?(:chef_version_for_provides)
2121

2222
provides :chef_client_cron
23+
unified_mode true
2324
resource_name :chef_client_cron
2425

2526
property :job_name, String, default: 'chef-client'
@@ -122,6 +123,7 @@ def log_command
122123
# @return [String]
123124
#
124125
def log_path
126+
puts "Log directory='#{new_resource.log_directory}'"
125127
return new_resource.log_file_name if new_resource.log_directory.nil?
126128
::File.join(new_resource.log_directory, new_resource.log_file_name)
127129
end

resources/scheduled_task.rb

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
chef_version_for_provides '< 16.0' if respond_to?(:chef_version_for_provides)
2121

2222
provides :chef_client_scheduled_task
23+
unified_mode true
2324
resource_name :chef_client_scheduled_task
2425

2526
property :task_name, String,
@@ -40,6 +41,9 @@
4041
callbacks: { 'should be a positive number' => proc { |v| v > 0 } },
4142
default: lazy { frequency == 'minute' ? 30 : 1 }
4243

44+
property :snap_time_to_frequency, [true, false],
45+
default: false
46+
4347
property :accept_chef_license, [true, false],
4448
default: false
4549

@@ -54,6 +58,9 @@
5458
callbacks: { 'should be a positive number' => proc { |v| v > 0 } },
5559
default: 300
5660

61+
property :use_consistent_splay, [true, false],
62+
default: false
63+
5764
property :run_on_battery, [true, false],
5865
default: true
5966

@@ -93,7 +100,7 @@
93100
frequency_modifier new_resource.frequency_modifier if frequency_supports_frequency_modifier?
94101
start_time start_time_value
95102
start_day new_resource.start_date unless new_resource.start_date.nil?
96-
random_delay new_resource.splay if frequency_supports_random_delay?
103+
random_delay new_resource.splay if frequency_supports_random_delay? && !new_resource.use_consistent_splay
97104
disallow_start_if_on_batteries new_resource.splay unless new_resource.run_on_battery || Gem::Requirement.new('< 14.4').satisfied_by?(Gem::Version.new(Chef::VERSION))
98105
action [ :create, :enable ]
99106
end
@@ -115,7 +122,17 @@ def full_command
115122
# Fetch path of cmd.exe through environment variable comspec
116123
cmd_path = ENV['COMSPEC']
117124

118-
"#{cmd_path} /c \"#{client_cmd}\""
125+
"#{cmd_path} /c \"#{consistent_splay_command}#{client_cmd}\""
126+
end
127+
128+
#
129+
# The consistent splay sleep time when use_consistent_splay is true
130+
#
131+
# @return [NilClass,String] The prepended sleep command to run prior to executing the full command.
132+
#
133+
def consistent_splay_command
134+
return unless new_resource.use_consistent_splay
135+
"C:/windows/system32/windowspowershell/v1.0/powershell.exe Start-Sleep -s #{splay_sleep_time(new_resource.splay)} && "
119136
end
120137

121138
#
@@ -157,6 +174,10 @@ def frequency_supports_frequency_modifier?
157174
def start_time_value
158175
if new_resource.start_time
159176
new_resource.start_time
177+
elsif new_resource.snap_time_to_frequency && new_resource.frequency == 'minute'
178+
snap_time = snap_time(new_resource.frequency_modifier)
179+
new_resource.start_date = snap_time.strftime('%m/%d/%Y')
180+
snap_time.strftime('%H:%M')
160181
elsif Gem::Requirement.new('< 13.7.0').satisfied_by?(Gem::Version.new(Chef::VERSION))
161182
new_resource.frequency == 'minute' ? (Time.now + 60 * new_resource.frequency_modifier.to_f).strftime('%H:%M') : nil
162183
end

resources/systemd_timer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
chef_version_for_provides '< 16.0' if respond_to?(:chef_version_for_provides)
2121

2222
provides :chef_client_systemd_timer
23+
unified_mode true
2324
resource_name :chef_client_systemd_timer
2425

2526
property :job_name, String, default: 'chef-client'

spec/spec_helper.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,31 @@
55
config.color = true # Use color in STDOUT
66
config.formatter = :documentation # Use the specified formatter
77
config.log_level = :error # Avoid deprecation notice SPAM
8+
9+
config.before do
10+
# Mock chef_version_for_provides to be < 18 for chef_client_scheduled_task so that resource will be evaluated
11+
# by current chef unit testing version
12+
class Chef::Resource
13+
class << self
14+
alias_method :chef_version_for_provides_original, :chef_version_for_provides
15+
16+
def chef_version_for_provides(constraint)
17+
if ::File.basename(__FILE__) == 'scheduled_task.rb'
18+
'< 18.0' # Bump the constraint to current 17.x versions of Chef
19+
else
20+
chef_version_for_provides_original(constraint)
21+
end
22+
end
23+
end
24+
end
25+
26+
unless RUBY_PLATFORM =~ /mswin|mingw32|windows/
27+
# This enables spec testing of windows recipes on nix OSes. This is used
28+
# to address the use of Win32::Service in recipe chef-client::task
29+
Win32 = Module.new unless defined?(Win32)
30+
Win32::Service = Class.new unless defined?(Win32::Service)
31+
allow(::Win32::Service).to receive(:exists?).with(anything).and_return(false)
32+
allow(::Win32::Service).to receive(:exists?).with('chef-client').and_return(true)
33+
end
34+
end
835
end

0 commit comments

Comments
 (0)