mirror of
https://github.com/willfarrell/docker-crontab.git
synced 2025-04-08 16:05:14 +02:00
init commit
This commit is contained in:
commit
53ffb16db5
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
config.json
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM library/alpine:3.5
|
||||
|
||||
ENV HOME_DIR=/opt/crontab
|
||||
RUN apk add --no-cache --virtual .run-deps bash curl jq docker \
|
||||
&& mkdir -p ${HOME_DIR}
|
||||
|
||||
# Dev
|
||||
COPY config.json ${HOME_DIR}/
|
||||
|
||||
COPY docker-entrypoint /
|
||||
ENTRYPOINT ["/docker-entrypoint"]
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=3s \
|
||||
CMD ps aux | grep '[c]rond' || exit 1
|
||||
|
||||
CMD ["crond","-f"]
|
62
README.md
Normal file
62
README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# docker-crontab
|
||||
|
||||
A simple wrapper over `docker` to all complex cron job to be run in other containers.
|
||||
|
||||
## Why?
|
||||
Yes, I'm aware of [mcuadros/ofelia](https://github.com/mcuadros/ofelia), it was the main inspiration for this project.
|
||||
A great project, don't get me wrong. It was just missing certain key enterprise features.
|
||||
|
||||
## Features
|
||||
- Easy to read schedule syntax allowed.
|
||||
- Allows for comments, cause we all need friendly reminders of what `update_script.sh` actually does.
|
||||
- Start an image using `image`.
|
||||
- Run command in a container using `container`.
|
||||
- Run command on a instances of a scaled container using `project`.
|
||||
- Ability to trigger scripts in other containers on completion cron job using `trigger`.
|
||||
|
||||
## Config.json
|
||||
- `comment`: Comments to be included with crontab entry
|
||||
- `schedule`: Crontab schedule syntax as described in https://godoc.org/github.com/robfig/cron. Ex `@hourly`, `@every 1h30m`, `* * * * * *`. Required.
|
||||
- `command`: Command to be run on docker container/image. Required.
|
||||
- `image`: Docker images name (ex `library/alpine:3.5`). Optional.
|
||||
- `project`: Docker Compose/Swarm project name. Optional, only applies when `contain` is included.
|
||||
- `container`: Full container name or container alias if `project` is set. Ignored if `image` is included.
|
||||
- `dockerargs`: Command line docker `run`/`exec` arguments for full control. Defaults to ` `.
|
||||
- `trigger`: Array of docker-crontab subset objects. Subset includes: `image`,`project`,`container`,`command`,`dockerargs`
|
||||
|
||||
See `./config.sample.json` for examples.
|
||||
|
||||
## Examples
|
||||
|
||||
### Command Line
|
||||
```bash
|
||||
docer build -t crontab .
|
||||
docker run -d \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
crontab
|
||||
```
|
||||
|
||||
### Dockerfile
|
||||
```Dockerfile
|
||||
FROM willfarrell/crontab
|
||||
|
||||
COPY config.json ${HOME_DIR}/
|
||||
```
|
||||
|
||||
### Logrotate Dockerfile
|
||||
```Dockerfile
|
||||
FROM willfarrell/crontab
|
||||
|
||||
RUN apk add --no-cache logrotate
|
||||
RUN echo "*/5 * * * * /usr/sbin/logrotate /etc/logrotate.conf" >> /etc/crontabs/logrotate
|
||||
ADD logrotate.conf /etc/logrotate.conf
|
||||
|
||||
CMD ["crond", "-f"]
|
||||
```
|
||||
|
||||
## TODO
|
||||
- [ ] Make smaller by using busybox?
|
||||
- [ ] Have ability to auto regenerate crontab on file change
|
||||
- [ ] Run commands on host machine
|
||||
- [ ] Write tests
|
||||
- [ ] Setup TravisCI
|
32
config.sample.json
Normal file
32
config.sample.json
Normal file
@ -0,0 +1,32 @@
|
||||
[{
|
||||
"comment":"cron with triggered commands",
|
||||
"schedule":"* * * * *",
|
||||
"command":"echo hello",
|
||||
"project":"crontab",
|
||||
"container":"myapp",
|
||||
"trigger":[{
|
||||
"command":"echo world",
|
||||
"container":"crontab_myapp_1"
|
||||
}]
|
||||
},{
|
||||
"comment":"map a volume",
|
||||
"schedule":"* * * * *",
|
||||
"dockerargs":"-d -v /tmp:/tmp",
|
||||
"command":"echo new",
|
||||
"image":"alpine:3.5"
|
||||
},{
|
||||
"comment":"use an ENV from inside a container",
|
||||
"schedule":"@hourly",
|
||||
"dockerargs":"-d -e FOO=BAR",
|
||||
"command":"sh -c 'echo hourly ${FOO}'",
|
||||
"image":"alpine:3.5"
|
||||
},{
|
||||
"comment":"trigger every 2 min",
|
||||
"schedule":"@every 2m",
|
||||
"command":"echo 2 minute",
|
||||
"image":"alpine:3.5",
|
||||
"trigger":[{
|
||||
"command":"echo world",
|
||||
"container":"crontab_myapp_1"
|
||||
}]
|
||||
}]
|
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
||||
version: "2.1"
|
||||
|
||||
services:
|
||||
myapp:
|
||||
image: alpine:3.5
|
||||
restart: always
|
||||
command: "sh -c 'while :; do sleep 1; done'"
|
||||
|
||||
crontab:
|
||||
build: .
|
||||
restart: always
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
164
docker-entrypoint
Executable file
164
docker-entrypoint
Executable file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# for local testing only
|
||||
#HOME_DIR=.
|
||||
|
||||
CONFIG=${HOME_DIR}/config.json
|
||||
DOCKER_SOCK=/var/run/docker.sock
|
||||
CRONTAB_FILE=${HOME_DIR}/docker
|
||||
|
||||
make_image_cmd() {
|
||||
DOCKERARGS=$(echo ${TMP_JSON} | jq -r .dockerargs)
|
||||
if [ "${DOCKERARGS}" == "null" ]; then DOCKERARGS=; fi
|
||||
IMAGE=$(echo ${TMP_JSON} | jq -r .image)
|
||||
TMP_COMMAND=$(echo ${TMP_JSON} | jq -r .command)
|
||||
echo "docker run ${DOCKERARGS} ${IMAGE} ${TMP_COMMAND}"
|
||||
}
|
||||
|
||||
make_container_cmd() {
|
||||
DOCKERARGS=$(echo ${TMP_JSON} | jq -r .dockerargs)
|
||||
if [ "${DOCKERARGS}" == "null" ]; then DOCKERARGS=; fi
|
||||
PROJECT=$(echo ${TMP_JSON} | jq -r .project)
|
||||
CONTAINER=$(echo ${TMP_JSON} | jq -r .container)
|
||||
TMP_COMMAND=$(echo ${TMP_JSON} | jq -r .command)
|
||||
|
||||
COMMAND_ARR=()
|
||||
|
||||
if [ "${PROJECT}" != "null" ]; then
|
||||
|
||||
# create bash script to detect all running containers
|
||||
SCRIPT_NAME=$(cat /proc/sys/kernel/random/uuid)
|
||||
cat << EOF > ${HOME_DIR}/${SCRIPT_NAME}
|
||||
!#/bin/bash
|
||||
set -e
|
||||
|
||||
CONTAINERS=\$(curl --no-buffer -s -XGET --unix-socket ${DOCKER_SOCK} http://localhost/containers/json | jq -r .[].Names[0] | sed 's@/@@')"
|
||||
for CONTAINER_NAME in \$CONTAINERS; do
|
||||
if [[ "\${CONTAINER_NAME}" =~ ^${PROJECT}_${CONTAINER}.+ ]]; then
|
||||
docker exec ${DOCKERARGS} \${CONTAINER_NAME} ${TMP_COMMAND}
|
||||
fi
|
||||
done
|
||||
EOF
|
||||
echo "sh ${HOME_DIR}/${SCRIPT_NAME}"
|
||||
else
|
||||
echo "docker exec ${DOCKERARGS} ${CONTAINER} ${TMP_COMMAND}"
|
||||
fi
|
||||
}
|
||||
|
||||
make_cmd() {
|
||||
IMAGE=$(echo ${TMP_JSON} | jq -r .image)
|
||||
CONTAINER=$(echo ${TMP_JSON} | jq -r .container)
|
||||
if [ "${IMAGE}" != "null" ]; then
|
||||
make_image_cmd
|
||||
elif [ "${CONTAINER}" != "null" ]; then
|
||||
make_container_cmd
|
||||
else
|
||||
echo "echo 'Error making docker command, image or container param missing.'"
|
||||
fi
|
||||
}
|
||||
|
||||
parse_schedule() {
|
||||
case $1 in
|
||||
"@yearly")
|
||||
echo "0 0 0 1 1 *"
|
||||
;;
|
||||
"@annually")
|
||||
echo "0 0 0 1 1 *"
|
||||
;;
|
||||
"@monthly")
|
||||
echo "0 0 0 1 * *"
|
||||
;;
|
||||
"@weekly")
|
||||
echo "0 0 0 * * 0"
|
||||
;;
|
||||
"@daily")
|
||||
echo "0 0 0 * * *"
|
||||
;;
|
||||
"@midnight")
|
||||
echo "0 0 0 * * *"
|
||||
;;
|
||||
"@hourly")
|
||||
echo "0 0 * * * *"
|
||||
;;
|
||||
"@every")
|
||||
TIME=$2
|
||||
TOTAL=0
|
||||
|
||||
M=$(echo $TIME | grep -o '[0-9]\+m')
|
||||
H=$(echo $TIME | grep -o '[0-9]\+h')
|
||||
D=$(echo $TIME | grep -o '[0-9]\+d')
|
||||
|
||||
if [ -n "${M}" ]; then
|
||||
TOTAL=$(($TOTAL + ${M::-1}))
|
||||
fi
|
||||
if [ -n "${H}" ]; then
|
||||
TOTAL=$(($TOTAL + ${H::-1} * 60))
|
||||
fi
|
||||
if [ -n "${D}" ]; then
|
||||
TOTAL=$(($TOTAL + ${D::-1} * 60 * 24))
|
||||
fi
|
||||
|
||||
echo "*/${TOTAL} * * * * *"
|
||||
;;
|
||||
*)
|
||||
echo "${@}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function build_crontab() {
|
||||
rm -rf ${CRONTAB_FILE}
|
||||
while read i ; do
|
||||
#echo "parse $(jq .[$i] ${CONFIG})"
|
||||
|
||||
SCHEDULE=$(jq -r .[$i].schedule ${CONFIG} | sed 's/\*/\\*/g')
|
||||
if [ "${SCHEDULE}" == "null" ]; then
|
||||
echo "Schedule Missing: $(jq -r .[$i].schedule ${CONFIG})"
|
||||
continue
|
||||
fi
|
||||
SCHEDULE=$(parse_schedule ${SCHEDULE} | sed 's/\\//g')
|
||||
|
||||
if [ "$(jq -r .[$i].command ${CONFIG})" == "null" ]; then
|
||||
echo "Command Missing: $(jq -r .[$i].command ${CONFIG})"
|
||||
continue
|
||||
fi
|
||||
|
||||
TMP_JSON=$(jq -c .[$i] ${CONFIG})
|
||||
COMMAND=$(make_cmd)
|
||||
if [ "$(jq -r .[$i].trigger ${CONFIG})" != "null" ]; then
|
||||
while read j ; do
|
||||
echo "trigger parse $(jq .[$i].trigger[$j] ${CONFIG})"
|
||||
if [ "$(jq .[$i].trigger[$j].command ${CONFIG})" == "null" ]; then
|
||||
echo "Command Missing: $(jq -r .[$i].trigger[$j].command ${CONFIG})"
|
||||
continue
|
||||
fi
|
||||
TMP_JSON=$(jq -c .[$i].trigger[$j] ${CONFIG})
|
||||
COMMAND="$COMMAND && $(make_cmd)"
|
||||
done < <(jq -r '.['$i'].trigger|keys[]' ${CONFIG})
|
||||
fi
|
||||
|
||||
NAME=$(jq -r .[$i].name ${CONFIG})
|
||||
COMMENT=$(jq -r .[$i].comment ${CONFIG})
|
||||
if [ "${NAME}" != "null" ] && [ "${COMMENT}" != "null" ]; then
|
||||
echo "# ${NAME}: ${COMMENT}" >> ${CRONTAB_FILE}
|
||||
elif [ "${COMMENT}" != "null" ]; then
|
||||
echo "# ${COMMENT}" >> ${CRONTAB_FILE}
|
||||
fi
|
||||
|
||||
echo "${SCHEDULE} ${COMMAND}" >> ${CRONTAB_FILE}
|
||||
done < <(jq -r '.|keys[]' ${CONFIG})
|
||||
|
||||
echo "crontab generation complete"
|
||||
cat ${CRONTAB_FILE}
|
||||
}
|
||||
|
||||
# Used to pass json to functions - total hack, I know
|
||||
TMP_JSON=
|
||||
|
||||
if [ "$1" = "crond" ] && [ -f ${CONFIG} ]; then
|
||||
build_crontab
|
||||
fi
|
||||
|
||||
echo "$@"
|
||||
exec "$@"
|
Loading…
x
Reference in New Issue
Block a user