--- - name: Rollback Application Deployment hosts: production gather_facts: yes become: no vars: rollback_to_version: "{{ rollback_to_version | default('previous') }}" app_stack_path: "{{ deploy_user_home }}/deployment/stacks/application" pre_tasks: - name: Optionally load registry credentials from encrypted vault include_vars: file: "{{ playbook_dir }}/../secrets/production.vault.yml" no_log: yes ignore_errors: yes delegate_to: localhost become: no - name: Derive docker registry credentials from vault when not provided set_fact: docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(omit)) }}" docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(omit)) }}" - name: Check Docker service systemd: name: docker state: started register: docker_service become: yes - name: Fail if Docker is not running fail: msg: "Docker service is not running - cannot perform rollback" when: docker_service.status.ActiveState != 'active' - name: Get list of available backups find: paths: "{{ backups_path }}" file_type: directory register: available_backups - name: Fail if no backups available fail: msg: "No backup versions available for rollback" when: available_backups.matched == 0 - name: Sort backups by date (newest first) set_fact: sorted_backups: "{{ available_backups.files | sort(attribute='mtime', reverse=true) }}" tasks: - name: Determine rollback target set_fact: rollback_backup: "{{ sorted_backups[1] if rollback_to_version == 'previous' else sorted_backups | selectattr('path', 'search', rollback_to_version) | first }}" when: sorted_backups | length > 1 - name: Fail if rollback target not found fail: msg: "Cannot determine rollback target. Available backups: {{ sorted_backups | map(attribute='path') | list }}" when: rollback_backup is not defined - name: Load rollback metadata slurp: src: "{{ rollback_backup.path }}/deployment_metadata.txt" register: rollback_metadata ignore_errors: yes - name: Parse rollback image from metadata set_fact: rollback_image: "{{ rollback_metadata.content | b64decode | regex_search('Deployed Image: ([^\\n]+)', '\\1') | first }}" when: rollback_metadata is succeeded - name: Alternative: Parse rollback image from docker-compose config backup set_fact: rollback_image: "{{ rollback_metadata.content | b64decode | regex_search('image:\\s+([^:]+):([^\\n]+)', '\\1:\\2') | first }}" when: - rollback_metadata is succeeded - rollback_image is not defined - name: Fail if cannot determine rollback image fail: msg: "Cannot determine image to rollback to from backup: {{ rollback_backup.path }}" when: rollback_image is not defined or rollback_image == '' - name: Display rollback information debug: msg: - "Rolling back to previous version" - "Backup: {{ rollback_backup.path }}" - "Image: {{ rollback_image }}" - name: Login to Docker registry (if credentials provided) community.docker.docker_login: registry_url: "{{ docker_registry_url }}" username: "{{ docker_registry_username }}" password: "{{ docker_registry_password }}" no_log: yes when: - docker_registry_username is defined - docker_registry_password is defined - name: Pull rollback image community.docker.docker_image: name: "{{ rollback_image.split(':')[0] }}" tag: "{{ rollback_image.split(':')[1] }}" source: pull force_source: yes register: image_pull - name: Update docker-compose.yml with rollback image replace: path: "{{ app_stack_path }}/docker-compose.yml" regexp: '^(\s+image:\s+){{ app_image }}:.*$' replace: '\1{{ rollback_image }}' register: compose_updated - name: Restart application stack with rollback image community.docker.docker_compose_v2: project_src: "{{ app_stack_path }}" state: present pull: always recreate: always remove_orphans: yes register: stack_rollback when: compose_updated.changed - name: Wait for services to be healthy after rollback wait_for: timeout: 60 changed_when: false - name: Get current running image shell: | docker compose -f {{ app_stack_path }}/docker-compose.yml config | grep -E "^\s+image:" | head -1 | awk '{print $2}' || echo "unknown" args: executable: /bin/bash register: current_image changed_when: false - name: Record rollback event copy: content: | Rollback Timestamp: {{ ansible_date_time.iso8601 }} Rolled back from: {{ sorted_backups[0].path | basename }} Rolled back to: {{ rollback_backup.path | basename }} Rollback Image: {{ rollback_image }} Current Image: {{ current_image.stdout }} dest: "{{ backups_path }}/rollback_{{ ansible_date_time.epoch }}.txt" owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0644' post_tasks: - name: Display rollback summary debug: msg: - "✅ Rollback completed successfully!" - "Rolled back to: {{ rollback_image }}" - "From backup: {{ rollback_backup.path }}" - "Current running image: {{ current_image.stdout }}" - "Health check URL: {{ health_check_url }}" - name: Recommend health check debug: msg: "⚠️ Please verify application health at {{ health_check_url }}"