97 Commits

Author SHA1 Message Date
7b249750e6 fix: change group. 2025-01-11 13:23:09 -08:00
903fb6059c fix: trying out gosu. 2025-01-11 13:19:37 -08:00
8cd66168bd fix: using docker user and back to dind. 2025-01-11 12:39:20 -08:00
495c98f4b8 fix: use rootless. 2025-01-11 12:29:22 -08:00
165ea6a764 chore: update pre-commit. 2025-01-11 11:57:44 -08:00
7725e2f697 fix: handle users at Dockerfile instead of in entrypoint script. 2025-01-11 11:57:27 -08:00
176cb2e29c fix: move back to root user to debug permissions more completely. 2025-01-05 11:05:58 -08:00
799ccc8edc fix: permissions for /opt/crontab. 2025-01-05 10:58:49 -08:00
18880befdb fix: LOG_DIR is not used anywhere, remove it. 2025-01-05 10:52:44 -08:00
1a540df103 fix: make /var/log/crontab writeable for everyone. 2025-01-05 10:46:14 -08:00
a87292bd6c chore: casing. 2024-12-31 10:53:17 -08:00
5172ed0b21 fix: use docker user. 2024-12-31 10:48:22 -08:00
a72e84783e chore: update pre-commit. 2024-12-31 10:47:59 -08:00
5aeeb19efb fix: missed id. 2023-06-25 19:39:09 -07:00
9c8b32cd22 chore: add discord notification to cleanup. 2023-06-25 19:37:31 -07:00
c3a1963b9c feat: cleanup old images on the 15th of the month. 2023-06-25 19:07:53 -07:00
c7a835217c chore: update and upgrade require the cache. 2023-06-24 14:39:30 -07:00
ac49cae228 chore: remove arm64. 2023-06-24 14:36:48 -07:00
4cfb3021b8 fix: typo. 2023-06-24 14:34:09 -07:00
e3bfdebe5d chore: move build to the repo rather than the dockerfiles repo. 2023-06-24 14:31:13 -07:00
ccaf6059e7 chore: remove forked build.yml. 2023-06-24 14:20:32 -07:00
2b46bb2949 chore: remove FUNDING.yml 2023-05-21 11:44:58 -07:00
899ec9c46e fix: allow multiple networks. 2023-05-21 11:43:46 -07:00
2b364b93a7 fix: fixing @random since it was broken and removing @every since it never worked. 2023-03-05 12:02:18 -08:00
022399e16a feat: adding TEST_MODE. 2023-03-04 18:31:55 -08:00
035cd4a906 feat: Major Updates
- removed support for `projects` as the feature was very incomplete and it served little purpose
- added support for common settings between jobs using `~~shared-settings` as a key in the config
- cleaned up some items that have long bugged me
  - better reuse of code
  - better variable naming
  - improved flow and readability
  - formatting to the logs
2023-02-18 18:42:20 -08:00
76b524fd17 chore: merge from private git repo. 2022-08-07 08:14:21 -07:00
c530324db3 Merge pull request #2 from SimplicityGuy/additional-fixes
fix: additional fixes
2022-07-16 11:28:48 -07:00
9da424985d fix: moving the top level key name to the "name" property. 2022-07-16 11:24:32 -07:00
31d0b732b2 chore: cleaning up echos. 2022-07-16 10:53:34 -07:00
e69b276707 chore: nicer formatting. 2022-07-16 10:45:41 -07:00
08bbceff95 fix: cleaning script creation. 2022-07-16 10:43:25 -07:00
0643e9ceae chore: moving line to be consistent. 2022-07-16 10:40:02 -07:00
9316c9f5c2 fix: address jq parsing errors when some properties are not set. 2022-07-16 10:22:43 -07:00
a523ad7d74 fix: reset COMMENT. 2022-07-16 09:49:17 -07:00
36ef24f15f chore: omit additional items. 2022-07-16 09:48:40 -07:00
c4da20ce83 fix: addressing issue with parsing json. 2022-07-16 09:29:04 -07:00
8f1d9200e0 chore: tabs to spaces. 2022-07-16 09:21:04 -07:00
2b1c20967d Merge pull request #1 from SimplicityGuy/fixes
fix: shellcheck fixes
2022-07-16 09:00:59 -07:00
1d844f8318 chore: missed one spot using previously set env var. 2022-07-16 08:58:33 -07:00
7f29f0621e chore: standardizing on env vars. 2022-07-16 08:51:38 -07:00
f0c30632bd chore: standardize on {}. 2022-07-16 08:43:14 -07:00
84265413ee chore: adding error handling. 2022-07-16 08:38:01 -07:00
c685687367 chore: rename function to better describe what it does. 2022-07-16 08:34:43 -07:00
dc18a5199d chore: reorganization. 2022-07-16 08:33:41 -07:00
99aa59ddb4 chore: whitespace and dead code cleanup. 2022-07-16 08:31:20 -07:00
f18275eb0d fix: simplifying logic. 2022-07-16 08:28:20 -07:00
882fb137f2 fix: SC1001, SC2018, SC2019 shellcheck fixes. 2022-07-16 08:25:09 -07:00
6d92c31545 fix: SC2005 and SC2046 shellcheck fixes. 2022-07-16 08:22:51 -07:00
dcd3e429f6 fix: SC2166 shellcheck fixes. 2022-07-16 08:17:58 -07:00
96e699c4d7 fix: SC2162 shellcheck fixes. 2022-07-16 08:17:17 -07:00
4d945526fe fix: SC2236 shellcheck fixes. 2022-07-16 08:16:22 -07:00
8c8e19c1c9 fix: SC2006 shellcheck fixes. 2022-07-16 08:15:18 -07:00
82875aba6e fix: SC2004 shellcheck fixes. 2022-07-16 08:14:30 -07:00
9701554b3c fix: SC2086 shellcheck fixes. 2022-07-16 08:13:11 -07:00
653dcb4091 chore: update alpine image. 2022-07-15 21:40:15 -07:00
be97512ec2 ci: kiss 2020-12-26 18:18:02 -07:00
89cf941fea ci: only allow supproted by docker 2020-12-26 18:13:22 -07:00
5611ac5014 ci: try with all platforms 2020-12-26 17:59:12 -07:00
b29a6bb3b3 ci: remove platform list 2020-12-26 17:55:52 -07:00
c2331fc9e7 Merge pull request #40 from chiqomar/master
Adding python conversion for TOML and YAML
2020-12-26 17:45:46 -07:00
88bed2bc4a Fixing entrypoint dealing with JSON mapping 2020-12-23 13:22:15 -05:00
0ef54cc06b Updating README, adding JSON mapping support for consistency 2020-12-23 13:08:05 -05:00
d9b4cecea0 ci: remove unsupported 2020-12-22 16:33:24 -07:00
e55dba16d7 ci: build all platforms 2020-12-22 15:21:41 -07:00
18e9954d74 Using RQ to change toml and yaml to JSON, removing all python stuff 2020-12-21 19:19:52 -05:00
b9bbeacae1 Merge branch 'main' of github.com:willfarrell/docker-crontab 2020-12-08 16:50:33 -05:00
a4e4227327 doc: clean up 2020-12-02 13:22:35 -07:00
ef29a8dadc ci: build on all tags 2020-12-02 13:18:01 -07:00
e28e8dbd2f docs: bump version 2020-12-02 13:16:14 -07:00
cba326b800 ci: remove platform list 2020-12-02 12:54:34 -07:00
16396988c2 ci: fix typo 2020-12-02 12:49:55 -07:00
22900fddd1 ci: extra logging 2020-12-02 12:47:50 -07:00
ff6fde0e5b ci: echo tag value 2020-12-02 12:41:45 -07:00
5f5abd5251 ci: fix typo 2020-12-02 12:01:31 -07:00
84ce659f86 ci: change how tag is built 2020-12-02 11:50:10 -07:00
92d2c91227 ci: force expression tag 2020-12-02 11:45:32 -07:00
0ef158253f ci: fix str 2020-12-02 11:43:12 -07:00
af7994a740 ci: fix how tag is built 2020-12-02 11:42:08 -07:00
1af64f5f7d ci: log env 2020-12-02 11:39:27 -07:00
44a4708123 ci: change how env is set 2020-12-02 11:35:28 -07:00
85a0ecf368 ci: ensure push always happens 2020-12-02 11:32:39 -07:00
cdf3f1a005 docs: update tag versions 2020-12-02 11:31:16 -07:00
e81c5c7fb9 ci: fix docker login 2020-12-02 11:26:30 -07:00
71b0bf9ac2 ci: fix eq operator 2020-12-02 11:23:41 -07:00
61149875ae ci: fix expression 2020-12-02 11:22:09 -07:00
2c633af586 ci: syntax fix 2020-12-02 11:20:25 -07:00
8940b3300e ci: add in build process 2020-12-02 11:18:46 -07:00
6406e1f6e4 docs: add docker pull count 2020-12-01 17:34:27 -07:00
9349a92372 docs: add funcing 2020-11-30 22:41:56 -07:00
c82354e261 Merge pull request #29 from martinec/patch-1
Update README.md
2020-02-26 12:33:12 -07:00
200cd1313f Adding gitignore 2020-01-09 12:46:37 -05:00
5013ecbc3f Adding vscode to gitignore 2019-09-13 16:25:07 -04:00
4f9146b8f8 using tini, functions for most code in entrypoint 2019-09-03 13:15:31 -04:00
4a0d85f51a Adding python conversion for TOML and YAML; slightly more verbose syntax for dockerargs 2019-08-15 10:52:20 -04:00
21e58d0588 feat: add license 2019-08-04 17:10:47 -06:00
911eed81f6 Update README.md
The final Ofelia image has now a size of ~10MB

@see https://github.com/mcuadros/ofelia/pull/20
2019-03-27 14:28:48 +01:00
18 changed files with 764 additions and 377 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
github: [SimplicityGuy]
ko_fi: robertwlodarczyk
custom: [paypal.me/RWlodarczyk]

75
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,75 @@
---
name: crontab
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: '0 1 * * 6'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.actor }}/crontab
jobs:
build-crontab:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository.
uses: actions/checkout@v3
with:
submodules: true
- name: Log in to the GitHub Container Registry.
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Extract metadata (tags, labels) for Docker.
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=ref,event=pr
type=schedule,pattern={{date 'YYYYMMDD'}}
- name: Set up QEMU.
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx.
uses: docker/setup-buildx-action@v2
with:
platforms: linux/amd64
- name: Build and push Docker image to GitHub Container Registry.
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: true
sbom: true
- name: Send notification to Discord.
uses: sarisia/actions-status-discord@v1.12.0
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}

39
.github/workflows/cleanup.yml vendored Normal file
View File

@ -0,0 +1,39 @@
---
name: cleanup
on:
schedule:
- cron: '0 0 15 * *'
env:
IMAGE_NAME: ${{ github.actor }}/docker-crontab
jobs:
cleanup-docker-crontab:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Delete Docker images older than a month.
id: cleanup-images
uses: snok/container-retention-policy@v2
with:
account-type: personal
cut-off: One month ago UTC
keep-at-least: 4
skip-tags: latest
image-names: ${{ env.IMAGE_NAME }}
token: ${{ secrets.GHCR_TOKEN }}
- name: Send notification to Discord.
uses: sarisia/actions-status-discord@v1.12.0
if: always()
with:
title: ${{ env.IMAGE_NAME }}
description: |
succeded cleanup : ${{ steps.cleanup-images.outputs.deleted }}
failed cleanup : ${{ steps.cleanup-images.outputs.failed }}
webhook: ${{ secrets.DISCORD_WEBHOOK }}

6
.gitignore vendored
View File

@ -1,4 +1,10 @@
.idea .idea
*.iml *.iml
.vscode
.DS_Store
config.json config.json
config.working.json
jobs/
projects/

32
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,32 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
hooks:
- id: check-added-large-files
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: check-yaml
- id: detect-aws-credentials
- id: detect-private-key
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 62833a79b57fcd1bc372b136911a0edca60c3dcb # frozen: 0.31.0
hooks:
- id: check-github-workflows
- repo: https://github.com/executablebooks/mdformat
rev: e20b1ac5acb8aba0b49d3a9109c6e6b58684ee83 # frozen: 0.7.21
hooks:
- id: mdformat
additional_dependencies:
- mdformat-gfm
- repo: https://github.com/hadolint/hadolint
rev: c3dc18df7a501f02a560a2cc7ba3c69a85ca01d3 # frozen: v2.13.1-beta
hooks:
- id: hadolint

View File

@ -1,14 +1,67 @@
FROM library/docker:stable #hadolint ignore=DL3007
FROM alpine:latest AS builder
LABEL org.opencontainers.image.title="crontab builder" \
org.opencontainers.image.description="crontab builder" \
org.opencontainers.image.authors="robert@simplicityguy.com" \
org.opencontainers.image.source="https://github.com/SimplicityGuy/alertmanager-discord/blob/main/Dockerfile" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.created="$(date +'%Y-%m-%d')" \
org.opencontainers.image.base.name="docker.io/library/alpine"
ENV RQ_VERSION=1.0.2
WORKDIR /usr/bin/rq/
#hadolint ignore=DL3018
RUN apk update --quiet && \
apk upgrade --quiet && \
apk add --quiet --no-cache \
upx && \
rm /var/cache/apk/* && \
wget --quiet https://github.com/dflemstr/rq/releases/download/v${RQ_VERSION}/rq-v${RQ_VERSION}-x86_64-unknown-linux-musl.tar.gz && \
tar -xvf rq-v${RQ_VERSION}-x86_64-unknown-linux-musl.tar.gz && \
upx --brute rq
#hadolint ignore=DL3007
FROM docker:latest AS release
LABEL org.opencontainers.image.title="crontab" \
org.opencontainers.image.description="A docker job scheduler (aka crontab for docker)." \
org.opencontainers.image.authors="robert@simplicityguy.com" \
org.opencontainers.image.source="https://github.com/SimplicityGuy/docker-crontab/blob/main/Dockerfile" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.created="$(date +'%Y-%m-%d')" \
org.opencontainers.image.base.name="docker.io/library/docker"
ENV HOME_DIR=/opt/crontab ENV HOME_DIR=/opt/crontab
RUN apk add --no-cache --virtual .run-deps gettext bash jq \
&& mkdir -p ${HOME_DIR}/jobs ${HOME_DIR}/projects \
&& adduser -S docker -D
COPY docker-entrypoint / #hadolint ignore=DL3018
ENTRYPOINT ["/docker-entrypoint"] RUN apk update --quiet && \
apk upgrade --quiet && \
apk add --quiet --no-cache \
bash \
coreutils \
curl \
gettext \
jq \
tini \
wget && \
apk add --quiet --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing \
gosu && \
rm /var/cache/apk/* && \
rm -rf /etc/periodic /etc/crontabs/root && \
adduser -S docker -D && \
mkdir -p ${HOME_DIR}/jobs && \
chown -R docker:root ${HOME_DIR}
USER docker
COPY --from=builder /usr/bin/rq/rq /usr/local/bin
COPY entrypoint.sh /opt
ENTRYPOINT ["/usr/bin/gosu", "docker", "/sbin/tini", "--", "/opt/entrypoint.sh"]
HEALTHCHECK --interval=5s --timeout=3s \ HEALTHCHECK --interval=5s --timeout=3s \
CMD ps aux | grep '[c]rond' || exit 1 CMD ps aux | grep '[c]rond' || exit 1
CMD ["crond", "-f", "-d", "6", "-c", "/etc/crontabs"] CMD ["crond", "-f", "-d", "7", "-c", "/etc/crontabs"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 will Farrell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

101
README.md
View File

@ -1,57 +1,59 @@
# docker-crontab # crontab
![crontab](https://github.com/SimplicityGuy/docker-crontab/actions/workflows/build.yml/badge.svg) ![License: MIT](https://img.shields.io/github/license/SimplicityGuy/docker-crontab) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
A simple wrapper over `docker` to all complex cron job to be run in other containers. A simple wrapper over `docker` to all complex cron job to be run in other containers.
## Supported tags and Dockerfile links
- [`latest` (*Dockerfile*)](https://github.com/willfarrell/docker-crontab/blob/master/Dockerfile)
[![](https://images.microbadger.com/badges/version/willfarrell/crontab.svg)](http://microbadger.com/images/willfarrell/crontab "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/willfarrell/crontab.svg)](http://microbadger.com/images/willfarrell/crontab "Get your own image badge on microbadger.com")
## Why? ## Why?
Yes, I'm aware of [mcuadros/ofelia](https://github.com/mcuadros/ofelia) (280MB), it was the main inspiration for this project.
Yes, I'm aware of [mcuadros/ofelia](https://github.com/mcuadros/ofelia) (>250MB when this was created), it was the main inspiration for this project.
A great project, don't get me wrong. It was just missing certain key enterprise features I felt were required to support where docker is heading. A great project, don't get me wrong. It was just missing certain key enterprise features I felt were required to support where docker is heading.
## Features ## Features
- Easy to read schedule syntax allowed. - Easy to read schedule syntax allowed.
- Allows for comments, cause we all need friendly reminders of what `update_script.sh` actually does. - Allows for comments, cause we all need friendly reminders of what `update_script.sh` actually does.
- Start an image using `image`. - Start an image using `image`.
- Run command in a container using `container`. - 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`. - Ability to trigger scripts in other containers on completion cron job using `trigger`.
- Ability to share settings between cron jobs using `~~shared-settings` as a key.
## Config file
The config file can be specified in any of `json`, `toml`, or `yaml`, and can be defined as either an array or mapping (top-level keys will be ignored; can be useful for organizing commands)
## Config.json
- `name`: Human readable name that will be used as the job filename. Will be converted into a slug. Optional. - `name`: Human readable name that will be used as the job filename. Will be converted into a slug. Optional.
- `comment`: Comments to be included with crontab entry. Optional. - `comment`: Comments to be included with crontab entry. Optional.
- `schedule`: Crontab schedule syntax as described in https://en.wikipedia.org/wiki/Cron. Ex `@hourly`, `@every 1h30m`, `* * * * *`. Required. - `schedule`: Crontab schedule syntax as described in https://en.wikipedia.org/wiki/Cron. Examples: `@hourly`, `@every 1h30m`, `* * * * *`. Required.
- `command`: Command to be run on in crontab container or docker container/image. Required. - `command`: Command to be run on in crontab container or docker container/image. Required.
- `image`: Docker images name (ex `library/alpine:3.5`). Optional. - `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. Ignored if `image` is included. Optional.
- `container`: Full container name or container alias if `project` is set. Ignored if `image` is included. Optional.
- `dockerargs`: Command line docker `run`/`exec` arguments for full control. Defaults to ` `. - `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` - `trigger`: Array of docker-crontab subset objects. Sub-set includes: `image`, `container`, `command`, `dockerargs`
- `onstart`: Run the command on `crontab` container start, set to `true`. Optional, defaults to falsey. - `onstart`: Run the command on `crontab` container start, set to `true`. Optional, defaults to false.
See [`config.sample.json`](https://github.com/willfarrell/docker-crontab/blob/master/config.sample.json) for examples. See [`config-samples`](config-samples) for examples.
```json ```json
[{ {
"schedule":"@every 5m", "logrotate": {
"command":"/usr/sbin/logrotate /etc/logrotate.conf" "schedule":"@every 5m",
},{ "command":"/usr/sbin/logrotate /etc/logrotate.conf"
"comment":"Regenerate Certificate then reload nginx", },
"schedule":"43 6,18 * * *", "cert-regen": {
"command":"sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge dns-01 --hook dehydrated-dns'", "comment":"Regenerate Certificate then reload nginx",
"dockerargs":"--env-file /opt/crontab/env/letsencrypt.env -v webapp_nginx_tls_cert:/etc/ssl -v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge", "schedule":"43 6,18 * * *",
"image":"willfarrell/letsencrypt", "command":"sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge dns-01 --hook dehydrated-dns'",
"trigger":[{ "dockerargs":"--it --env-file /opt/crontab/env/letsencrypt.env",
"command":"sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t && /usr/sbin/nginx -s reload'", "volumes":["webapp_nginx_tls_cert:/etc/ssl", "webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge"],
"project":"conduit", "image":"willfarrell/letsencrypt",
"container":"nginx" "trigger":[{
}], "command":"sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t && /usr/sbin/nginx -s reload'",
"onstart":true "container":"nginx"
}] }],
"onstart":true
}
}
``` ```
## How to use ## How to use
@ -71,27 +73,26 @@ docker run -d \
### Use with docker-compose ### Use with docker-compose
1. Figure out which network name used for your docker-compose containers 1. Figure out which network name used for your docker-compose containers
* use `docker network ls` to see existing networks - use `docker network ls` to see existing networks
* if your `docker-compose.yml` is in `my_dir` directory, you probably has network `my_dir_default` - if your `docker-compose.yml` is in `my_dir` directory, you probably has network `my_dir_default`
* otherwise [read the docker-compose docs](https://docs.docker.com/compose/networking/) - otherwise [read the docker-compose docs](https://docs.docker.com/compose/networking/)
2. Add `dockerargs` to your docker-crontab `config.json` 1. Add `dockerargs` to your docker-crontab `config.json`
* use `--network NETWORK_NAME` to connect new container into docker-compose network - use `--network NETWORK_NAME` to connect new container into docker-compose network
* use `--rm --name NAME` to use named container - use `--name NAME` to use named container
* e.g. `"dockerargs": "--network my_dir_default --rm --name my-best-cron-job"` - e.g. `"dockerargs": "--it"`
### Dockerfile ### Dockerfile
```Dockerfile ```Dockerfile
FROM willfarrell/crontab FROM registry.gitlab.com/simplicityguy/docker/crontab
COPY config.json ${HOME_DIR}/ COPY config.json ${HOME_DIR}/
``` ```
### Logrotate Dockerfile ### Logrotate Dockerfile
```Dockerfile ```Dockerfile
FROM willfarrell/crontab FROM registry.gitlab.com/simplicityguy/docker/crontab
RUN apk add --no-cache logrotate RUN apk add --no-cache logrotate
RUN echo "*/5 * * * * /usr/sbin/logrotate /etc/logrotate.conf" >> /etc/crontabs/logrotate RUN echo "*/5 * * * * /usr/sbin/logrotate /etc/logrotate.conf" >> /etc/crontabs/logrotate
@ -99,17 +100,3 @@ COPY logrotate.conf /etc/logrotate.conf
CMD ["crond", "-f"] CMD ["crond", "-f"]
``` ```
### Logging - In Dev
All `stdout` is captured, formatted, and saved to `/var/log/crontab/jobs.log`. Set `LOG_FILE` to `/dev/null` to disable logging.
example: `e6ced859-1563-493b-b1b1-5a190b29e938 2017-06-18T01:27:10+0000 [info] Start Cronjob **map-a-vol** map a volume`
grok: `CRONTABLOG %{DATA:request_id} %{TIMESTAMP_ISO8601:timestamp} \[%{LOGLEVEL:severity}\] %{GREEDYDATA:message}`
## TODO
- [ ] Have ability to auto regenerate crontab on file change (signal HUP?)
- [ ] Run commands on host machine (w/ --privileged?)
- [ ] Write tests
- [ ] Setup TravisCI

View File

@ -0,0 +1,60 @@
[
{
"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"
}
]
},
{
"schedule": "*/5 * * * *",
"command": "/usr/sbin/logrotate /etc/logrotate.conf"
},
{
"comment": "Regenerate Certificate then reload nginx",
"schedule": "43 6,18 * * *",
"command": "sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge dns-01 --hook dehydrated-dns'",
"dockerargs": "--env-file /opt/crontab/env/letsencrypt.env -v webapp_nginx_tls_cert:/etc/ssl -v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge",
"image": "willfarrell/letsencrypt",
"trigger": [
{
"command": "sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t && /usr/sbin/nginx -s reload'",
"project": "conduit",
"container": "nginx"
}
],
"onstart": true
}
]

View File

@ -0,0 +1,50 @@
{
"cron with triggered commands": {
"comment": "cron with triggered commands",
"schedule": "* * * * *",
"command": "echo hello",
"project": "crontab",
"container": "myapp",
"trigger": [{ "command": "echo world", "container": "crontab_myapp_1" }]
},
"map a volume": {
"comment": "map a volume",
"schedule": "* * * * *",
"dockerargs": "-d -v /tmp:/tmp",
"command": "echo new",
"image": "alpine:3.5"
},
"use an ENV from inside a container": {
"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"
},
"trigger every 2 min": {
"comment": "trigger every 2 min",
"schedule": "@every 2m",
"command": "echo 2 minute",
"image": "alpine:3.5",
"trigger": [{ "command": "echo world", "container": "crontab_myapp_1" }]
},
"null": {
"schedule": "*/5 * * * *",
"command": "/usr/sbin/logrotate /etc/logrotate.conf"
},
"Regenerate Certificate then reload nginx": {
"comment": "Regenerate Certificate then reload nginx",
"schedule": "43 6,18 * * *",
"command": "sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge dns-01 --hook dehydrated-dns'",
"dockerargs": "--env-file /opt/crontab/env/letsencrypt.env -v webapp_nginx_tls_cert:/etc/ssl -v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge",
"image": "willfarrell/letsencrypt",
"trigger": [
{
"command": "sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t && /usr/sbin/nginx -s reload'",
"project": "conduit",
"container": "nginx"
}
],
"onstart": true
}
}

View File

@ -0,0 +1,46 @@
cron with triggered commands:
command: echo hello
comment: cron with triggered commands
container: myapp
project: crontab
schedule: '* * * * *'
trigger:
- command: echo world
container: crontab_myapp_1
map a volume:
command: echo new
comment: map a volume
dockerargs: -d -v /tmp:/tmp
image: alpine:3.5
schedule: '* * * * *'
use an ENV from inside a container:
command: sh -c 'echo hourly ${FOO}'
comment: use an ENV from inside a container
dockerargs: -d -e FOO=BAR
image: alpine:3.5
schedule: '@hourly'
trigger every 2 min:
command: echo 2 minute
comment: trigger every 2 min
image: alpine:3.5
schedule: '@every 2m'
trigger:
- command: echo world
container: crontab_myapp_1
null:
command: /usr/sbin/logrotate /etc/logrotate.conf
schedule: '*/5 * * * *'
Regenerate Certificate then reload nginx:
command: sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge
dns-01 --hook dehydrated-dns'
comment: Regenerate Certificate then reload nginx
dockerargs: --env-file /opt/crontab/env/letsencrypt.env -v webapp_nginx_tls_cert:/etc/ssl
-v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge
image: willfarrell/letsencrypt
onstart: true
schedule: 43 6,18 * * *
trigger:
- command: sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t &&
/usr/sbin/nginx -s reload'
container: nginx
project: conduit

View File

@ -0,0 +1,49 @@
# toml files can only have top-loevl mappings, so this is the only sample
["cron with triggered commands"]
comment = "cron with triggered commands"
schedule = "* * * * *"
command = "echo hello"
project = "crontab"
container = "myapp"
[["cron with triggered commands".trigger]]
command = "echo world"
container = "crontab_myapp_1"
["map a volume"]
comment = "map a volume"
schedule = "* * * * *"
dockerargs = "-d -v /tmp:/tmp"
command = "echo new"
image = "alpine:3.5"
["use an ENV from inside a container"]
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"
["trigger every 2 min"]
comment = "trigger every 2 min"
schedule = "@every 2m"
command = "echo 2 minute"
image = "alpine:3.5"
[["trigger every 2 min".trigger]]
command = "echo world"
container = "crontab_myapp_1"
["? /usr/sbin/logrotate /etc/logrotate.conf*/5 * * * *"]
schedule = "*/5 * * * *"
command = "/usr/sbin/logrotate /etc/logrotate.conf"
["Regenerate Certificate then reload nginx"]
comment = "Regenerate Certificate then reload nginx"
schedule = "43 6,18 * * *"
command = "sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge dns-01 --hook dehydrated-dns'"
dockerargs = "--env-file /opt/crontab/env/letsencrypt.env -v ${PWD}:/etc/ssl -v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge"
image = "willfarrell/letsencrypt"
onstart = true
[["Regenerate Certificate then reload nginx".trigger]]
command = "sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t && /usr/sbin/nginx -s reload'"
project = "conduit"
container = "nginx"

View File

@ -0,0 +1,40 @@
- command: echo hello
comment: cron with triggered commands
container: myapp
project: crontab
schedule: '* * * * *'
trigger:
- command: echo world
container: crontab_myapp_1
- command: echo new
comment: map a volume
dockerargs: -d -v /tmp:/tmp
image: alpine:3.5
schedule: '* * * * *'
- command: sh -c 'echo hourly ${FOO}'
comment: use an ENV from inside a container
dockerargs: -d -e FOO=BAR
image: alpine:3.5
schedule: '@hourly'
- command: echo 2 minute
comment: trigger every 2 min
image: alpine:3.5
schedule: '@every 2m'
trigger:
- command: echo world
container: crontab_myapp_1
- command: /usr/sbin/logrotate /etc/logrotate.conf
schedule: '*/5 * * * *'
- command: sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge
dns-01 --hook dehydrated-dns'
comment: Regenerate Certificate then reload nginx
dockerargs: --env-file /opt/crontab/env/letsencrypt.env -v webapp_nginx_tls_cert:/etc/ssl
-v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge
image: willfarrell/letsencrypt
onstart: true
schedule: 43 6,18 * * *
trigger:
- command: sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t &&
/usr/sbin/nginx -s reload'
container: nginx
project: conduit

View File

@ -1,47 +0,0 @@
[{
"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"
}]
},{
"schedule":"*/5 * * * *",
"command":"/usr/sbin/logrotate /etc/logrotate.conf"
},{
"comment":"Regenerate Certificate then reload nginx",
"schedule":"43 6,18 * * *",
"command":"sh -c 'dehydrated --cron --out /etc/ssl --domain ${LE_DOMAIN} --challenge dns-01 --hook dehydrated-dns'",
"dockerargs":"--env-file /opt/crontab/env/letsencrypt.env -v webapp_nginx_tls_cert:/etc/ssl -v webapp_nginx_acme_challenge:/var/www/.well-known/acme-challenge",
"image":"willfarrell/letsencrypt",
"trigger":[{
"command":"sh -c '/etc/scripts/make_hpkp ${NGINX_DOMAIN} && /usr/sbin/nginx -t && /usr/sbin/nginx -s reload'",
"project":"conduit",
"container":"nginx"
}],
"onstart":true
}]

View File

@ -11,5 +11,4 @@ services:
restart: always restart: always
volumes: volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro" - "/var/run/docker.sock:/var/run/docker.sock:ro"
# - "/usr/bin/docker:/usr/bin/docker:ro" - "${PWD}/config-samples/config.sample.mapping.json:/opt/crontab/config.json:rw"
- "/Users/willfarrell/Development/docker/docker-crontab/config.json:/opt/crontab/config.json:rw"

View File

@ -1,244 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -z "$DOCKER_HOST" -a "$DOCKER_PORT_2375_TCP" ]; then
export DOCKER_HOST='tcp://docker:2375'
fi
# for local testing only
#HOME_DIR=.
if [ "${LOG_FILE}" == "" ]; then
LOG_DIR=/var/log/crontab
LOG_FILE=${LOG_DIR}/jobs.log
mkdir -p ${LOG_DIR}
touch ${LOG_FILE}
fi
CONFIG=${HOME_DIR}/config.json
DOCKER_SOCK=/var/run/docker.sock
CRONTAB_FILE=/etc/crontabs/docker
# Ensure dir exist - in case of volume mapping
mkdir -p ${HOME_DIR}/jobs ${HOME_DIR}/projects
ensure_docker_socket_accessible() {
if ! grep -q "^docker:" /etc/group; then
# Ensure 'docker' user has permissions for docker socket (without changing permissions)
DOCKER_GID=$(stat -c '%g' ${DOCKER_SOCK})
if [ "${DOCKER_GID}" != "0" ]; then
if ! grep -qE "^[^:]+:[^:]+:${DOCKER_GID}:" /etc/group; then
# No group with such gid exists - create group docker
addgroup -g ${DOCKER_GID} docker
adduser docker docker
else
# Group with such gid exists - add user "docker" to this group
DOCKER_GROUP_NAME=`getent group "${DOCKER_GID}" | awk -F':' '{{ print $1 }}'`
adduser docker $DOCKER_GROUP_NAME
fi
else
# Docker socket belongs to "root" group - add user "docker" to this group
adduser docker root
fi
fi
}
slugify() {
echo "$@" | iconv -t ascii | sed -r s/[~\^]+//g | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z
}
make_image_cmd() {
DOCKERARGS=$(echo ${1} | jq -r .dockerargs)
if [ "${DOCKERARGS}" == "null" ]; then DOCKERARGS=; fi
IMAGE=$(echo ${1} | jq -r .image | envsubst)
TMP_COMMAND=$(echo ${1} | jq -r .command)
echo "docker run ${DOCKERARGS} ${IMAGE} ${TMP_COMMAND}"
}
make_container_cmd() {
DOCKERARGS=$(echo ${1} | jq -r .dockerargs)
if [ "${DOCKERARGS}" == "null" ]; then DOCKERARGS=; fi
SCRIPT_NAME=$(echo ${1} | jq -r .name)
SCRIPT_NAME=$(slugify $SCRIPT_NAME)
PROJECT=$(echo ${1} | jq -r .project)
CONTAINER=$(echo ${1} | jq -r .container | envsubst)
TMP_COMMAND=$(echo ${1} | jq -r .command)
if [ "${PROJECT}" != "null" ]; then
# create bash script to detect all running containers
if [ "${SCRIPT_NAME}" == "null" ]; then
SCRIPT_NAME=$(cat /proc/sys/kernel/random/uuid)
fi
cat << EOF > ${HOME_DIR}/projects/${SCRIPT_NAME}.sh
#!/usr/bin/env bash
set -e
CONTAINERS=\$(docker ps --format '{{.Names}}' | grep -E "^${PROJECT}_${CONTAINER}.[0-9]+")
for CONTAINER_NAME in \$CONTAINERS; do
docker exec ${DOCKERARGS} \${CONTAINER_NAME} ${TMP_COMMAND}
done
EOF
echo "/bin/bash ${HOME_DIR}/projects/${SCRIPT_NAME}.sh"
else
echo "docker exec ${DOCKERARGS} ${CONTAINER} ${TMP_COMMAND}"
fi
}
#make_host_cmd() {
# HOST_BINARY=$(echo ${1} | jq -r .host)
# TMP_COMMAND=$(echo ${1} | jq -r .command)
# echo "${HOST_BINARY} ${TMP_COMMAND}"
#}
make_cmd() {
if [ "$(echo ${1} | jq -r .image)" != "null" ]; then
make_image_cmd "$1"
elif [ "$(echo ${1} | jq -r .container)" != "null" ]; then
make_container_cmd "$1"
#elif [ "$(echo ${1} | jq -r .host)" != "null" ]; then
# make_host_cmd "$1"
else
echo ${1} | jq -r .command
fi
}
parse_schedule() {
case $1 in
"@yearly")
echo "0 0 1 1 *"
;;
"@annually")
echo "0 0 1 1 *"
;;
"@monthly")
echo "0 0 1 * *"
;;
"@weekly")
echo "0 0 * * 0"
;;
"@daily")
echo "0 0 * * *"
;;
"@midnight")
echo "0 0 * * *"
;;
"@hourly")
echo "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}
ONSTART=()
while read i ; do
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
COMMENT=$(jq -r .[$i].comment ${CONFIG})
if [ "${COMMENT}" != "null" ]; then
echo "# ${COMMENT}" >> ${CRONTAB_FILE}
fi
SCRIPT_NAME=$(jq -r .[$i].name ${CONFIG})
SCRIPT_NAME=$(slugify $SCRIPT_NAME)
if [ "${SCRIPT_NAME}" == "null" ]; then
SCRIPT_NAME=$(cat /proc/sys/kernel/random/uuid)
fi
COMMAND="/bin/bash ${HOME_DIR}/jobs/${SCRIPT_NAME}.sh"
cat << EOF > ${HOME_DIR}/jobs/${SCRIPT_NAME}.sh
#!/usr/bin/env bash
set -e
# TODO find workaround
# [error] write /dev/stdout: broken pipe <- when using docker commands
#UUID=\$(cat /proc/sys/kernel/random/uuid)
#exec > >(read message; echo "\${UUID} \$(date -Iseconds) [info] \$message" | tee -a ${LOG_FILE} )
#exec 2> >(read message; echo "\${UUID} \$(date -Iseconds) [error] \$message" | tee -a ${LOG_FILE} >&2)
echo "Start Cronjob **${SCRIPT_NAME}** ${COMMENT}"
$(make_cmd "$(jq -c .[$i] ${CONFIG})")
EOF
if [ "$(jq -r .[$i].trigger ${CONFIG})" != "null" ]; then
while read j ; do
if [ "$(jq .[$i].trigger[$j].command ${CONFIG})" == "null" ]; then
echo "Command Missing: $(jq -r .[$i].trigger[$j].command ${CONFIG})"
continue
fi
#TRIGGER_COMMAND=$(make_cmd "$(jq -c .[$i].trigger[$j] ${CONFIG})")
echo "$(make_cmd "$(jq -c .[$i].trigger[$j] ${CONFIG})")" >> ${HOME_DIR}/jobs/${SCRIPT_NAME}.sh
#COMMAND="${COMMAND} && ${TRIGGER_COMMAND}"
done < <(jq -r '.['$i'].trigger|keys[]' ${CONFIG})
fi
echo "echo \"End Cronjob **${SCRIPT_NAME}** ${COMMENT}\"" >> ${HOME_DIR}/jobs/${SCRIPT_NAME}.sh
echo "${SCHEDULE} ${COMMAND}" >> ${CRONTAB_FILE}
if [ "$(jq -r .[$i].onstart ${CONFIG})" == "true" ]; then
ONSTART+=("${COMMAND}")
fi
done < <(jq -r '.|keys[]' ${CONFIG})
echo "##### crontab generation complete #####"
cat ${CRONTAB_FILE}
echo "##### run commands with onstart #####"
for COMMAND in "${ONSTART[@]}"; do
echo "${COMMAND}"
${COMMAND} &
done
}
ensure_docker_socket_accessible
if [ "$1" = "crond" ]; then
if [ -f ${CONFIG} ]; then
build_crontab
else
echo "Unable to find ${HOME_DIR}/config.json"
fi
fi
echo "$@"
exec "$@"

238
entrypoint.sh Executable file
View File

@ -0,0 +1,238 @@
#!/bin/bash
set -e
CRONTAB_FILE=/etc/crontabs/docker
if [ -z "${HOME_DIR}" ] && [ -n "${TEST_MODE}" ]; then
HOME_DIR=/tmp/crontab-docker-testing
CRONTAB_FILE=${HOME_DIR}/test
elif [ -z "${HOME_DIR}" ]; then
echo "HOME_DIR not set."
exit 1
fi
# Ensure dir exist - in case of volume mapping.
mkdir -p "${HOME_DIR}"/jobs
if [ -z "${DOCKER_HOST}" ] && [ -a "${DOCKER_PORT_2375_TCP}" ]; then
export DOCKER_HOST="tcp://docker:2375"
fi
normalize_config() {
JSON_CONFIG={}
if [ -f "${HOME_DIR}/config.json" ]; then
JSON_CONFIG="$(cat "${HOME_DIR}"/config.json)"
elif [ -f "${HOME_DIR}/config.toml" ]; then
JSON_CONFIG="$(rq -t <<< "$(cat "${HOME_DIR}"/config.toml)")"
elif [ -f "${HOME_DIR}/config.yml" ]; then
JSON_CONFIG="$(rq -y <<< "$(cat "${HOME_DIR}"/config.yml)")"
elif [ -f "${HOME_DIR}/config.yaml" ]; then
JSON_CONFIG="$(rq -y <<< "$(cat "${HOME_DIR}"/config.yaml)")"
fi
jq -S -r '."~~shared-settings" as $shared | del(."~~shared-settings") | to_entries | map_values(.value + { name: .key } + $shared)' <<< "${JSON_CONFIG}" > "${HOME_DIR}"/config.working.json
}
slugify() {
echo "${@}" | iconv -t ascii | sed -r s/[~^]+//g | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr '[:upper:]' '[:lower:]'
}
make_image_cmd() {
DOCKERARGS=$(echo "${1}" | jq -r .dockerargs)
ENVIRONMENT=$(echo "${1}" | jq -r 'select(.environment != null) | .environment | map("--env " + .) | join(" ")')
EXPOSE=$(echo "${1}" | jq -r 'select(.expose != null) | .expose | map("--expose " + .) | join(" ")' )
NAME=$(echo "${1}" | jq -r 'select(.name != null) | .name')
NETWORKS=$(echo "${1}" | jq -r 'select(.networks != null) | .networks | map("--network " + .) | join(" ")')
PORTS=$(echo "${1}" | jq -r 'select(.ports != null) | .ports | map("--publish " + .) | join(" ")')
VOLUMES=$(echo "${1}" | jq -r 'select(.volumes != null) | .volumes | map("--volume " + .) | join(" ")')
if [ "${DOCKERARGS}" == "null" ]; then DOCKERARGS=; fi
DOCKERARGS+=" "
if [ -n "${ENVIRONMENT}" ]; then DOCKERARGS+="${ENVIRONMENT} "; fi
if [ -n "${EXPOSE}" ]; then DOCKERARGS+="${EXPOSE} "; fi
if [ -n "${NAME}" ]; then DOCKERARGS+="--name ${NAME} "; fi
if [ -n "${NETWORKS}" ]; then DOCKERARGS+="${NETWORKS} "; fi
if [ -n "${PORTS}" ]; then DOCKERARGS+="${PORTS} "; fi
if [ -n "${VOLUMES}" ]; then DOCKERARGS+="${VOLUMES} "; fi
IMAGE=$(echo "${1}" | jq -r .image | envsubst)
if [ "${IMAGE}" == "null" ]; then return; fi
COMMAND=$(echo "${1}" | jq -r .command)
echo "docker run ${DOCKERARGS} ${IMAGE} ${COMMAND}"
}
make_container_cmd() {
DOCKERARGS=$(echo "${1}" | jq -r .dockerargs)
if [ "${DOCKERARGS}" == "null" ]; then DOCKERARGS=; fi
CONTAINER=$(echo "${1}" | jq -r .container | envsubst)
if [ "${CONTAINER}" == "null" ]; then return; fi
COMMAND=$(echo "${1}" | jq -r .command )
if [ "${COMMAND}" == "null" ]; then return; fi
echo "docker exec ${DOCKERARGS} ${CONTAINER} ${COMMAND}"
}
make_cmd() {
if [ "$(echo "${1}" | jq -r .image)" != "null" ]; then
make_image_cmd "${1}"
elif [ "$(echo "${1}" | jq -r .container)" != "null" ]; then
make_container_cmd "${1}"
else
echo "${1}" | jq -r .command
fi
}
parse_schedule() {
IFS=" "
read -a params <<< "$@"
case ${params[0]} in
"@yearly" | "@annually")
echo "0 0 1 1 *"
;;
"@monthly")
echo "0 0 1 * *"
;;
"@weekly")
echo "0 0 * * 0"
;;
"@daily")
echo "0 0 * * *"
;;
"@midnight")
echo "0 0 * * *"
;;
"@hourly")
echo "0 * * * *"
;;
"@random")
M="*"
H="*"
D="*"
for when in "${params[@]:1}"
do
case $when in
"@m")
M=$(shuf -i 0-59 -n 1)
;;
"@h")
H=$(shuf -i 0-23 -n 1)
;;
"@d")
D=$(shuf -i 0-6 -n 1)
;;
esac
done
echo "${M} ${H} * * ${D}"
;;
*)
echo "${params[@]}"
;;
esac
}
function build_crontab() {
rm -rf "${CRONTAB_FILE}"
ONSTART=()
while read -r i ; do
KEY=$(jq -r .["$i"] "${CONFIG}")
SCHEDULE=$(echo "${KEY}" | jq -r '.schedule' | sed 's/\*/\\*/g')
if [ "${SCHEDULE}" == "null" ]; then
echo "'schedule' missing: '${KEY}"
continue
fi
SCHEDULE=$(parse_schedule "${SCHEDULE}" | sed 's/\\//g')
COMMAND=$(echo "${KEY}" | jq -r '.command')
if [ "${COMMAND}" == "null" ]; then
echo "'command' missing: '${KEY}'"
continue
fi
COMMENT=$(echo "${KEY}" | jq -r '.comment')
SCRIPT_NAME=$(echo "${KEY}" | jq -r '.name')
SCRIPT_NAME=$(slugify "${SCRIPT_NAME}")
if [ "${SCRIPT_NAME}" == "null" ]; then
SCRIPT_NAME=$(cat /proc/sys/kernel/random/uuid)
fi
CRON_COMMAND=$(make_cmd "${KEY}")
SCRIPT_PATH="${HOME_DIR}/jobs/${SCRIPT_NAME}.sh"
touch "${SCRIPT_PATH}"
chmod +x "${SCRIPT_PATH}"
{
echo "#\!/usr/bin/env bash"
echo "set -e"
echo ""
echo "echo \"start cron job __${SCRIPT_NAME}__\""
echo "${CRON_COMMAND}"
} > "${SCRIPT_PATH}"
TRIGGER=$(echo "${KEY}" | jq -r '.trigger')
if [ "${TRIGGER}" != "null" ]; then
while read -r j ; do
TRIGGER_KEY=$(echo "${KEY}" | jq -r .trigger["$j"])
TRIGGER_COMMAND=$(echo "${TRIGGER_KEY}" | jq -r '.command')
if [ "${TRIGGER_COMMAND}" == "null" ]; then
continue
fi
make_cmd "${TRIGGER_KEY}" >> "${SCRIPT_PATH}"
done < <(echo "${KEY}" | jq -r '.trigger | keys[]')
fi
echo "echo \"end cron job __${SCRIPT_NAME}__\"" >> "${SCRIPT_PATH}"
if [ "${COMMENT}" != "null" ]; then
echo "# ${COMMENT}" >> "${CRONTAB_FILE}"
fi
echo "${SCHEDULE} ${SCRIPT_PATH}" >> "${CRONTAB_FILE}"
ONSTART_COMMAND=$(echo "${KEY}" | jq -r '.onstart')
if [ "${ONSTART_COMMAND}" == "true" ]; then
ONSTART+=("${SCRIPT_PATH}")
fi
done < <(jq -r '. | keys[]' "${CONFIG}")
printf "##### crontab generated #####\n"
cat "${CRONTAB_FILE}"
printf "##### run commands with onstart #####\n"
for ONSTART_COMMAND in "${ONSTART[@]}"; do
printf "%s\n" "${ONSTART_COMMAND}"
${ONSTART_COMMAND} &
done
printf "##### cron running #####\n"
}
start_app() {
normalize_config
export CONFIG=${HOME_DIR}/config.working.json
if [ ! -f "${CONFIG}" ]; then
printf "missing generated %s. exiting.\n" "${CONFIG}"
exit 1
fi
if [ "${1}" == "crond" ]; then
build_crontab
fi
printf "%s\n" "${@}"
exec "${@}"
}
printf "✨ starting crontab container ✨\n"
start_app "${@}"

View File

@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -e
# This file is for testing the logging of docker output #8
LOG_FILE=./jobs.log
touch ${LOG_FILE}
UUID="xxxxxxxxxxxxxxxxx"
exec > >(read message; echo "${UUID} $(date) [info] $message" | tee -a ${LOG_FILE} )
exec 2> >(read message; echo "${UUID} $(date) [error] $message" | tee -a ${LOG_FILE} >&2)
echo "Start"
docker run alpine sh -c 'while :; do echo "ping"; sleep 1; done'
# [error] write /dev/stdout: broken pipe
# --log-driver syslog <- errors
# --log-driver none <- errors
echo "End"