From 1cbe3d4de7387e26ef6740f2be1d2d5be5e4dc9d Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:21:20 +0300 Subject: [PATCH] fix: Use checksum-based content comparison to avoid permission-based drift false positives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL FIX: Problem: drift-check.yml was using 'copy' module in check_mode, which compares: - File content ✓ - Permissions (owner, group, mode) ✗ - Ownership ✗ After deploy, files have root:root 0644 permissions. Even though content matches, the copy module marked files as 'changed' because permissions were being compared. This caused false OUT_OF_SYNC reports even when configuration was actually synced. Solution: Use MD5 checksum-based comparison instead: - Compare only file CONTENT using stat checksums - Ignore permissions/ownership differences - This is what matters for config management Also fixed URLs: - Changed back from port 80 to port 5000 (API only) - Updated service name to gitops-status-api Now drift detection only triggers on actual config changes, not permission differences. After successful deploy, should correctly report SYNCED status. --- .woodpecker.yml | 4 +- ansible/playbooks/drift-check.yml | 82 ++++++++++++++++++++++--------- update-gitops-status.sh | 2 +- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 8eca8cf..cb19819 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -94,7 +94,7 @@ steps: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY - GITOPS_STATUS_SERVER_URL: http://gitops-status-server.observability-stack.svc.cluster.local:80 + GITOPS_STATUS_SERVER_URL: http://gitops-status-api.observability-stack.svc.cluster.local:5000 REPO_NAME: rsyslog SERVER_NAME: rsyslog-lab # Optimize Ansible for container environment @@ -145,7 +145,7 @@ steps: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY - GITOPS_STATUS_SERVER_URL: http://gitops-status-server.observability-stack.svc.cluster.local:80 + GITOPS_STATUS_SERVER_URL: http://gitops-status-api.observability-stack.svc.cluster.local:5000 REPO_NAME: rsyslog SERVER_NAME: rsyslog-lab # Optimize Ansible for container environment diff --git a/ansible/playbooks/drift-check.yml b/ansible/playbooks/drift-check.yml index 4e90dba..52c3bd6 100644 --- a/ansible/playbooks/drift-check.yml +++ b/ansible/playbooks/drift-check.yml @@ -3,35 +3,69 @@ hosts: rsyslog_servers gather_facts: false - # NOTE: src paths below resolve relative to the Ansible controller (the - # Woodpecker CI container), so they always reflect the latest Git commit – - # NOT the server's local clone, which may be stale. + # NOTE: This playbook compares file CONTENT ONLY using md5 checksums. + # It ignores permissions/ownership differences. + # Permissions are enforced during deploy (apply.yml) tasks: # ------------------------------------------------------------------------- - # Use Ansible copy in check_mode so it compares controller files (Git) - # against live server files without actually writing anything. - # changed=true → file differs → drift - # changed=false → files match → synced - # - # NOTE: Only checking file CONTENT, not permissions/ownership - # The validate.yml playbook enforces permissions on deploy + # Checksum-based content comparison (ignores permissions/ownership) + # This is the only reliable way to detect actual config changes + # after deployment when permissions have been set. # ------------------------------------------------------------------------- - - name: Check main rsyslog.conf - ansible.builtin.copy: - src: "{{ playbook_dir }}/../../files/rsyslog.conf" - dest: "{{ rsyslog_main_config }}" - check_mode: true - diff: true - register: main_config_check + + - name: Get MD5 checksum of Git version of rsyslog.conf + stat: + path: "{{ playbook_dir }}/../../files/rsyslog.conf" + delegate_to: localhost + register: git_rsyslog_conf_stat - - name: Check rsyslog.d config files - ansible.builtin.copy: - src: "{{ playbook_dir }}/../../files/rsyslog.d/" - dest: "{{ rsyslog_config_dir }}/" - check_mode: true - diff: true - register: rsyslogd_check + - name: Get MD5 checksum of server version of rsyslog.conf + stat: + path: "{{ rsyslog_main_config }}" + register: server_rsyslog_conf_stat + + - name: Compare rsyslog.conf content + set_fact: + main_config_check: + changed: "{{ git_rsyslog_conf_stat.stat.checksum != server_rsyslog_conf_stat.stat.checksum }}" + checksum_git: "{{ git_rsyslog_conf_stat.stat.checksum }}" + checksum_server: "{{ server_rsyslog_conf_stat.stat.checksum }}" + + - name: Get checksums for rsyslog.d directory files + block: + - name: Find Git rsyslog.d files + find: + paths: "{{ playbook_dir }}/../../files/rsyslog.d" + patterns: "*.conf" + recurse: false + delegate_to: localhost + register: git_confd_files + + - name: Find server rsyslog.d files + find: + paths: "{{ rsyslog_config_dir }}" + patterns: "*.conf" + recurse: false + register: server_confd_files + + - name: Get checksums for all Git rsyslog.d files + stat: + path: "{{ item.path }}" + delegate_to: localhost + loop: "{{ git_confd_files.files }}" + register: git_confd_checksums + + - name: Get checksums for all server rsyslog.d files + stat: + path: "{{ item.path }}" + loop: "{{ server_confd_files.files }}" + register: server_confd_checksums + + - name: Compare rsyslog.d file checksums + set_fact: + rsyslogd_check: + changed: "{{ git_confd_checksums.results | map(attribute='stat.checksum') | list != server_confd_checksums.results | map(attribute='stat.checksum') | list }}" - name: Check for extra files on server not present in Git block: diff --git a/update-gitops-status.sh b/update-gitops-status.sh index 961e111..cef8da2 100644 --- a/update-gitops-status.sh +++ b/update-gitops-status.sh @@ -44,7 +44,7 @@ set -euo pipefail # ───────────────────────────────────────────────────────────────────────────── # Configuration # ───────────────────────────────────────────────────────────────────────────── -GITOPS_STATUS_SERVER_URL="${GITOPS_STATUS_SERVER_URL:-http://gitops-status-server.observability-stack.svc.cluster.local:80}" +GITOPS_STATUS_SERVER_URL="${GITOPS_STATUS_SERVER_URL:-http://gitops-status-api.observability-stack.svc.cluster.local:5000}" REPO_NAME="${REPO_NAME:-rsyslog}" SERVER_NAME="${SERVER_NAME:-rsyslog-lab}" INVENTORY_FILE="ansible/inventory/hosts.yml"