playbooks: generic per-service stack image update (pull + recreate + verify)
Adds playbooks/update-stack-image.yaml — pull the newest image for one
compose stack service and recreate it, with a verify phase that asserts
the container's image id equals what the tag now resolves to rather than
trusting a 'Up' line from docker ps.
Scoped to a single service on purpose: the recreate is 'up -d <service>',
never a bare 'up -d', which would recreate every service in the project.
Go template format strings are written bare; elway's {{ identifier }}
substitution leaves them alone, but {{end}} / {{else}} would match and
die as undefined variables, so the health read uses {{json .State.Health}}
instead of an if/else.
First use: drawio on esh-docker-vm, 28.1.2 -> 31.4.6.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Pull the newest image for one compose stack and recreate it.
|
||||
#
|
||||
# The generic "update a stack that tracks a floating tag" playbook. Works for
|
||||
# any stack laid out per the house convention
|
||||
# (`/opt/docker/compose/<stack>/compose.yaml`).
|
||||
#
|
||||
# Usage:
|
||||
# scripts/elway esh-docker-vm --playbook playbooks/update-stack-image.yaml \
|
||||
# --var stack=drawio
|
||||
#
|
||||
# `service` and `container` default to `stack`. Override them when the
|
||||
# compose service name or `container_name:` differs from the directory name.
|
||||
#
|
||||
# ⚠ SCOPED TO ONE SERVICE ON PURPOSE. The recreate is `up -d <service>`, never
|
||||
# a bare `up -d` — a bare one recreates every service in the project
|
||||
# (auto-memory `feedback_compose_up_recreates_whole_stack`). A stack that
|
||||
# really needs all its services bounced gets one run per service.
|
||||
#
|
||||
# ⚠ For stacks tracking a FLOATING tag (`:latest` and friends). A SHA-pinned
|
||||
# stack has nothing to pull; updating that means moving the pin, which is a
|
||||
# deliberate edit and not this.
|
||||
#
|
||||
# ⚠ Go template format strings are written bare (`{{.Image}}`). elway's
|
||||
# substitution only matches `{{ identifier }}`, so those pass through
|
||||
# untouched — but `{{end}}` / `{{else}}` DO match and would die as undefined
|
||||
# variables. Hence `{{json .State.Health}}` below instead of an if/else.
|
||||
|
||||
vars:
|
||||
compose_root: /opt/docker/compose
|
||||
stack: ""
|
||||
service: ""
|
||||
container: ""
|
||||
|
||||
steps:
|
||||
- name: Refuse to run without a stack name
|
||||
shell: test -n "{{ stack }}"
|
||||
changed_when: "false"
|
||||
|
||||
- name: Record the image the service runs BEFORE the pull
|
||||
# Written to a file, not just echoed, so the verify phase can compare
|
||||
# against it — an "updated" claim nobody can falsify is not a result.
|
||||
shell: |
|
||||
ctr="{{ container }}"; [ -n "$ctr" ] || ctr="{{ service }}"; [ -n "$ctr" ] || ctr="{{ stack }}"
|
||||
docker inspect "$ctr" --format '{{.Image}}' | tee /tmp/elway-update-{{ stack }}.before
|
||||
changed_when: "false"
|
||||
|
||||
- name: Pull the newest image for the service
|
||||
shell: |
|
||||
svc="{{ service }}"; [ -n "$svc" ] || svc="{{ stack }}"
|
||||
cd {{ compose_root }}/{{ stack }}
|
||||
docker compose pull "$svc"
|
||||
|
||||
- name: Recreate the service on the pulled image
|
||||
shell: |
|
||||
svc="{{ service }}"; [ -n "$svc" ] || svc="{{ stack }}"
|
||||
cd {{ compose_root }}/{{ stack }}
|
||||
docker compose up -d "$svc"
|
||||
|
||||
verify:
|
||||
- name: Service runs the image its tag now resolves to
|
||||
# `docker ps` saying "Up" only proves something is running. This asserts
|
||||
# the container's image id equals what the tag resolves to right now, and
|
||||
# reports whether that is actually a move off the pre-pull image.
|
||||
shell: |
|
||||
svc="{{ service }}"; [ -n "$svc" ] || svc="{{ stack }}"
|
||||
ctr="{{ container }}"; [ -n "$ctr" ] || ctr="$svc"
|
||||
cd {{ compose_root }}/{{ stack }}
|
||||
tag=$(docker compose config --images "$svc" | head -1)
|
||||
want=$(docker image inspect "$tag" --format '{{.Id}}')
|
||||
have=$(docker inspect "$ctr" --format '{{.Image}}')
|
||||
test "$want" = "$have" || { echo "MISMATCH: $ctr runs $have, $tag is $want"; exit 1; }
|
||||
before=$(cat /tmp/elway-update-{{ stack }}.before 2>/dev/null)
|
||||
if [ "$before" = "$have" ]; then
|
||||
echo "already current: $tag == $have (nothing newer upstream)"
|
||||
else
|
||||
echo "updated: $before -> $have ($tag)"
|
||||
fi
|
||||
changed_when: "false"
|
||||
|
||||
- name: Container is up, and healthy if it declares a healthcheck
|
||||
shell: |
|
||||
ctr="{{ container }}"; [ -n "$ctr" ] || ctr="{{ service }}"; [ -n "$ctr" ] || ctr="{{ stack }}"
|
||||
docker inspect "$ctr" --format 'status={{.State.Status}} started={{.State.StartedAt}}'
|
||||
echo "health=$(docker inspect "$ctr" --format '{{json .State.Health}}' | head -c 120)"
|
||||
test "$(docker inspect "$ctr" --format '{{.State.Status}}')" = running
|
||||
changed_when: "false"
|
||||
Reference in New Issue
Block a user