Skip to content

Commit

Permalink
Crontab: Rewrite the crontab recipe to work with sections (#3708)
Browse files Browse the repository at this point in the history
* Rewrite the crontab recipe to work with sections

* Crontab: Move existing cronjobs to the section when first generating to prevent breaking backwards compatibility
Generate new docs
  • Loading branch information
ardentsword authored Oct 12, 2023
1 parent 6166a6c commit 003b4e5
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 110 deletions.
131 changes: 50 additions & 81 deletions contrib/crontab.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
/*
Recipe for adding crontab jobs.
It checks for duplicates by the command part of the job. Changing the schedule will update the crontab. So when you change the command part you have to manually remove the old one. Use `crontab -e` on the server to remove it.
This recipe creates a new section in the crontab file with the configured jobs.
The section is identified by the *crontab:identifier* variable, by default the application name.
## Configuration
Expand All @@ -20,117 +21,85 @@
]);
```
*/

namespace Deployer;

// Get path to bin
set('bin/crontab', function () {
return which('crontab');
});

desc('Loads crontab');
task('crontab:load', function () {
set('crontab:all', []);

// Crontab is empty
if (!test ("{{bin/crontab}} -l >> /dev/null 2>&1")) {
return;
}

$cronData = run ("{{bin/crontab}} -l");
$cronLines = explode (PHP_EOL, $cronData);

$currentTasks = [];
foreach ($cronLines as $cronLine) {
$jobData = parseJob($cronLine);
if (is_null ($jobData)) {
continue;
}

$currentTasks[$jobData['ckey']] = $jobData;
}

set ('crontab:all', $currentTasks);
// Set the identifier used in the crontab, application name by default
set('crontab:identifier', function () {
return get('application', 'application');
});

desc('Sync crontab jobs');
task('crontab:sync', function () {
$syncJobs = get('crontab:jobs', []);
$cronJobsLocal = array_map(
fn($job) => parse($job),
get('crontab:jobs', [])
);

if (count ($syncJobs) == 0) {
if (count($cronJobsLocal) == 0) {
writeln("Nothing to sync - configure crontab:jobs");
return;
}

// Load current jobs
invoke('crontab:load');
$cronJobs = get('crontab:all');

foreach ($syncJobs as $syncJob) {
$syncJob = parse($syncJob);
$syncJobData = parseJob($syncJob);

if (is_null ($syncJobData)) {
continue;
}

$cronJobData = $cronJobs[$syncJobData['ckey']] ?? NULL;

if (!is_null ($cronJobData) && $cronJobData['skey'] == $syncJobData['skey']) {
// Job is exists and correct
writeLn($syncJobData['cmd'] . ': <fg=green;options=bold>OK</>');
}
else {
if (is_null ($cronJobData)) {
writeLn($syncJobData['cmd'] . ': <fg=yellow;options=bold>NEW</>');
}
else {
writeLn($syncJobData['cmd'] . ': <fg=red;options=bold>FIX</>');
$cronJobs = getRemoteCrontab();
$identifier = get('crontab:identifier');
$sectionStart = "###< $identifier";
$sectionEnd = "###> $identifier";

// find our cronjob section
$start = array_search($sectionStart, $cronJobs);
$end = array_search($sectionEnd, $cronJobs);

if ($start === false || $end === false) {
// Move the duplicates over when first generating the section
foreach ($cronJobs as $index => $cronJob) {
if (in_array($cronJob, $cronJobsLocal)) {
unset($cronJobs[$index]);
writeln("Crontab: Found existing job in crontab, moving it to the section");
}

$cronJobs[$syncJobData['ckey']] = $syncJobData;
}

// Create the section
$cronJobs[] = $sectionStart;
$cronJobs += $cronJobsLocal;
$cronJobs[] = $sectionEnd;
writeln("Crontab: Found no section, created the section with configured jobs");
} else {
// Replace the existing section
array_splice($cronJobs, $start + 1, $end - $start - 1, $cronJobsLocal);
writeln("Crontab: Found existing section, replaced with configured jobs");
}

$tmpCrontabPath = \sprintf('/tmp/%s', \uniqid('crontab_save_'));
setRemoteCrontab($cronJobs);
});

function setRemoteCrontab(array $lines): void
{
$tmpCrontabPath = sprintf('/tmp/%s', uniqid('crontab_save_'));

if (test("[ -f '$tmpCrontabPath' ]")) {
run("unlink '$tmpCrontabPath'");
}

foreach ($cronJobs as $cronJob) {
$jobString = $cronJob['minute'] . ' ' . $cronJob['hour'] . ' ' . $cronJob['day'] . ' ' . $cronJob['month'] . ' ' . $cronJob['weekday'] . ' ' . $cronJob['cmd'];
run("echo '" . $jobString . "' >> $tmpCrontabPath");
foreach ($lines as $line) {
run("echo '" . $line . "' >> $tmpCrontabPath");
}

run('{{bin/crontab}} ' . $tmpCrontabPath);
run('unlink ' . $tmpCrontabPath);
});


function parseJob ($job) {
if (!is_string($job)) {
return NULL;
}

if (substr ($job, 0, 1) == '#') {
return NULL;
}

$jobData = explode (' ', $job, 6);
}

if (count ($jobData) != 6) {
return NULL;
function getRemoteCrontab(): array
{
if (!test("{{bin/crontab}} -l >> /dev/null 2>&1")) {
return [];
}

return [
'skey' => md5 ($job),
'ckey' => md5 ($jobData['5']),
'minute' => $jobData['0'],
'hour' => $jobData['1'],
'day' => $jobData['2'],
'month' => $jobData['3'],
'weekday' => $jobData['4'],
'cmd' => $jobData['5'],
];
return explode(PHP_EOL, run("{{bin/crontab}} -l"));
}

38 changes: 9 additions & 29 deletions docs/contrib/crontab.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,9 @@ require 'contrib/crontab.php';
[Source](/contrib/crontab.php)



Recipe for adding crontab jobs.

It checks for duplicates by the command part of the job. Changing the schedule will update the crontab. So when you change the command part you have to manually remove the old one. Use `crontab -e` on the server to remove it.

## Configuration

- *crontab:jobs* - An array of strings with crontab lines.

## Usage

```php
require 'contrib/crontab.php';

after('deploy:success', 'crontab:sync');

add('crontab:jobs', [
'* * * * * cd {{current_path}} && {{bin/php}} artisan schedule:run >> /dev/null 2>&1',
]);
```


## Configuration
### bin/crontab
[Source](https://github.com/deployphp/deployer/blob/master/contrib/crontab.php#L26)
[Source](https://github.com/deployphp/deployer/blob/master/contrib/crontab.php#L28)

Get path to bin

Expand All @@ -44,19 +22,21 @@ return which('crontab');
```


### crontab:identifier
[Source](https://github.com/deployphp/deployer/blob/master/contrib/crontab.php#L33)

## Tasks
Set the identifier used in the crontab, application name by default

### crontab:load
[Source](https://github.com/deployphp/deployer/blob/master/contrib/crontab.php#L31)

Loads crontab.
```php title="Default value"
return get('application', 'application');
```



## Tasks

### crontab:sync
[Source](https://github.com/deployphp/deployer/blob/master/contrib/crontab.php#L56)
[Source](https://github.com/deployphp/deployer/blob/master/contrib/crontab.php#L38)

Sync crontab jobs.

Expand Down

0 comments on commit 003b4e5

Please sign in to comment.