diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35d6ebf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build/ +/.idea/ +/pids/*.pid +/logs/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e172ad --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +cronBasher: Launch cron script +======================== + +cronBasher helps you to launch command from cron and handle environment variables from Kuberancher. cronBasher locked commands, to avoid launching multiple command in parallel. + +Environment variables +-------------------- +PATH_CRON_BASHER: Path to cronBasher project. Set this variable into `/etc/crontab` +DATADOG_URL: URL to log to Datadog. Keep empty to not send log +DATADOG_APP_API_KEY: your Datadog APP key +ENVIRONMENT: working environment + +launch_cron.sh +----------- +Remember that scripts launched by cron do not have access to environment variables. +This script copy environment variables into the file `/etc/environment` and launch `cron` +Has we launch our script into Kuberancher pod, we use an `ENTRY_POINT` on this script, and run cron in foreground (`cron -f`) + +Simple command +----------------- +Set PATH_CRON_BASHER into `/etc/crontab` and the line : +``` +* * * * * www-data cd PATH_CRON_BASHER && ./simple_command.sh +``` +This command use function `launchScript`. The first argument is a string with the command to run. + +Recursive command +----------------- +The way of recusive command, is to launch again the command if an action has been made. cronBasher read the number of actiows on the output. If it's not equals to `0`, cronBasher will run command again. It will stop in case of non-zero exit status. +``` +* * * * * www-data cd PATH_CRON_BASHER && ./recursive_command.sh +``` +This command use function `launchUntilScript`. The first argument is a string with the command to run, the seond one is a message to print with the number of stuffs done. + +Send log to Datadog +------------------- +Send all error log to Datadog + +Right +----- +Do not forget to add the `executable` right on your script. diff --git a/bash/functions.sh b/bash/functions.sh new file mode 100644 index 0000000..405ed4f --- /dev/null +++ b/bash/functions.sh @@ -0,0 +1,154 @@ +#!/bin/bash +SERVICE='cron' +DATADOG_FULL_URL="$DATADOG_URL$DATADOG_APP_API_KEY" +START_DATE=$(date --iso-8601=ns) + +# ---------------------------------------------------------------------------------- +# DESCRIPTION +# To easy launch command in cron, and log there results, follow these steps: +# +# - Configure these variables into your script: +# PID_FILE="./pids/.pid" +# LOG_FILE="./logs/cron_>.log" +# DATADOG_TAGS="tags,to,send,to,datadog" +# +# - Import into your script this file with: +# if [ -f ./bash/functions.sh ]; then +# . ./bash/functions.sh +# else +# echo "Cannot find file \`function.sh\`. Please run into the symfony project." +# exit 1 +# fi +# +# - Launch your simple command with: +# launchScript "sudo myCommand --options arguments" +# +# - Launch your command until there work to do. Your commad must write number of result on stdout +# launchUntilScript "sudo myCommand --options arguments" "number of stuff done" +# +# - Add a boolean in environment variabe in Kuberancher `CRON_` to simply start and stop this cron from kuberancher +# +# - Modify ./jobs/etc/ermeo.crontag +# * * * * * www-data $CRON_ && cd $PATH_SYMFONY && ./jobs/.sh +# +# EXAMPLES +# Launch simple command +# @see ./simple_command.sh +# +# Launch command while work to do +# @see ./recursive_command.sh +# ---------------------------------------------------------------------------------- + +# Launch the command and handle returns +# launchScript +function launchScript { + testCmd "$1" + lockScript + + RETURN_MESSAGE=$($CMD) + RETURN_VALUE=$? + logMessageToDatadog "$RETURN_MESSAGE" $RETURN_VALUE + + unlockScript + + return $RETURN_VALUE +} + +# Launch the command while it works +# launchUntilScript +function launchUntilScript { + testCmd "$1" + SUCCESS_MESSAGE=$2 + lockScript + + NBR_DONE=0 + CONTINUE=true + while $CONTINUE; do + # Do not redirect stdError on stdOut! + # Because we need the return to count result + RETURN_MESSAGE=$($CMD) + RETURN_VALUE=$? + + if [[ $RETURN_VALUE != 0 ]]; then + logMessageToDatadog "$RETURN_MESSAGE" $RETURN_VALUE + unlockScript + return $RETURN_VALUE + fi + + if [[ "$RETURN_MESSAGE" == "0" ]]; then + CONTINUE=false + else + if [[ $RETURN_MESSAGE =~ ^-?[0-9]+$ ]]; then + NBR_DONE=$((NBR_DONE + RETURN_MESSAGE)) + fi + fi + done + + logMessageToDatadog "$NBR_DONE $SUCCESS_MESSAGE" $RETURN_VALUE $NBR_DONE + unlockScript + + return $RETURN_VALUE +} + +# ---------------------------------------------------------------------------------- +# DO NOT USE FOLLOWING FUNCTIONS DIRECTLY INTO YOUR SCRIPTS +# ---------------------------------------------------------------------------------- +function testCmd { + CMD=$1 + if [[ "$CMD" == "" ]]; then + logMessageToDatadog 'Command is not correctly configured.' 126 + exit 2 + fi +} + +# Lock the script +function lockScript { + if [ -f "$PID_FILE" ]; then + logMessageToDatadog "A command \`$COMMAND_NAME\` is running" 127 + exit 3 # Another command is running + fi + + touch "$PID_FILE" + date > "$PID_FILE" +} + +# Unlock the script +function unlockScript { + rm "$PID_FILE" +} + +# Log messages into DATADOG +function logMessageToDatadog { + local RETURN_MESSAGE=$1 + local RETURN_VALUE=$2 + local NBR_DONE=$3 + local END_DATE + local STATUS="FAILED" + local DATADOG_MESSAGE="{}" + + # Message on Stdout + echo "$RETURN_MESSAGE" + + # Message in log file + date +"$1: %x %X" >> "$LOG_FILE" + + if [[ "$DATADOG_APP_API_KEY" == "" ]]; then + echo "DATADOG is not correctly configured!" + return + fi + + if [[ "$RETURN_VALUE" == "0" ]]; then + local STATUS="SUCCESSED" + fi + + # Clean message with ' and " + RETURN_MESSAGE=$(echo "$RETURN_MESSAGE" | sed -e 's/"//g' -e "s/'//g") + + # Message to DATADOG + END_DATE=$(date --iso-8601=ns) + DATADOG_MESSAGE='{"status":"'$STATUS'","return_value":"'$RETURN_VALUE'","message":"'$RETURN_MESSAGE'","command_name":"'$COMMAND_NAME'","command":"'$CMD'","ddsource":"'$DATADOG_APP_NAME'","host":"'$HOSTNAME'","ddtags":"'$DATADOG_TAGS'","service":"'$SERVICE'","source":"'$SERVICE'","environment":"'$ENVIRONMENT'","date_start":"'$START_DATE'","date_end":"'$END_DATE'","number_done":"'$NBR_DONE'"}' + + curl -X POST "$DATADOG_FULL_URL" \ + -H "Content-Type: application/json" \ + -d "$DATADOG_MESSAGE" +} diff --git a/crontab b/crontab new file mode 100644 index 0000000..4f765bd --- /dev/null +++ b/crontab @@ -0,0 +1,28 @@ +# /etc/crontab: system-wide crontab +# Unlike any other crontab you don't have to run the `crontab' +# command to install the new version when you edit this file +# and files in /etc/cron.d. These files also have username fields, +# that none of the other crontabs do. + +# need: - apt-get install cron +# - chmod +x .../jobs/your_scrip.sh +# - service cron start + +SHELL=/bin/sh +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +# Set path to your own symfony project (default: /var/www/symfony): +PATH_CRON_BASHER=/home/user/cronBasher +DATADOG_URL=https://http-intake.logs.datadoghq.com/v1/input/ + +# m h dom mon dow user command +17 * * * * root cd / && run-parts --report /etc/cron.hourly +25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily ) +47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly ) +52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly ) + +#### LAUNCH YOU SCRIPT HERE #### + +# user command +* * * * * www-data cd PATH_CRON_BASHER && ./simple_command.sh +* * * * * www-data cd PATH_CRON_BASHER && ./recursive_command.sh diff --git a/example/random.sh b/example/random.sh new file mode 100755 index 0000000..f6d1756 --- /dev/null +++ b/example/random.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo -n $((RANDOM % 100)) diff --git a/launch_cron.sh b/launch_cron.sh new file mode 100644 index 0000000..2ece882 --- /dev/null +++ b/launch_cron.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Script launched from cron do not have access to the environment variables from Kuberancher +# So we need to copy all env into /etc/environment +printenv >> /etc/environment + +# Launch cron in foreground +cron -f diff --git a/recursive_command.sh b/recursive_command.sh new file mode 100755 index 0000000..cbae745 --- /dev/null +++ b/recursive_command.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +COMMAND_NAME="recurcive_command" +PID_FILE="./pids/$COMMAND_NAME.pid" +LOG_FILE="./logs/$COMMAND_NAME.log" +DATADOG_TAGS="cron,synchro,register_envents,$ENVIRONMENT" + +# Import common functions +if [ -f ./bash/functions.sh ]; then + . ./bash/functions.sh +else + echo "Cannot find file \`function.sh\`. Please run into the cronBasher project." + exit 1 +fi + +# Launch your command while job to do +launchUntilScript './example/random.sh' 'stuff(s) done!' +exit $? diff --git a/simple_command.sh b/simple_command.sh new file mode 100755 index 0000000..d48c711 --- /dev/null +++ b/simple_command.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +COMMAND_NAME="simple_command" +PID_FILE="./pids/$COMMAND_NAME.pid" +LOG_FILE="./logs/$COMMAND_NAME.log" +DATADOG_TAGS="cron,synchro,synctag,$ENVIRONMENT" + +# Import common functions +if [ -f ./bash/functions.sh ]; then + . ./bash/functions.sh +else + echo "Cannot find file \`function.sh\`. Please run into the cronBasher project." + exit 1 +fi + +## Launch your command here +launchScript "echo 'This is a simple command launch by cronBasher! '" +exit $?