diff --git a/.gitattributes b/.gitattributes index bb27001..6e7f2a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ * text=auto -*.sh eol=lf \ No newline at end of file +*.sh eol=lf +cron-tick-execute eol=lf +cron-tick eol=lf \ No newline at end of file diff --git a/.github/settings.yml b/.github/settings.yml index f980fff..945844f 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -1,6 +1,6 @@ # https://developer.github.com/v3/repos/#edit repository: - name: docker-$$IMAGE_NAME$$ + name: docker-cron-base description: "" homepage: https://homecentr.github.io/ private: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25d8109..a3d1337 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: pull_request: env: - IMAGE_NAME: "homecentr/$$IMAGE_NAME$$" + IMAGE_NAME: "homecentr/cron-base" jobs: build: @@ -30,7 +30,7 @@ jobs: run: docker build . -t ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.docker_tag }} - name: Test Docker image - run: cd tests && sudo gradle test --info -Dimage_tag=${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.docker_tag }} + run: cd tests && sudo gradle test --info -Ddocker_image_tag=${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.docker_tag }} - name: Scan with Phonito Security uses: phonito/phonito-scanner-action@master diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 9d34d27..c956302 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -5,7 +5,7 @@ on: - master env: - IMAGE_NAME: "homecentr/$$IMAGE_NAME$$" + IMAGE_NAME: "homecentr/cron-base" jobs: build: @@ -22,6 +22,7 @@ jobs: uses: codfish/semantic-release-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_URL: "https://api.github.com" - name: Verify Dockerfile with Hadolint uses: brpaz/hadolint-action@master @@ -40,7 +41,7 @@ jobs: - name: Test Docker image if: env.RELEASE_VERSION != '' - run: cd tests && sudo gradle test -Dimage_tag=${{ env.IMAGE_NAME }}:${{ env.RELEASE_VERSION }} + run: cd tests && sudo gradle test -Ddocker_image_tag=${{ env.IMAGE_NAME }}:${{ env.RELEASE_VERSION }} - name: Scan with Phonito Security if: env.RELEASE_VERSION != '' diff --git a/.github/workflows/regular_scan.yml b/.github/workflows/regular_scan.yml index b4ca6d5..b0a3c45 100644 --- a/.github/workflows/regular_scan.yml +++ b/.github/workflows/regular_scan.yml @@ -4,7 +4,7 @@ on: - cron: '0 6 * * *' env: - IMAGE_NAME: "homecentr/$$IMAGE_NAME$$" + IMAGE_NAME: "homecentr/cron-base" jobs: build: diff --git a/Dockerfile b/Dockerfile index c3c78df..576874c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1,15 @@ -FROM alpine \ No newline at end of file +FROM homecentr/base:2.3.0-alpine + +ENV CRON_SCHEDULE="" +ENV PUSH_GATEWAY_URL="" + +# Copy s6 configuration and scripts +COPY ./fs/ / + +RUN rm /etc/crontabs/root && \ + apk add --no-cache \ + # Required to push metrics to push gateway + curl=7.67.0-r0 \ + # Required for UUID generation + util-linux=2.34-r1 && \ + chmod a+x /usr/sbin/cron-tick-execute \ No newline at end of file diff --git a/README.md b/README.md index 56b1530..07dc304 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,55 @@ -# HomeCentr - $$IMAGE_NAME$$ -Template repository for Docker container repositories +[![Project status](https://img.shields.io/badge/Project%20status-stable%20%26%20actively%20maintaned-green.svg)](https://github.com/homecentr/docker-cron-base/graphs/commit-activity) +[![](https://img.shields.io/github/issues-raw/homecentr/docker-cron-base/bug?label=open%20bugs)](https://github.com/homecentr/docker-cron-base/labels/bug) +[![](https://images.microbadger.com/badges/version/homecentr/cron-base.svg)](https://hub.docker.com/repository/docker/homecentr/cron-base) +[![](https://img.shields.io/docker/pulls/homecentr/cron-base.svg)](https://hub.docker.com/repository/docker/homecentr/cron-base) +[![](https://img.shields.io/docker/image-size/homecentr/cron-base/latest)](https://hub.docker.com/repository/docker/homecentr/cron-base) -## Project status +![CI/CD on master](https://github.com/homecentr/docker-cron-base/workflows/CI/CD%20on%20master/badge.svg) +![Regular Docker image vulnerability scan](https://github.com/homecentr/docker-cron-base/workflows/Regular%20Docker%20image%20vulnerability%20scan/badge.svg) -## Usage (Docker compose) -### Env. variables -### Exposed ports +# HomeCentr - cron-base +This docker image is used as base image for all homecentr images which require a cron scheduler but can also be used on its own. The image executes a mounted script at the specified schedule and reports the results into a Prometheus [push-gateway](https://github.com/prometheus/pushgateway) for easy monitoring and alerting. + +## Usage + +```yml +version: "3.7" +services: + cron-base: + build: . + image: homecentr/cron-base + restart: unless-stopped + environment: + CRON_SCHEDULE: "* * * * *" # Run script every minute + PUSH_GATEWAY_URL: "http://push_gateway:9091/metrics/job/cron/label-name/label-value" + volumes: + - ./example/success:/config # must contain cron-tick script +``` + +## Environment variables + +| Name | Default value | Description | +|------|---------------|-------------| +| PUID | 7077 | UID of the user the cron-tick script should be running as. | +| PGID | 7077 | GID of the group the cron-tick script should be running as. | +| CRON_SCHEDULE | | [Cron expression](https://crontab.guru/) which defines when/how often the script will be executed. This variable is **mandatory**. | +| PUSH_GATEWAY_URL | | URL of the [push gateway](https://github.com/prometheus/pushgateway) job where the metrics should be reported. The reporting is skipped if the variable is not set. | + +## Exposed ports + +The image does not expose any ports. + +## Volumes + +| Container path | Description | +|-------------|----------------| +| /config | Directory containing the script which should be executed, the script must be named `cron-tick`. | ## Security +The container is regularly scanned for vulnerabilities and updated. Further info can be found in the [Security tab](https://github.com/homecentr/docker-cron-base/security). + +### Container user +The container supports privilege drop. Even though the container starts as root, it will use the permissions only to perform the initial set up. The cron-tick script is executed as UID/GID provided in the PUID and PGID environment variables. -### Vulnerabilities \ No newline at end of file +:warning: Do not change the container user directly using the `user` Docker compose property or using the `--user` argument. This would break the privilege drop logic. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 980a102..499d1bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,15 @@ version: "3.7" services: - $$IMAGE_NAME$$: + cron: build: . - image: homecentr/$$IMAGE_NAME$$ - restart: unless-stopped \ No newline at end of file + image: homecentr/cron-base:local + environment: + CRON_SCHEDULE: "* * * * *" + PUSH_GATEWAY_URL: "http://push_gateway:9091/metrics/job/cron/instance/base" + volumes: + - ./example/cron-tick:/config/cron-tick + #- ./example/cron-tick-fail:/config/cron-tick + push_gateway: + image: prom/pushgateway + ports: + - 9091:9091 \ No newline at end of file diff --git a/example/failure/cron-tick b/example/failure/cron-tick new file mode 100644 index 0000000..55c8452 --- /dev/null +++ b/example/failure/cron-tick @@ -0,0 +1,5 @@ +#!/usr/bin/env ash + +sleep 2 + +exit 10 \ No newline at end of file diff --git a/example/success/cron-tick b/example/success/cron-tick new file mode 100644 index 0000000..eba081d --- /dev/null +++ b/example/success/cron-tick @@ -0,0 +1,4 @@ +#!/usr/bin/env ash + + +mktemp -d \ No newline at end of file diff --git a/fs/etc/cont-init.d/20-tick-script.sh b/fs/etc/cont-init.d/20-tick-script.sh new file mode 100644 index 0000000..a311a78 --- /dev/null +++ b/fs/etc/cont-init.d/20-tick-script.sh @@ -0,0 +1,4 @@ +#!/usr/bin/with-contenv sh + +cp /config/cron-tick /usr/sbin/cron-tick +chmod a+x /usr/sbin/cron-tick \ No newline at end of file diff --git a/fs/etc/cont-init.d/21-crontab.sh b/fs/etc/cont-init.d/21-crontab.sh new file mode 100644 index 0000000..e4c3b87 --- /dev/null +++ b/fs/etc/cont-init.d/21-crontab.sh @@ -0,0 +1,10 @@ +#!/usr/bin/with-contenv sh + +if [ "$CRON_SCHEDULE" == "" ] +then + echo "The env. variable CRON_SCHEDULE is not set but is mandatory." + exit 1 +fi + +USER_NAME=$(getent passwd "$PUID" | cut -d: -f1) +echo "$CRON_SCHEDULE /usr/sbin/cron-tick-execute" > /etc/crontabs/$USER_NAME \ No newline at end of file diff --git a/fs/etc/services.d/cron/finish b/fs/etc/services.d/cron/finish new file mode 100644 index 0000000..010f8ca --- /dev/null +++ b/fs/etc/services.d/cron/finish @@ -0,0 +1,3 @@ +#!/usr/bin/execlineb -S1 + +s6-svscanctl -t /var/run/s6/services \ No newline at end of file diff --git a/fs/etc/services.d/cron/run b/fs/etc/services.d/cron/run new file mode 100644 index 0000000..4f3b604 --- /dev/null +++ b/fs/etc/services.d/cron/run @@ -0,0 +1,6 @@ +#!/usr/bin/with-contenv sh + +# crond must be ALWAYS started as root (requires setpgid) +# exec s6-setuidgid "$PUID:$PGID" crond -f -d 2 + +exec crond -f -l 2 \ No newline at end of file diff --git a/fs/usr/sbin/cron-tick-execute b/fs/usr/sbin/cron-tick-execute new file mode 100644 index 0000000..00d8194 --- /dev/null +++ b/fs/usr/sbin/cron-tick-execute @@ -0,0 +1,35 @@ +#!/usr/bin/env ash +EXEC_ID=$(uuidgen) +ALREADY_RUNNING=$(pgrep -f /usr/sbin/cron-tick-execute > /dev/null 2> /dev/null || "YES") + +if [ "$ALREADY_RUNNING" == "YES" ] +then + echo "The last execution is still running. Skipping this one..." + exit 0 +fi + +echo "====== Execution started ($(date), $EXEC_ID) =======" + +OUTPUT=$(time -f "TIME_OUTPUT|%x|%e" /usr/sbin/cron-tick 2>&1) + +TICK_EXIT_CODE=$(echo "$OUTPUT" | grep "TIME_OUTPUT" | cut -d'|' -f2 | tr -d '[:space:]') +TICK_DURATION=$(echo "$OUTPUT" | grep "TIME_OUTPUT" | cut -d'|' -f3 | tr -d '[:space:]') + +echo "Script exit code: $TICK_EXIT_CODE, execution duration: $TICK_DURATION seconds" + +if [ "$PUSH_GATEWAY_URL" == "" ] +then + echo "PUSH_GATEWAY_URL variable not set, skipping pushing metrics to push gateway." +else + echo "Pushing metrics to $PUSH_GATEWAY_URL" + +cat < - - - - - - diff --git a/tests/.idea/.name b/tests/.idea/.name index 71d0457..4f0a63b 100644 --- a/tests/.idea/.name +++ b/tests/.idea/.name @@ -1 +1 @@ -docker-$$IMAGE_NAME$$-tests \ No newline at end of file +docker-cron-base-tests \ No newline at end of file diff --git a/tests/.idea/compiler.xml b/tests/.idea/compiler.xml index 098cf64..26bfddf 100644 --- a/tests/.idea/compiler.xml +++ b/tests/.idea/compiler.xml @@ -2,8 +2,8 @@ - - + + \ No newline at end of file diff --git a/tests/.idea/jarRepositories.xml b/tests/.idea/jarRepositories.xml index fdc392f..5060e6b 100644 --- a/tests/.idea/jarRepositories.xml +++ b/tests/.idea/jarRepositories.xml @@ -16,5 +16,10 @@