Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Test entrypoint script
run: sh docker-entrypoint-test.sh

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added

- `BACKUP_INTERVAL_MS` environment variable for the Docker daemon image, mapped by `docker-entrypoint.sh` to `-Dbackup.scheduled.interval.ms`, so the backup interval can be configured from `.env`/`docker-compose.yml` without overriding the entrypoint
- `docker-entrypoint-test.sh`, a shell test that runs `docker-entrypoint.sh` against a stub `java` and asserts the argument list built for each combination of `BACKUP_DIRECTORY`, `SCHEDULED_USERS` and `BACKUP_INTERVAL_MS`, including empty values and values containing spaces; it runs in the `docker-build` CI job, so entrypoint changes are no longer merged unexecuted

### Fixed

Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ Windows:
mvn clean test
```

The Docker entrypoint script is covered by a separate shell test, which runs `docker-entrypoint.sh` against a stub `java` and asserts the arguments it builds. It requires a POSIX shell, so run it on Linux / macOS (or under WSL or Git Bash on Windows):

```bash
sh docker-entrypoint-test.sh
```

Both suites run in CI on every pull request.

## Questions

Ask in the [Discord server](https://discord.gg/xXtuAQ2).
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ mvn clean test

If you see `BUILD SUCCESS`, the tests have passed.

### Entrypoint Script Test

`docker-entrypoint.sh` is covered by a shell test that runs it against a stub `java` and asserts the arguments it builds. A POSIX shell is required, so on Windows it is run under WSL or Git Bash.

Linux / macOS:

```bash
sh docker-entrypoint-test.sh
```

Windows (Git Bash / WSL):

```bash
sh docker-entrypoint-test.sh
```

If you see `All docker-entrypoint.sh tests passed.`, the tests have passed.

## Development

### Building and Running Locally
Expand Down
118 changes: 118 additions & 0 deletions docker-entrypoint-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/bin/sh
# Tests docker-entrypoint.sh by putting a stub `java` on PATH that prints its
# arguments instead of starting a JVM, then asserting the argument list the
# entrypoint builds for each combination of the environment variables it reads.
#
# Each argument is printed wrapped in brackets, so a value containing a space is
# distinguishable from two separate arguments.
#
# Run from the repository root:
# sh docker-entrypoint-test.sh

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
ENTRYPOINT="$SCRIPT_DIR/docker-entrypoint.sh"

if [ ! -f "$ENTRYPOINT" ]; then
echo "docker-entrypoint.sh not found at $ENTRYPOINT" >&2
exit 1
fi

STUB_BIN=$(mktemp -d)
trap 'rm -rf "$STUB_BIN"' EXIT INT TERM

cat > "$STUB_BIN/java" <<'STUB'
#!/bin/sh
for arg in "$@"; do
printf '[%s]' "$arg"
done
echo
STUB
chmod +x "$STUB_BIN/java"

PROFILE="[-Dspring.profiles.active=daemon]"
JAR="[-jar][gh-backup.jar]"

failures=0

# Runs the entrypoint with only the given NAME=VALUE assignments exported, so a
# variable set in the caller's environment cannot leak into a case. Callers
# invoke this inside a command substitution, so the changes stay in a subshell.
run_entrypoint() {
unset BACKUP_DIRECTORY SCHEDULED_USERS BACKUP_INTERVAL_MS
for assignment in "$@"; do
case $assignment in
BACKUP_DIRECTORY=*)
BACKUP_DIRECTORY=${assignment#*=}
export BACKUP_DIRECTORY
;;
SCHEDULED_USERS=*)
SCHEDULED_USERS=${assignment#*=}
export SCHEDULED_USERS
;;
BACKUP_INTERVAL_MS=*)
BACKUP_INTERVAL_MS=${assignment#*=}
export BACKUP_INTERVAL_MS
;;
*)
echo "Not a variable docker-entrypoint.sh reads: $assignment" >&2
exit 1
;;
esac
done
PATH="$STUB_BIN:$PATH" sh "$ENTRYPOINT"
}

expect() {
description=$1
expected=$2
shift 2

actual=$(run_entrypoint "$@")

if [ "$actual" = "$expected" ]; then
echo "PASS: $description"
else
echo "FAIL: $description"
echo " expected: $expected"
echo " actual: $actual"
failures=$((failures + 1))
fi
}

expect "no variables set" \
"$PROFILE$JAR"

expect "BACKUP_DIRECTORY only" \
"$PROFILE[-Dbackup.directory=/data/backups]$JAR" \
"BACKUP_DIRECTORY=/data/backups"

expect "SCHEDULED_USERS only" \
"$PROFILE[-Dbackup.scheduled.users=octocat,github]$JAR" \
"SCHEDULED_USERS=octocat,github"

expect "BACKUP_INTERVAL_MS only" \
"$PROFILE[-Dbackup.scheduled.interval.ms=3600000]$JAR" \
"BACKUP_INTERVAL_MS=3600000"

expect "all variables set, in declaration order" \
"$PROFILE[-Dbackup.directory=/backups][-Dbackup.scheduled.users=octocat][-Dbackup.scheduled.interval.ms=60000]$JAR" \
"BACKUP_DIRECTORY=/backups" "SCHEDULED_USERS=octocat" "BACKUP_INTERVAL_MS=60000"

# The Dockerfile ships SCHEDULED_USERS and BACKUP_INTERVAL_MS as empty defaults,
# so an empty value must be treated as unset rather than passed through as an
# empty system property.
expect "empty variables add no arguments" \
"$PROFILE$JAR" \
"BACKUP_DIRECTORY=" "SCHEDULED_USERS=" "BACKUP_INTERVAL_MS="

expect "value containing a space stays a single argument" \
"$PROFILE[-Dbackup.directory=/data/my backups]$JAR" \
"BACKUP_DIRECTORY=/data/my backups"

if [ "$failures" -eq 0 ]; then
echo "All docker-entrypoint.sh tests passed."
exit 0
fi

echo "$failures docker-entrypoint.sh test(s) failed." >&2
exit 1
Loading