iac/ansible/roles/docker_service/tasks/main.yaml
Sergei Poljanski 2f50229428 docker_service: let the dry run of a new service pass
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
2026-11-12 20:55:34 +04:00

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 != ""