One role that syncs a payload and brings the stack up, configured entirely through stack_* variables, plus six playbooks that each demonstrate one part of that contract. Every playbook runs standalone and is tagged with its stack name, so site.yml --tags <stack> works. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
83 lines
3.2 KiB
YAML
83 lines
3.2 KiB
YAML
---
|
|
- name: Verify the caller named a stack
|
|
ansible.builtin.assert:
|
|
that:
|
|
- stack_name | length > 0
|
|
fail_msg: compose_stack requires stack_name
|
|
quiet: true
|
|
|
|
- name: Look for the payload on the control node
|
|
ansible.builtin.stat:
|
|
path: "{{ stack_src }}"
|
|
register: compose_stack_payload
|
|
delegate_to: localhost
|
|
become: false
|
|
changed_when: false
|
|
|
|
# A missing payload otherwise surfaces as an empty sync that reports success and
|
|
# leaves the host running whatever was there before.
|
|
- name: Verify the payload directory exists
|
|
ansible.builtin.assert:
|
|
that:
|
|
- compose_stack_payload.stat.isdir | default(false)
|
|
fail_msg: "no payload directory at {{ stack_src }}"
|
|
quiet: true
|
|
|
|
- name: Create the stack directory
|
|
ansible.builtin.file:
|
|
path: "{{ stack_dest }}"
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
- name: Create the extra directories the stack needs
|
|
ansible.builtin.file:
|
|
path: "{{ item.path }}"
|
|
state: directory
|
|
# All four default to omit: create the directory if it is absent, and leave
|
|
# whatever is already on the host alone. Bind mounts are routinely created
|
|
# root-owned by Docker on first start, and re-chmodding those breaks them.
|
|
mode: "{{ item.mode | default(omit) }}"
|
|
owner: "{{ item.owner | default(omit) }}"
|
|
group: "{{ item.group | default(omit) }}"
|
|
# For a bind mount whose existing tree is owned by the wrong uid. Set it with
|
|
# `owner`/`group` and no `mode`: recursing a mode over live data rewrites the
|
|
# permissions of everything the container has already written there.
|
|
recurse: "{{ item.recurse | default(omit) }}"
|
|
# Escalate only to set an owner or group. Creating these as root would defeat
|
|
# the point of pre-creating them at all — callers do it precisely so the bind
|
|
# mount is *not* root-owned when Docker first starts the stack. Unescalated
|
|
# they land as the deploy user, which is the user Compose runs as.
|
|
become: "{{ item.owner is defined or item.group is defined }}"
|
|
loop: "{{ stack_dirs }}"
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
|
|
# Deliberately the docker CLI rather than community.docker.docker_network: that
|
|
# module needs the Docker SDK for Python installed on every host, and creating a
|
|
# network is the only thing this repository would need it for.
|
|
- name: Create the external Docker networks
|
|
ansible.builtin.command:
|
|
cmd: docker network create {{ item }}
|
|
register: compose_stack_network
|
|
changed_when: compose_stack_network.rc == 0
|
|
failed_when:
|
|
- compose_stack_network.rc != 0
|
|
- '"already exists" not in compose_stack_network.stderr'
|
|
loop: "{{ stack_networks }}"
|
|
|
|
- name: Sync the payload to the host
|
|
ansible.posix.synchronize:
|
|
src: "{{ stack_src }}/"
|
|
dest: "{{ stack_dest }}/"
|
|
delete: "{{ stack_prune }}"
|
|
rsync_opts: "{{ stack_exclude | map('regex_replace', '^', '--exclude=') | list }}"
|
|
register: compose_stack_sync
|
|
|
|
# Bind-mounted configuration changes without the compose file changing, so
|
|
# docker_compose_v2 will not recreate the container and the new file has no
|
|
# effect. Playbooks key a reload task off this fact — see
|
|
# playbooks/reverse-proxy.yml.
|
|
- name: Record whether the payload changed
|
|
ansible.builtin.set_fact:
|
|
compose_stack_synced: "{{ compose_stack_sync.changed }}"
|