--- # ============================================================================= # DRIFT-CHECK PLAYBOOK # Purpose: Check file drift and output detailed file-level JSON report # Usage: ansible-playbook drift-check.yml # Output: Structured JSON with sync status and drifted files info # Registers: drift_detected, drifted_files_json for consumption by update script # ============================================================================= - name: Check file drift hosts: all gather_facts: false vars_files: - ../deploy-config.yml tasks: # ───────────────────────────────────────────────────────────────────── # TASK 1: Initialize drift tracking variables # ───────────────────────────────────────────────────────────────────── - name: Initialize drift tracking set_fact: drift_detected: false drifted_items: [] drift_items_result: [] synced_count: 0 # ───────────────────────────────────────────────────────────────────── # TASK 2: Read local files from repo # ───────────────────────────────────────────────────────────────────── - name: Read local files from repository slurp: src: "{{ playbook_dir }}/{{ '../../' + item.src }}" delegate_to: localhost register: local_files loop: "{{ deploy_items }}" loop_control: loop_var: item label: "{{ item.name }}" failed_when: false # ───────────────────────────────────────────────────────────────────── # TASK 3: Read files from server # ───────────────────────────────────────────────────────────────────── - name: Read files from servers slurp: src: "{{ item.dest }}" register: server_files loop: "{{ deploy_items }}" loop_control: loop_var: item label: "{{ item.name }}" failed_when: false # ───────────────────────────────────────────────────────────────────── # TASK 4: Detect drift for each file # Compares local (repo) vs server file, detects missing or content diff # ───────────────────────────────────────────────────────────────────── - name: Detect drift for each file set_fact: drift_items_result: "{{ drift_items_result | default([]) + [file_drift_info] }}" vars: local_file: "{{ local_files.results[index] }}" server_file: "{{ server_files.results[index] }}" index: "{{ loop_index0 }}" file_drift_info: | {%- if server_file.rc != 0 -%} { "name": "{{ item.name }}", "destination": "{{ item.dest }}", "status": "MISSING", "reason": "File not found on server" } {%- elif local_file.content | b64decode != server_file.content | b64decode -%} { "name": "{{ item.name }}", "destination": "{{ item.dest }}", "status": "CONTENT_DIFFERS", "reason": "File content differs from repository" } {%- else -%} {} {%- endif -%} loop: "{{ deploy_items }}" loop_control: loop_var: item label: "{{ item.name }}" # ───────────────────────────────────────────────────────────────────── # TASK 5: Filter drifted files # ───────────────────────────────────────────────────────────────────── - name: Extract drifted files set_fact: drifted_items: "{{ drift_items_result | map('from_json') | selectattr('status', 'defined') | list }}" synced_count: "{{ drift_items_result | map('from_json') | rejectattr('status', 'defined') | list | length }}" # ───────────────────────────────────────────────────────────────────── # TASK 6: Update drift detection flag # ───────────────────────────────────────────────────────────────────── - name: Set drift_detected flag set_fact: drift_detected: "{{ drifted_items | length > 0 }}" # ───────────────────────────────────────────────────────────────────── # TASK 7: Generate JSON report with drift details # ───────────────────────────────────────────────────────────────────── - name: Generate drift detection JSON report set_fact: drifted_files_json: "{{ drifted_items | to_nice_json }}" # ───────────────────────────────────────────────────────────────────── # TASK 8: Save drift report to file for script consumption # ───────────────────────────────────────────────────────────────────── - name: Save drift report to file copy: content: "{{ drifted_files_json }}" dest: "/tmp/drifted_files_{{ inventory_hostname }}.json" owner: root group: root mode: "0644" delegate_to: localhost # ───────────────────────────────────────────────────────────────────── # TASK 9: Output status summary # ───────────────────────────────────────────────────────────────────── - name: Output SYNCED status debug: msg: | ✓ All files are in sync Synced files: {{ synced_count }} when: not drift_detected - name: Output OUT_OF_SYNC status with details debug: msg: | ✗ Configuration drift detected! Drifted files: {{ drifted_items | length }} Details: {{ drifted_files_json }} when: drift_detected # ───────────────────────────────────────────────────────────────────── # TASK 10: Fail if drift detected (for CI/CD pipeline) # ───────────────────────────────────────────────────────────────────── - name: Fail if drift detected fail: msg: "Configuration drift detected. {{ drifted_items | length }} file(s) out of sync." when: drift_detected