diff --git a/.woodpecker.yml b/.woodpecker.yml index 01a4d3b..591a625 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,4 +1,17 @@ +# ============================================================================= +# Pipeline 1: CI/CD +# Triggered by: pull_request (validate + drift-check) +# push to master (validate + deploy + post-deploy sync metric) +# ============================================================================= +when: + - event: pull_request + - event: push + branch: master + steps: + # --------------------------------------------------------------------------- + # syntax-check: Lint all playbooks – runs on pull_request and push + # --------------------------------------------------------------------------- syntax-check: image: alpine/ansible:latest environment: @@ -6,6 +19,9 @@ steps: commands: - ansible-playbook -i ansible/inventory/hosts.yml --syntax-check ansible/playbooks/*.yml + # --------------------------------------------------------------------------- + # validate: Validate server state – runs on pull_request and push + # --------------------------------------------------------------------------- validate: image: alpine/ansible:latest depends_on: [syntax-check] @@ -19,6 +35,9 @@ steps: - chmod 600 ~/.ssh/id_rsa - ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/validate.yml + # --------------------------------------------------------------------------- + # drift-check: Compare server config to Git – runs on pull_request only + # --------------------------------------------------------------------------- drift-check: image: alpine/ansible:latest depends_on: [syntax-check] @@ -34,6 +53,9 @@ steps: when: event: pull_request + # --------------------------------------------------------------------------- + # deploy: Apply Git configuration to server – runs on push to master only + # --------------------------------------------------------------------------- deploy: image: alpine/ansible:latest depends_on: [syntax-check, validate] @@ -49,3 +71,119 @@ steps: when: branch: master event: push + + # --------------------------------------------------------------------------- + # update-sync-metric: Verify post-deploy sync and report to Prometheus + # Runs drift-check after deploy; pushes gitops_sync_status metric. + # STATUS=1 means SYNCED, STATUS=0 means OUT_OF_SYNC. + # Runs on push to master only, after deploy succeeds. + # --------------------------------------------------------------------------- + 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: + from_secret: PUSHGATEWAY_URL + 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" + + exit $DRIFT_RC + when: + branch: master + event: push + +--- +# ============================================================================= +# Pipeline 2: GitOps Sync Check (ArgoCD-style continuous drift detection) +# Triggered by: cron – runs every 2 minutes even without a new push +# +# Purpose: Detect manual configuration changes made directly on the server +# (outside Git). If someone edits /etc/rsyslog.conf by hand, the next cron +# run will catch it and mark the system OUT_OF_SYNC in Prometheus. +# +# This pipeline does NOT deploy anything – it is a read-only drift check. +# +# ─── Woodpecker Cron Configuration (set in the Woodpecker UI) ─────────────── +# Name: gitops-drift-check +# Branch: master +# Schedule: */2 * * * * +# ───────────────────────────────────────────────────────────────────────────── +# +# Required secrets (Woodpecker → Repository Settings → Secrets): +# SSH_PRIVATE_KEY – SSH key to reach the managed server +# PUSHGATEWAY_URL – Base URL of Prometheus Pushgateway +# e.g. http://pushgateway:9091 +# ============================================================================= +when: + event: cron + +steps: + # --------------------------------------------------------------------------- + # gitops_sync_check: Read-only drift check + Prometheus metric push + # STATUS=1 → SYNCED (server matches Git) + # STATUS=0 → OUT_OF_SYNC (manual drift detected on server) + # Pipeline is marked FAILED when out of sync so it is visible in the UI. + # --------------------------------------------------------------------------- + gitops_sync_check: + image: alpine/ansible:latest + environment: + ANSIBLE_CONFIG: ansible.cfg + SSH_PRIVATE_KEY: + from_secret: SSH_PRIVATE_KEY + PUSHGATEWAY_URL: + from_secret: PUSHGATEWAY_URL + commands: + - | + apk add --no-cache curl > /dev/null 2>&1 + + # Prepare SSH key for remote server access + 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" + + # Exit non-zero when out of sync so Woodpecker marks the pipeline FAILED + exit $DRIFT_RC