diff --git a/charts/outprint/Chart.yaml b/charts/outprint/Chart.yaml index 66dfea3..f746ea8 100644 --- a/charts/outprint/Chart.yaml +++ b/charts/outprint/Chart.yaml @@ -15,7 +15,7 @@ sources: - https://github.com/taskmedia/helm icon: https://media.task.media/images/logo.png type: application -version: 1.3.1 +version: 1.3.2 # outprint release tag without the leading v appVersion: "1.1.0" @@ -32,5 +32,5 @@ annotations: - name: outprint-injector image: ghcr.io/taskmedia/outprint-injector:1.1.0 artifacthub.io/changes: | - - kind: changed - description: Bump Outprint to 1.1.0 + - kind: fixed + description: Only one package sync works on the volume at a time; the first Job and the CronJob could overlap diff --git a/charts/outprint/files/packages-activate.sh b/charts/outprint/files/packages-activate.sh index 38b0b2f..fd44706 100644 --- a/charts/outprint/files/packages-activate.sh +++ b/charts/outprint/files/packages-activate.sh @@ -3,15 +3,15 @@ set -eu PK=/packages -[ -f "$PK/.pending" ] || { echo "no change"; exit 0; } -sha="$(cat "$PK/.pending")" +[ -f /state/pending ] || { echo "no change"; exit 0; } +sha="$(cat /state/pending)" had_revision=false [ -f "$PK/.revision" ] && had_revision=true ln -sfn "rev-$sha" "$PK/.current.tmp" mv -T "$PK/.current.tmp" "$PK/current" printf '%s\n' "$sha" >"$PK/.revision" -rm -f "$PK/.pending" +rm -rf "$PK/.lock" echo "activated $sha" # newest first; the active revision is the newest diff --git a/charts/outprint/files/packages-sync.sh b/charts/outprint/files/packages-sync.sh index 88dd8dc..43d757f 100644 --- a/charts/outprint/files/packages-sync.sh +++ b/charts/outprint/files/packages-sync.sh @@ -1,14 +1,34 @@ # Resolve the ref of every source in /scripts/sources.json to a commit. If the # set of commits differs from the active revision, check every source out (Git # LFS included), merge the packages into /packages/rev- and leave the id in -# /packages/.pending for the validate and activate steps. Never touches the -# active revision. Any failure (a ref that does not resolve, a pinned commit +# /state/pending (private to this pod) for the validate and activate steps. +# Never touches the active revision. Only one sync works on the volume at a +# time: /packages/.lock is taken here and released by the activate step. Any failure (a ref that does not resolve, a pinned commit # that differs, a package name that two sources provide) stops here. set -eu PK=/packages +STATE=/state SOURCES=/scripts/sources.json -rm -f "$PK/.pending" +LOCK="$PK/.lock" +LOCK_MAX_AGE=180 +rm -f "$STATE/pending" + +# mkdir is atomic. A lock older than LOCK_MAX_AGE seconds is left over from a +# sync that died or failed validation, and is taken over. +if ! mkdir "$LOCK" 2>/dev/null; then + age=$(( $(date +%s) - $(cat "$LOCK/since" 2>/dev/null || echo 0) )) + if [ "$age" -lt "$LOCK_MAX_AGE" ]; then + echo "another sync holds the lock (started ${age}s ago): skipping" + exit 0 + fi + echo "taking over a stale lock (${age}s old)" + rm -rf "$LOCK" + mkdir "$LOCK" +fi +date +%s >"$LOCK/since" +# Without a pending revision nothing follows this step, so give the lock back. +trap '[ -f "$STATE/pending" ] || rm -rf "$LOCK"' EXIT apk add --no-cache git-lfs openssh-client >/dev/null @@ -130,5 +150,5 @@ done printf '%s' "$resolved" >"$rev/.sources" rm -rf "$PK/.work" chmod -R a+rX "$rev" -printf '%s\n' "$id" >"$PK/.pending" -chmod a+r "$PK/.pending" +printf '%s\n' "$id" >"$STATE/pending" +chmod a+r "$STATE/pending" diff --git a/charts/outprint/files/packages-validate.sh b/charts/outprint/files/packages-validate.sh index a946fa1..73a87c7 100644 --- a/charts/outprint/files/packages-validate.sh +++ b/charts/outprint/files/packages-validate.sh @@ -2,8 +2,8 @@ # With a service configuration the roots come from the environment. set -eu -[ -f /packages/.pending ] || { echo "nothing to validate"; exit 0; } -sha="$(cat /packages/.pending)" +[ -f /state/pending ] || { echo "nothing to validate"; exit 0; } +sha="$(cat /state/pending)" export OUTPRINT_TEMPLATES_ROOT="/packages/rev-$sha/templates" export OUTPRINT_CONFIGURATIONS_ROOT="/packages/rev-$sha/configurations" exec outprint validate --service-config /etc/outprint/service.yaml diff --git a/charts/outprint/templates/_packages-sync.tpl b/charts/outprint/templates/_packages-sync.tpl index 446b284..e48e8b0 100644 --- a/charts/outprint/templates/_packages-sync.tpl +++ b/charts/outprint/templates/_packages-sync.tpl @@ -27,6 +27,7 @@ initContainers: volumeMounts: - { name: packages, mountPath: /packages } - { name: scripts, mountPath: /scripts, readOnly: true } + - { name: state, mountPath: /state } {{- range $sources }} {{- if .sshKeySecret }} - { name: {{ printf "ssh-%s" .name }}, mountPath: {{ printf "/ssh/%s" .name | quote }}, readOnly: true } @@ -47,6 +48,7 @@ initContainers: volumeMounts: - { name: packages, mountPath: /packages, readOnly: true } - { name: scripts, mountPath: /scripts, readOnly: true } + - { name: state, mountPath: /state, readOnly: true } - { name: service-config, mountPath: /etc/outprint, readOnly: true } - { name: tmp, mountPath: /tmp } containers: @@ -72,6 +74,7 @@ containers: volumeMounts: - { name: packages, mountPath: /packages } - { name: scripts, mountPath: /scripts, readOnly: true } + - { name: state, mountPath: /state, readOnly: true } - { name: tmp, mountPath: /tmp } volumes: - name: packages @@ -85,6 +88,9 @@ volumes: name: {{ include "outprint.fullname" . }}-service - name: tmp emptyDir: { sizeLimit: 64Mi } + # What this pod has prepared: the id of the revision for validate and activate. + - name: state + emptyDir: { sizeLimit: 1Mi } {{- range $sources }} {{- if .sshKeySecret }} - name: {{ printf "ssh-%s" .name }}