From e0d9dd6602df9e27a077dcbd897d75329357c334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Fri, 25 Feb 2022 17:24:50 +0100 Subject: [PATCH] refactoring + README --- Dockerfile | 4 +- README.md | 40 +++++++++---- cron-exec | 151 ---------------------------------------------- kiwi-cron | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 164 deletions(-) delete mode 100755 cron-exec create mode 100755 kiwi-cron diff --git a/Dockerfile b/Dockerfile index b93af9c..035128a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,6 @@ RUN set -ex; \ docker-cli \ ; -COPY cron-exec /usr/local/bin/ +COPY kiwi-cron /usr/local/bin/ -CMD [ "cron-exec" ] \ No newline at end of file +CMD [ "kiwi-cron" ] \ No newline at end of file diff --git a/README.md b/README.md index 443dcab..5e2f7d8 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,36 @@ Simple cron-jobs for [`kiwi-scp`](https://github.com/ldericher/kiwi-scp) ## Quick start -kiwi-cron comes with a pre-configured cron daemon for periodic jobs. -Just drop your scripts into the relevant directory under `/kiwi-cron/` and that's it. +`kiwi-cron` comes with a (slightly) opinionated `cron` daemon config for periodic jobs. +Just drop your scripts into the relevant directory under `/kiwi-cron`, that's it. -- `/kiwi-cron/hourly` – is run every full hour -- `/kiwi-cron/daily` – is run every day at 2 am -- `/kiwi-cron/weekly` – is run every saturday at 3 am -- `/kiwi-cron/monthly` – is run on the first day of every month at 5 am -- `/kiwi-cron/yearly` and `/kiwi-cron/annually` – is run on every January 1st at 12 am +You will likely want to automate some tasks regarding your `docker` infrastructure. +That's why the `kiwi-cron` images package a current `docker-cli` – you can just mount your `docker.sock` in its containers and use `docker` commands normally. -## `/kiwi-cron/every` directory +## Simple jobs -You can use directories like `/kiwi-cron/every/5_minutes` to run scripts every 5 minutes. -`kiwi-cron` automatically picks up on that format and generates cron schedules for you. +On startup, `kiwi-cron` checks for possible job files in the `/kiwi-cron` directory structure. -You can define schedules to be run every N minutes, hours, days, or months that way. +For each subdirectory, a random valid cron schedule is generated, so that: + +- `/kiwi-cron/hourly` runs once every hour (random minute) +- `/kiwi-cron/daily` runs once every day (random nighttime value) +- `/kiwi-cron/weekly` runs once every weekend (random nighttime value on Saturday or Sunday) +- `/kiwi-cron/monthly` runs once every month (random nighttime value on a random day) +- `/kiwi-cron/yearly` and `/kiwi-cron/annually` runs once a year (random nighttime value on a random day in January or February) + +Cron schedules are regenerated once on each startup, only for directories that have files. + +## Finer granularity: The `/kiwi-cron/every` directory + +Directories like `/kiwi-cron/every/5_minutes` will run scripts every 5 minutes. +`kiwi-cron` picks up on that format and generates valid `cron` schedules on startup. + +You can define schedules to be run every N minutes, hours, days, or months by creating the corresponding directories. + +Scheduling for every N weeks (or years) doesn't work that way; jobs in those directories will instead be run every week (or every year). + +## Inspection + +Checking the generated `cron` schedules is done using the standard `crontab` command: +`docker exec kiwi-cron-container crontab -l` will show the effective schedules. diff --git a/cron-exec b/cron-exec deleted file mode 100755 index b4f79ed..0000000 --- a/cron-exec +++ /dev/null @@ -1,151 +0,0 @@ -#!/bin/sh - -crontab_append () { - crontab="$( - printf '%s\n%s' \ - "${crontab}" "${1}" - )" -} - -print_cron_schedule() { - cas_min="${1}" - cas_hour="${2}" - cas_day="${3}" - cas_month="${4}" - cas_weekday="${5}" - cas_command="${6}" - - printf '%-8s%-8s%-8s%-8s%-8s%s\n' \ - "${cas_min}" "${cas_hour}" "${cas_day}" "${cas_month}" "${cas_weekday}" "${cas_command}" -} - -crontab='# crontab generated for kiwi-cron' -crontab_append '# generation time: '"$(date)" -crontab_append '#' -crontab_append "$( - print_cron_schedule \ - "# min" "hour" "day" "month" "weekday" "command" -)" - -# check *ly dirs in root directory -schedule_dirs="$( - find "/kiwi-cron" -type d -name "*ly" -mindepth 1 -maxdepth 1 -)" - -for schedule_dir in ${schedule_dirs}; do - # count files in scheduler directory - schedule_filecount="$( - find "${schedule_dir}" -type f -mindepth 1 -maxdepth 1 \ - | wc -l - )" - - # ignore if empty - if [ "${schedule_filecount}" -eq "0" ]; then - continue - fi - - # infer cron schedule by directory name - units="${schedule_dir##*/}" - - case "${units}" in - hourly) - crontab_append "$( - print_cron_schedule \ - "0" "*" "*" "*" "*" "run-parts '${schedule_dir}'" - )" - ;; - - daily) - crontab_append "$( - print_cron_schedule \ - "0" "2" "*" "*" "*" "run-parts '${schedule_dir}'" - )" - ;; - - weekly) - crontab_append "$( - print_cron_schedule \ - "0" "3" "*" "*" "6" "run-parts '${schedule_dir}'" - )" - ;; - - monthly) - crontab_append "$( - print_cron_schedule \ - "0" "5" "1" "*" "*" "run-parts '${schedule_dir}'" - )" - ;; - - yearly|annually) - crontab_append "$( - print_cron_schedule \ - "0" "0" "1" "1" "*" "run-parts '${schedule_dir}'" - )" - ;; - esac -done - -# check dirs in "every" subdirectory -every_schedule_dirs="$( - find "/kiwi-cron/every" -type d -mindepth 1 -maxdepth 1 -)" - -for schedule_dir in ${every_schedule_dirs}; do - # count files in scheduler directory - schedule_filecount="$( - find "${schedule_dir}" -type f -mindepth 1 -maxdepth 1 \ - | wc -l - )" - - # ignore if empty - if [ "${schedule_filecount}" -eq "0" ]; then - continue - fi - - # infer cron schedule by directory name - schedule="${schedule_dir##*/}" - every="$( echo "${schedule}" | awk -F_ '{print $1}' )" - units="$( echo "${schedule}" | awk -F_ '{print $2}' )" - units="${units%s}" - - # generate valid random values - rand_min=$(( RANDOM % 60 )) - # rand_hour=$(( RANDOM % 24 )) - # generating nighttime values seems like a good idea! - rand_hour=$(( (RANDOM % 7 + 21) % 24 )) - rand_day=$(( RANDOM % 31 + 1 )) - - case "${units}" in - minute) - crontab_append "$( - print_cron_schedule \ - "*/${every}" "*" "*" "*" "*" "run-parts '${schedule_dir}'" - )" - ;; - - hour) - crontab_append "$( - print_cron_schedule \ - "${rand_min}" "*/${every}" "*" "*" "*" "run-parts '${schedule_dir}'" - )" - ;; - - day) - crontab_append "$( - print_cron_schedule \ - "${rand_min}" "${rand_hour}" "*/${every}" "*" "*" "run-parts '${schedule_dir}'" - )" - ;; - - month) - crontab_append "$( - print_cron_schedule \ - "${rand_min}" "${rand_hour}" "${rand_day}" "*/${every}" "*" "run-parts '${schedule_dir}'" - )" - ;; - esac -done - -# replace crontab -echo "${crontab}" | crontab - -exec crond -fd 8 diff --git a/kiwi-cron b/kiwi-cron new file mode 100755 index 0000000..4660c02 --- /dev/null +++ b/kiwi-cron @@ -0,0 +1,172 @@ +#!/bin/sh + +crontab='' +crontab_line () { + if [ -z "${crontab}" ]; then + crontab="${1}" + else + crontab="$( + printf '%s\n%s' "${crontab}" "${1}"; + )" + fi +} + +print_cron_schedule() { + pcs_min="${1}" + pcs_hour="${2}" + pcs_day="${3}" + pcs_month="${4}" + pcs_weekday="${5}" + pcs_command="${6}" + + printf '%-8s%-8s%-8s%-8s%-8s%s\n' \ + "${pcs_min}" "${pcs_hour}" "${pcs_day}" "${pcs_month}" "${pcs_weekday}" "${pcs_command}" +} + +create_schedule() { + cs_every="${1:-1}" + cs_units="${2}" + cs_command="${3}" + + if [ "${cs_every}" -eq 1 ]; then + cs_every="" + else + cs_every="/${cs_every}" + fi + + # generate valid random schedule values + # generally, "nighttime" and "weekend" will be preferred. + + # shellcheck disable=SC3028 + rand_min=$(( RANDOM % 60 )) + + # shellcheck disable=SC3028 + # rand_hour=$(( RANDOM % 24 )) + rand_hour=$(( (21 + RANDOM % 7) % 24 )) + + # shellcheck disable=SC3028 + rand_day=$(( RANDOM % 31 + 1 )) + + # shellcheck disable=SC3028 + # rand_weekday=$(( RANDOM % 7 )) + rand_weekday=$(( (6 + RANDOM % 2) % 7 )) + + # shellcheck disable=SC3028 + # rand_month=$(( RANDOM % 12 + 1 )) + rand_month=$(( RANDOM % 2 + 1 )) + + case "${cs_units}" in + minute) + print_cron_schedule \ + "*${cs_every}" "*" "*" "*" "*" "${cs_command}" + ;; + hour) + print_cron_schedule \ + "${rand_min}" "*${cs_every}" "*" "*" "*" "${cs_command}" + ;; + day) + print_cron_schedule \ + "${rand_min}" "${rand_hour}" "*${cs_every}" "*" "*" "${cs_command}" + ;; + week) + print_cron_schedule \ + "${rand_min}" "${rand_hour}" "*" "*" "${rand_weekday}" "${cs_command}" + ;; + month) + print_cron_schedule \ + "${rand_min}" "${rand_hour}" "${rand_day}" "*${cs_every}" "*" "${cs_command}" + ;; + year) + print_cron_schedule \ + "${rand_min}" "${rand_hour}" "${rand_day}" "${rand_month}" "*" "${cs_command}" + ;; + esac +} + +dir_has_no_files () { + dhnf_directory="${1}" + + # count files in directory + dhnf_filecount="$( + find "${dhnf_directory}" -type f -mindepth 1 -maxdepth 1 \ + | wc -l + )" + + # true if empty + test "${dhnf_filecount}" -eq "0" +} + +dir_to_unit () { + dtu_dirname="${1}" + + case "${dtu_dirname}" in + minute|hour|day|week|month|year) + echo "${dtu_dirname}" + ;; + + # truncated "daily" + dai) + echo "day" + ;; + + # truncated "annually" + annual) + echo "year" + ;; + esac +} + +crontab_line '# crontab generated by kiwi-cron' +crontab_line '# generation time: '"$(date)" +crontab_line '#' +crontab_line "$( + print_cron_schedule \ + "# min" "hour" "day" "month" "weekday" "command" +)" + +# check *ly dirs in "/kiwi-cron" directory +schedule_dirs="$( + find "/kiwi-cron" -type d -name "*ly" -mindepth 1 -maxdepth 1 +)" + +for schedule_dir in ${schedule_dirs}; do + # ignore if no files + if dir_has_no_files "${schedule_dir}"; then + continue + fi + + # infer units by directory name + dir_name="${schedule_dir##*/}" + units="${dir_name%ly}" + units="$( dir_to_unit "${units}" )" + + crontab_line "$( + create_schedule "1" "${units}" "run-parts '${schedule_dir}'" + )" +done + +# check dirs in "/kiwi-cron/every" directory +every_schedule_dirs="$( + find "/kiwi-cron/every" -type d -mindepth 1 -maxdepth 1 +)" + +for schedule_dir in ${every_schedule_dirs}; do + # ignore if no files + if dir_has_no_files "${schedule_dir}"; then + continue + fi + + # infer schedule params by directory name + dir_name="${schedule_dir##*/}" + every="$( echo "${dir_name}" | awk -F_ '{print $1}' )" + units="$( echo "${dir_name}" | awk -F_ '{print $2}' )" + units="${units%s}" + + crontab_line "$( + create_schedule "${every}" "${units}" "run-parts '${schedule_dir}'" + )" +done + +# replace crontab, run crond +echo "${crontab}" | crontab - +exec crond -fd 8