--- - 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 }}"