Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync fb_cron with upstream #248

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions cookbooks/fb_cron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ Attributes
----------
* node['fb_cron']['environment'][$NAME][$VALUE]
* node['fb_cron']['jobs'][$NAME]['command']
* node['fb_cron']['jobs'][$NAME]['comment']
* node['fb_cron']['jobs'][$NAME]['time']
* node['fb_cron']['jobs'][$NAME]['user']
* node['fb_cron']['jobs'][$NAME]['only_if']
* node['fb_cron']['jobs'][$NAME]['splaysecs']
* node['fb_cron']['jobs'][$NAME]['exclusive']
* node['fb_cron']['anacrontab']['environment']['$SETTING']
* node['fb_cron']['cron_allow']
* node['fb_cron']['cron_deny']

Usage
-----

### Adding Jobs
`node['fb_cron']['jobs']` is a hash of crons. To add a job, simply do:

```
```ruby
node.default['fb_cron']['jobs']['do_this_thing'] = {
'time' => '4 5 * * *',
'user' => 'apache',
Expand All @@ -50,7 +53,7 @@ Any cron entry can include an `only_if` that *must* be a `proc`. It will
be evaluated at runtime and the job will not be included if the only_if does
not evaluate to true. For example:

```
```ruby
node.default['fb_cron']['jobs']['do_this_thing'] = {
'only_if' => proc { node['fb_bla']['enabled'] }
'time' => '4 5 * * *',
Expand All @@ -59,6 +62,9 @@ node.default['fb_cron']['jobs']['do_this_thing'] = {
}
```

#### comment
The comment entry of the job, which defaults to the job name.

### splaysecs
Defaults to false/none. Please set a splay time for your cronjob, or
explicitly set this to false to indicate that your job can't tolerate a splay.
Expand All @@ -77,7 +83,7 @@ job, it'll be removed from any systems it was on.
A bunch of default crons we want everywhere are set in the attributes file, if
you need to exempt yourself from one, you can simply remove it from the hash:

```
```ruby
node.default['fb_cron']['jobs'].delete('do_this_thing')
```

Expand All @@ -93,7 +99,7 @@ affect the environment of the init script. On Redhat-like systems these
variables go into `/etc/sysconfig/crond`, on Debian-like systems these go to
`/etc/default/cron`. For example:

```
```ruby
# For RH
node.default['fb_cron']['environment']['CRONDARGS'] = "-s"
# For Debian
Expand All @@ -108,8 +114,20 @@ execution. This can be configured using the
to modify the start time of anacron jobs from the default 3-22 o'clock to 6-8
o'clock (server time):

```
```ruby
node.default['fb_cron']['anacrontab']['environment']['start_hours_range'] = '6-8'
```

NOTE: This is currently only implemented on Redhat-like OSes.

### configuring who can run crontab command
Use the `node['fb_cron']['cron_allow']` and `node['fb_cron']['cron_deny']`
attributes to control the content of the `/etc/cron.allow` and `/etc/cron.deny`
files. The attributes default to empty arrays, and the files will be removed if
they remain empty arrays. Simply append to them:

```ruby
node.default['fb_cron']['cron_allow'] << 'user1'
```

This can be used for compliance with security benchmarks.
2 changes: 2 additions & 0 deletions cookbooks/fb_cron/attributes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
'start_hours_range' => '3-22',
},
},
'cron_deny' => [],
'cron_allow' => [],

# Path for the crontab that contains all the fb_cron job entries.
# This is a hidden attribute because people shouldn't change this unless
Expand Down
1 change: 0 additions & 1 deletion cookbooks/fb_cron/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
license 'Apache-2.0'
description 'Installs/Configures cron'
source_url 'https://github.com/facebook/chef-cookbooks/'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.0.1'
supports 'centos'
supports 'debian'
Expand Down
33 changes: 31 additions & 2 deletions cookbooks/fb_cron/recipes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@
block do
node['fb_cron']['jobs'].to_hash.each do |name, data|
if data['only_if']
unless data['only_if'].class == Proc
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)
Expand Down Expand Up @@ -71,13 +72,19 @@
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
Expand Down Expand Up @@ -124,7 +131,7 @@
# Make sure we nuke all crons from the cron resource.
root_crontab = value_for_platform_family(
['rhel', 'fedora', 'suse'] => '/var/spool/cron/root',
['debian', 'ubuntu'] => '/var/spool/cron/crontabs/root',
['debian'] => '/var/spool/cron/crontabs/root',
)
if root_crontab
file 'clean out root crontab' do
Expand Down Expand Up @@ -152,3 +159,25 @@
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
2 changes: 2 additions & 0 deletions cookbooks/fb_cron/recipes/packages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
if node['platform'] == 'amazon' || node['platform_version'].to_i >= 6
package_name = 'cronie'
end
when 'debian'
package_name = 'cron'
end

if package_name # ~FC023
Expand Down
97 changes: 97 additions & 0 deletions cookbooks/fb_cron/spec/default_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
#
# 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 './spec/spec_helper'

recipe 'fb_cron::default' do |tc|
let(:chef_run) { tc.chef_run }

it 'should render basic crontab' do
chef_run.converge(described_recipe) do |node|
node.default['fb_cron']['jobs']['do_this_thing'] = {
'time' => '1 2 3 4 5',
'user' => 'apache',
'command' => '/usr/local/bin/foo.php',
}
node.default['fb_cron']['jobs']['comment_special'] = {
'time' => '1 2 3 4 5',
'user' => 'apache',
'command' => '/usr/local/bin/foo.php',
'comment' => 'a very useful comment',
}
end

expect(chef_run).to render_file('/etc/cron.d/fb_crontab').with_content(
tc.fixture('fb_crontab'),
)
end

it 'should render crontab with mailto' do
chef_run.converge(described_recipe) do |node|
node.default['fb_cron']['jobs']['do_this_thing'] = {
'time' => '2 1 5 4 3',
'user' => 'root',
'command' => '/usr/local/bin/foo.php',
'mailto' => '[email protected]',
}
end

expect(chef_run).to render_file('/etc/cron.d/fb_crontab').with_content(
tc.fixture('fb_crontab_mailto'),
)
end

it 'should render crontab with more than one job' do
chef_run.converge(described_recipe) do |node|
node.default['fb_cron']['jobs']['do_this_thing'] = {
'time' => '* 1 * 2 *',
'user' => 'hank',
'command' => '/usr/local/bin/foo.php',
}
node.default['fb_cron']['jobs']['do_this_other_thing'] = {
'time' => '1 * 3 * 5',
'user' => 'fred',
'command' => '/usr/local/bin/bar.php',
'mailto' => '[email protected]',
}
end

expect(chef_run).to render_file('/etc/cron.d/fb_crontab').with_content(
tc.fixture('fb_crontab_several'),
)
end

it 'should render anacrontab on appropriate platforms' do
chef_run.converge(described_recipe) do |node|
node.default['fb_cron']['anacrontab']['environment'] = {
'shell' => '/bin/bash',
'path' => '/sbin:/bin:/usr/sbin:/usr/bin:/usr/fake',
'mailto' => '[email protected]',
'random_delay' => '8',
'start_hours_range' => '2-3',
}
end

if tc.platform.to_s.start_with?('centos')
expect(chef_run).to render_file('/etc/anacrontab').with_content(
tc.fixture('anacrontab'),
)
else
expect(chef_run).to_not render_file('/etc/anacrontab')
end
end
end
16 changes: 16 additions & 0 deletions cookbooks/fb_cron/spec/fixtures/default/anacrontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# THIS FILE IS CONTROLLED BY CHEF, DO NOT EDIT!
#
# You may add cronjobs following the instructions in
# fb_cron/README.md
#
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/fake
[email protected]
RANDOM_DELAY=8
START_HOURS_RANGE=2-3

#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly
11 changes: 11 additions & 0 deletions cookbooks/fb_cron/spec/fixtures/default/fb_crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#
# THIS FILE IS CONTROLLED BY CHEF, DO NOT EDIT!
#
# You may add cronjobs following the instructions in
# fb_cron/README.md
#
# do_this_thing
1 2 3 4 5 apache /usr/local/bin/foo.php

# a very useful comment
1 2 3 4 5 apache /usr/local/bin/foo.php
10 changes: 10 additions & 0 deletions cookbooks/fb_cron/spec/fixtures/default/fb_crontab_mailto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#
# THIS FILE IS CONTROLLED BY CHEF, DO NOT EDIT!
#
# You may add cronjobs following the instructions in
# fb_cron/README.md
#
# do_this_thing
[email protected]
2 1 5 4 3 root /usr/local/bin/foo.php
MAILTO=root
13 changes: 13 additions & 0 deletions cookbooks/fb_cron/spec/fixtures/default/fb_crontab_several
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# THIS FILE IS CONTROLLED BY CHEF, DO NOT EDIT!
#
# You may add cronjobs following the instructions in
# fb_cron/README.md
#
# do_this_thing
* 1 * 2 * hank /usr/local/bin/foo.php

# do_this_other_thing
[email protected]
1 * 3 * 5 fred /usr/local/bin/bar.php
MAILTO=root
4 changes: 4 additions & 0 deletions cookbooks/fb_cron/templates/default/fb_cron_allow_deny.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# this file generated by Chef. See fb_cron/README.md
<% node['fb_cron'][@config].to_a.each do |user| %>
<%= user %>
<% end %>
2 changes: 1 addition & 1 deletion cookbooks/fb_cron/templates/default/fb_crontab.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
<% node['fb_cron']['jobs'].to_hash.each do |name, job| %>
<% job['user'] = 'root' unless job['user'] -%>
# <%= name %>
# <%= job['comment'] %>
<% if job['mailto'] -%>
MAILTO=<%= job['mailto'] %>
<% end -%>
Expand Down