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.
87 lines
3.7 KiB
YAML
87 lines
3.7 KiB
YAML
# 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"
|