# ============================================================================= # Single pipeline – all event types handled via step-level when conditions: # # pull_request → syntax-check, validate # ONLY validates config correctness. # Does NOT compare to the live server – a PR branch is # expected to differ from the server (not yet deployed), # so a drift-check here would always be OUT_OF_SYNC by # design and is meaningless as a failure signal. # # push (master) → syntax-check, validate, deploy, update-sync-metric # Deploys to the server, then verifies sync and pushes metric. # # cron → gitops_sync_check (read-only drift check, no deploy) # Continuously verifies that the live server still matches # Git even when no push has happened. Detects manual edits # made directly on the server. # # NOTE: Woodpecker does not support multiple YAML documents (---) in one file. # All pipelines must live in a single document with step-level filtering. # ============================================================================= when: - event: pull_request - event: push branch: master - event: cron steps: # --------------------------------------------------------------------------- # syntax-check: Lint all playbooks – runs on pull_request and push # --------------------------------------------------------------------------- syntax-check: image: alpine/ansible:latest environment: ANSIBLE_CONFIG: ansible.cfg commands: - ansible-playbook -i ansible/inventory/hosts.yml --syntax-check ansible/playbooks/*.yml when: - event: pull_request - event: push branch: master # --------------------------------------------------------------------------- # validate: Validate server state – runs on pull_request and push # --------------------------------------------------------------------------- validate: image: alpine/ansible:latest depends_on: [syntax-check] environment: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY commands: - mkdir -p ~/.ssh - printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/validate.yml when: - event: pull_request - event: push branch: master # --------------------------------------------------------------------------- # deploy: Apply Git configuration to server – runs on push to master only # --------------------------------------------------------------------------- deploy: image: alpine/ansible:latest depends_on: [syntax-check, validate] environment: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY commands: - mkdir -p ~/.ssh - printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/apply.yml when: branch: master event: push # --------------------------------------------------------------------------- # update-sync-metric: Post-deploy sync check + Prometheus metric push # Runs on push to master only, after deploy succeeds. # STATUS=1 means SYNCED, STATUS=0 means OUT_OF_SYNC. # --------------------------------------------------------------------------- update-sync-metric: image: alpine/ansible:latest depends_on: [deploy] environment: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY PUSHGATEWAY_URL: http://pushgateway.observability-stack.svc.cluster.local:9091 commands: - | apk add --no-cache curl > /dev/null 2>&1 mkdir -p ~/.ssh printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa echo "==> Verifying post-deploy sync status..." set +e ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml DRIFT_RC=$? set -e if [ "$DRIFT_RC" -eq 0 ]; then STATUS=1 echo "==> SYNCED (1) – server configuration matches Git" else STATUS=0 echo "==> OUT OF SYNC (0) – drift detected after deploy" fi printf 'gitops_sync_status{repo="rsyslog",server="rsyslog-lab"} %s\n' "$STATUS" | \ curl --silent --show-error --fail --data-binary @- \ "$${PUSHGATEWAY_URL}/metrics/job/gitops_rsyslog/instance/rsyslog-lab" echo "==> Metric pushed. Pipeline always succeeds; sync status is in Prometheus." when: branch: master event: push # --------------------------------------------------------------------------- # gitops_sync_check: ArgoCD-style cron drift check – read-only, no deploy # Detects manual changes made directly on the server between pushes. # STATUS=1 → SYNCED, STATUS=0 → OUT_OF_SYNC # Pipeline marked FAILED when drift found so it is visible in the UI. # # ─── Woodpecker Cron UI settings ────────────────────────────────────────── # Name: gitops_sync_check # Branch: master # Schedule: */2 * * * * # --------------------------------------------------------------------------- gitops_sync_check: image: alpine/ansible:latest environment: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY PUSHGATEWAY_URL: http://pushgateway.observability-stack.svc.cluster.local:9091 commands: - | apk add --no-cache curl > /dev/null 2>&1 mkdir -p ~/.ssh printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa echo "==> [cron] Running drift check against remote server..." set +e ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml DRIFT_RC=$? set -e if [ "$DRIFT_RC" -eq 0 ]; then STATUS=1 echo "==> STATUS: SYNCED (1) – server configuration matches Git" else STATUS=0 echo "==> STATUS: OUT OF SYNC (0) – manual drift detected on server" fi echo "==> Pushing metric: gitops_sync_status{repo=\"rsyslog\",server=\"rsyslog-lab\"} $STATUS" printf 'gitops_sync_status{repo="rsyslog",server="rsyslog-lab"} %s\n' "$STATUS" | \ curl --silent --show-error --fail --data-binary @- \ "$${PUSHGATEWAY_URL}/metrics/job/gitops_rsyslog/instance/rsyslog-lab" echo "==> Metric pushed. Pipeline always succeeds; sync status is in Prometheus." when: event: cron