# ============================================================================= # 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-gitops-status # Deploys to the server, then verifies sync and sends # JSON status snapshot to gitops-status-server for Grafana. # # 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. Sends JSON status with # detailed file-level drift information to gitops-status-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-gitops-status: Post-deploy sync check + JSON status update # Runs on push to master only, after deploy succeeds. # Generates structured JSON with sync status, drift count, and changed files. # Sends JSON to gitops-status-server for Grafana visualization. # --------------------------------------------------------------------------- update-gitops-status: image: alpine/ansible:latest depends_on: [deploy] environment: ANSIBLE_CONFIG: ansible.cfg SSH_PRIVATE_KEY: from_secret: SSH_PRIVATE_KEY 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 ANSIBLE_HOST_KEY_CHECKING: "False" ANSIBLE_FORCE_COLOR: "False" ANSIBLE_RETRY_FILES_ENABLED: "False" ANSIBLE_UNSAFE_WRITES: "True" ANSIBLE_FORKS: "1" commands: - | # Increase file descriptor limit for Ansible (max safe value) ulimit -n 65536 # Install dependencies: curl for HTTP requests, jq for JSON formatting apk add --no-cache curl jq > /dev/null 2>&1 # Setup SSH key for Ansible mkdir -p ~/.ssh printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa echo "==> Running post-deploy GitOps status check..." # Make script executable and run it chmod +x update-gitops-status.sh ./update-gitops-status.sh echo "==> JSON status update complete. Pipeline always succeeds." 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. # Generates structured JSON with sync status, drift count, and changed files. # Sends JSON to gitops-status-server for continuous GitOps monitoring. # 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 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 ANSIBLE_HOST_KEY_CHECKING: "False" ANSIBLE_FORCE_COLOR: "False" ANSIBLE_RETRY_FILES_ENABLED: "False" ANSIBLE_UNSAFE_WRITES: "True" ANSIBLE_FORKS: "1" commands: - | # Increase file descriptor limit for Ansible (max safe value) ulimit -n 65536 # Install dependencies: curl for HTTP requests, jq for JSON formatting apk add --no-cache curl jq bash > /dev/null 2>&1 # Setup SSH key for Ansible mkdir -p ~/.ssh printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa echo "==> [cron] Running continuous GitOps drift check..." # Make script executable and run it chmod +x update-gitops-status.sh # Capture exit code to determine if drift was detected set +e ./update-gitops-status.sh SCRIPT_RC=$? set -e if [ "$SCRIPT_RC" -ne 0 ]; then echo "==> ERROR: Status update failed" exit 1 fi # Check sync status to determine pipeline result # Read the generated JSON or re-run drift check echo "==> Verifying drift status for pipeline result..." set +e ANSIBLE_FORCE_COLOR=false \ ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml > /dev/null 2>&1 DRIFT_RC=$? set -e if [ "$DRIFT_RC" -eq 0 ]; then echo "==> Pipeline SUCCESS: Server is SYNCED with Git" exit 0 else echo "==> Pipeline FAILED: OUT OF SYNC - manual drift detected on server" echo " Check gitops-status-server for detailed file-level drift information" exit 1 fi when: event: cron