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