In check mode nothing is written, so for a service that is not on the host yet the restart task finds no project directory and fails the whole dry run. Ignore errors there in check mode, as the compose task already does. Assisted-by: Claude:opus-5-5
143 lines
4.8 KiB
YAML
143 lines
4.8 KiB
YAML
---
|
|
- name: Validate required variables
|
|
ansible.builtin.assert:
|
|
that:
|
|
- service_name | length > 0
|
|
- service_base_path | length > 0
|
|
fail_msg: "service_name and service_base_path are required"
|
|
quiet: true
|
|
|
|
- name: Create base service directory
|
|
ansible.builtin.file:
|
|
path: "{{ service_base_path }}"
|
|
state: directory
|
|
|
|
- name: Create service subdirectories
|
|
ansible.builtin.file:
|
|
path: "{{ service_base_path }}/{{ item.path }}"
|
|
state: directory
|
|
owner: "{{ item.owner | default(omit) }}"
|
|
group: "{{ item.group | default(omit) }}"
|
|
mode: "{{ item.mode | default(omit) }}"
|
|
loop: "{{ service_directories }}"
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
|
|
- name: Deploy service templates
|
|
ansible.builtin.template:
|
|
src: "{{ item.src }}"
|
|
dest: "{{ service_base_path }}/{{ item.dest }}"
|
|
owner: "{{ item.owner | default('root') }}"
|
|
group: "{{ item.group | default('root') }}"
|
|
mode: "{{ item.mode | default('0644') }}"
|
|
loop: "{{ service_templates }}"
|
|
loop_control:
|
|
label: "{{ item.dest }}"
|
|
# Templates may carry secrets; never print their contents
|
|
diff: false
|
|
register: template_result
|
|
|
|
- name: Deploy service files
|
|
ansible.builtin.copy:
|
|
src: "{{ item.src }}"
|
|
dest: "{{ service_base_path }}/{{ item.dest }}"
|
|
owner: "{{ item.owner | default('root') }}"
|
|
group: "{{ item.group | default('root') }}"
|
|
mode: "{{ item.mode | default('0644') }}"
|
|
loop: "{{ service_files }}"
|
|
loop_control:
|
|
label: "{{ item.dest }}"
|
|
register: file_result
|
|
|
|
- name: Remove files that are no longer part of the service
|
|
ansible.builtin.file:
|
|
path: "{{ service_base_path }}/{{ item }}"
|
|
state: absent
|
|
loop: "{{ service_absent }}"
|
|
|
|
# Game servers need time to save: Docker's default stop timeout is 10 s.
|
|
# When compose is about to recreate one of the listed containers, save
|
|
# and stop it properly first; compose then replaces the stopped container.
|
|
- name: Ask compose what it would do
|
|
ansible.builtin.command:
|
|
cmd: docker compose -p {{ docker_compose_project_name }} -f {{ docker_compose_file }} up -d --dry-run
|
|
chdir: "{{ service_base_path }}"
|
|
register: compose_plan
|
|
changed_when: false
|
|
# The dry run exits non-zero when an image still has to be pulled; the
|
|
# plan it prints up to that point is all that is needed here
|
|
failed_when: false
|
|
check_mode: false
|
|
when: service_graceful_stop | length > 0
|
|
|
|
- name: Run pre-stop command before recreation
|
|
ansible.builtin.command:
|
|
cmd: "{{ item.pre_stop }}"
|
|
loop: "{{ service_graceful_stop }}"
|
|
loop_control:
|
|
label: "{{ item.container }}"
|
|
when:
|
|
- item.pre_stop is defined
|
|
- (compose_plan.stdout ~ compose_plan.stderr) is search('Container ' ~ item.container ~ ' Recreate')
|
|
failed_when: false
|
|
|
|
- name: Stop gracefully before recreation
|
|
ansible.builtin.command:
|
|
cmd: "docker stop -t {{ item.timeout | default(120) }} {{ item.container }}"
|
|
loop: "{{ service_graceful_stop }}"
|
|
loop_control:
|
|
label: "{{ item.container }}"
|
|
when: (compose_plan.stdout ~ compose_plan.stderr) is search('Container ' ~ item.container ~ ' Recreate')
|
|
|
|
- name: Start service with Docker Compose
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ service_base_path }}"
|
|
project_name: "{{ docker_compose_project_name }}"
|
|
files:
|
|
- "{{ docker_compose_file }}"
|
|
state: present
|
|
register: compose_result
|
|
# In check mode compose cannot pull, so a missing image looks like an error
|
|
ignore_errors: "{{ ansible_check_mode }}"
|
|
|
|
- name: Restart service after a config change
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ service_base_path }}"
|
|
project_name: "{{ docker_compose_project_name }}"
|
|
files:
|
|
- "{{ docker_compose_file }}"
|
|
state: restarted
|
|
when:
|
|
- service_restart_on_change
|
|
- template_result is changed or file_result is changed
|
|
- compose_result is not changed
|
|
# A first dry run has no project directory to restart yet
|
|
ignore_errors: "{{ ansible_check_mode }}"
|
|
|
|
- name: Wait before health check
|
|
ansible.builtin.pause:
|
|
seconds: "{{ health_check_delay }}"
|
|
when:
|
|
- health_check_enabled
|
|
- health_check_url != "" or health_check_port != ""
|
|
- compose_result is changed or template_result is changed or file_result is changed
|
|
|
|
- name: Wait for service HTTP endpoint
|
|
ansible.builtin.uri:
|
|
url: "{{ health_check_url }}"
|
|
status_code: [200, 301, 302, 303, 401, 404]
|
|
follow_redirects: none
|
|
timeout: 5
|
|
register: health_result
|
|
until: health_result is not failed
|
|
retries: "{{ health_check_retries }}"
|
|
delay: "{{ health_check_retry_delay }}"
|
|
when: health_check_enabled and health_check_url != ""
|
|
check_mode: false
|
|
|
|
- name: Wait for service port
|
|
ansible.builtin.wait_for:
|
|
host: "{{ health_check_host }}"
|
|
port: "{{ health_check_port }}"
|
|
timeout: "{{ health_check_retries * health_check_retry_delay }}"
|
|
when: health_check_enabled and health_check_port != ""
|