cookbooks/fb_cron/recipes/default.rb (135 lines of code) (raw):
#
# Cookbook Name:: fb_cron
# Recipe:: default
#
# Copyright (c) 2016-present, Facebook, Inc.
# All rights reserved.
#
# 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.
#
require 'shellwords'
case node['platform_family']
when 'mac_os_x'
svc_name = 'com.vix.cron'
when 'rhel', 'fedora', 'suse'
svc_name = 'crond'
end
include_recipe 'fb_cron::packages'
# keep the name 'cron' so we can notify it easily from other places
service 'cron' do
service_name svc_name
action [:enable, :start]
end
whyrun_safe_ruby_block 'validate_data' do
block do
node['fb_cron']['jobs'].to_hash.each do |name, data|
if data['only_if']
unless data['only_if'].instance_of?(Proc)
fail 'fb_cron\'s only_if requires a Proc'
end
unless data['only_if'].call
Chef::Log.debug("fb_cron: Not including #{name} due to only_if")
node.rm('fb_cron', 'jobs', name)
next
end
end
unless data['command']
fail "fb_cron entry #{name} lacks a command"
end
unless data['time']
fail "fb_cron entry #{name} lacks a time"
end
# If only one instance of this job should be run, add a wrapper script
# with 'command' as an argument. That code gets eval'd, so you can even
# use arbitrary bash
if data['exclusive']
lockfile = "/tmp/cron-#{name}.lock"
escaped = Shellwords.shellescape(data['command'])
command = "/usr/local/bin/exclusive_cron.sh #{lockfile} #{escaped}"
node.default['fb_cron']['jobs'][name]['command'] = command
end
# Calculate a splay that varies across hosts/jobs but is static
# for each host+job to make debugging easier and stats line up.
if data['splaysecs']
if Integer(data['splaysecs']) <= 0 || Integer(data['splaysecs']) > 9600
fail "unreasonable splaysecs #{data['splaysecs']} in #{name} cron"
end
sleepnum = node.get_seeded_flexible_shard(Integer(data['splaysecs']),
data['command'])
node.default['fb_cron']['jobs'][name]['splaycmd'] =
"/bin/sleep #{sleepnum}; "
else
node.default['fb_cron']['jobs'][name]['splaycmd'] = ''
end
# Populate comment field
unless data['comment']
node.default['fb_cron']['jobs'][name]['comment'] = name
end
end
end
end
template 'fb_cron crontab' do
path lazy {
node['fb_cron']['_crontab_path']
}
source 'fb_crontab.erb'
owner 'root'
group 'root'
mode '0644'
end
template '/etc/anacrontab' do
only_if { node['platform_family'] == 'rhel' }
source 'anacrontab.erb'
owner 'root'
group 'root'
mode '0644'
end
envfile = value_for_platform_family(
'debian' => '/etc/default/cron',
['rhel', 'fedora'] => '/etc/sysconfig/crond',
)
if envfile # ~FC023
template envfile do
source 'crond_env.erb'
owner 'root'
group 'root'
mode '0644'
notifies :restart, 'service[cron]'
end
end
# Cleanup rpmnew and rpmsave files
Dir.glob('/etc/cron*/*.rpm{save,new}').each do |todel|
file todel do
action :delete
end
end
# Make sure we nuke all crons from the cron resource.
root_crontab = value_for_platform_family(
['rhel', 'fedora', 'suse'] => '/var/spool/cron/root',
['debian'] => '/var/spool/cron/crontabs/root',
)
if root_crontab
file 'clean out root crontab' do
path root_crontab
action :delete
end
end
cookbook_file '/usr/local/bin/exclusive_cron.sh' do
source 'exclusive_cron.sh'
owner 'root'
group 0
mode '0755'
end
if node.macos?
cookbook_file '/usr/local/bin/osx_make_crond.sh' do
source 'osx_make_crond.sh'
owner 'root'
group 0
mode '0755'
end
execute 'osx_make_crond.sh' do
command '/usr/local/bin/osx_make_crond.sh'
end
end
{
'cron_deny' => '/etc/cron.deny',
'cron_allow' => '/etc/cron.allow',
}.each do |key, cronfile|
file cronfile do # this is an absolute path: ~FB031
only_if { node['fb_cron'][key].empty? }
action :delete
end
template cronfile do # this is an absolute path: ~FB031
not_if { node['fb_cron'][key].empty? }
source 'fb_cron_allow_deny.erb'
owner node.root_user
group node.root_group
mode '0600'
variables(
:config => key,
)
action :create
end
end