# Integration Examples This document provides integration examples for common CI/CD tools and automation frameworks. ## Table of Contents - [Ansible Integration](#ansible-integration) - [Woodpecker CI Integration](#woodpecker-ci-integration) - [GitLab CI Integration](#gitlab-ci-integration) - [Cron Job for Drift Detection](#cron-job-for-drift-detection) - [Grafana Dashboard JSON](#grafana-dashboard-json) --- ## Ansible Integration ### Example: rsyslog Deployment Playbook ```yaml --- - name: Deploy rsyslog configuration hosts: rsyslog_servers vars: api_url: "http://gitops-status-api:5000" repo_name: "rsyslog" server_group: "rsyslog-{{ env }}" commit_sha: "{{ lookup('env', 'CI_COMMIT_SHA') | default('manual', true) }}" branch: "{{ lookup('env', 'CI_COMMIT_BRANCH') | default('unknown', true) }}" updated_by: "{{ lookup('env', 'CI_PIPELINE_SOURCE') | default('ansible', true) }}" tasks: - name: Copy rsyslog configuration files copy: src: "files/{{ item }}" dest: "/etc/rsyslog.d/{{ item }}" owner: root group: root mode: '0644' loop: - 50-default.conf - 60-custom.conf register: copy_result - name: Validate rsyslog configuration command: rsyslogd -N1 register: validation failed_when: false changed_when: false - name: Restart rsyslog if configuration changed service: name: rsyslog state: restarted when: copy_result.changed and validation.rc == 0 - name: Report status to API (success) uri: url: "{{ api_url }}/status/pipeline" method: POST body_format: json status_code: 200 body: project_name: "gitops-for-servers" repo_name: "{{ repo_name }}" server_group: "{{ server_group }}" server_type: "rsyslog" environment: "{{ env }}" host: "{{ inventory_hostname }}" status: "synced" changed_files: "{{ copy_result.results | selectattr('changed') | map(attribute='dest') | list }}" validation_status: "{{ 'passed' if validation.rc == 0 else 'failed' }}" validation_message: "{{ validation.stderr if validation.rc != 0 else 'rsyslog syntax validation passed' }}" commit_sha: "{{ commit_sha }}" branch: "{{ branch }}" updated_by: "{{ updated_by }}" last_pipeline_run: "{{ ansible_date_time.iso8601 }}" when: validation.rc == 0 - name: Report status to API (failure) uri: url: "{{ api_url }}/status/pipeline" method: POST body_format: json status_code: 200 body: project_name: "gitops-for-servers" repo_name: "{{ repo_name }}" server_group: "{{ server_group }}" server_type: "rsyslog" environment: "{{ env }}" host: "{{ inventory_hostname }}" status: "failed" changed_files: [] validation_status: "failed" validation_message: "{{ validation.stderr }}" commit_sha: "{{ commit_sha }}" branch: "{{ branch }}" updated_by: "{{ updated_by }}" last_pipeline_run: "{{ ansible_date_time.iso8601 }}" when: validation.rc != 0 - name: Fail the playbook if validation failed fail: msg: "rsyslog configuration validation failed" when: validation.rc != 0 ``` ### Example: nginx Deployment Playbook ```yaml --- - name: Deploy nginx configuration hosts: nginx_servers vars: api_url: "http://gitops-status-api:5000" tasks: - name: Copy nginx configuration template: src: templates/nginx.conf.j2 dest: /etc/nginx/nginx.conf register: nginx_config - name: Validate nginx configuration command: nginx -t register: validation failed_when: false changed_when: false - name: Reload nginx if valid service: name: nginx state: reloaded when: nginx_config.changed and validation.rc == 0 - name: Report to status API uri: url: "{{ api_url }}/status/pipeline" method: POST body_format: json body: project_name: "gitops-for-servers" repo_name: "nginx" server_group: "nginx-{{ env }}" server_type: "nginx" environment: "{{ env }}" host: "{{ inventory_hostname }}" status: "{{ 'synced' if validation.rc == 0 else 'failed' }}" changed_files: "{{ ['/etc/nginx/nginx.conf'] if nginx_config.changed else [] }}" validation_status: "{{ 'passed' if validation.rc == 0 else 'failed' }}" validation_message: "{{ validation.stderr if validation.rc != 0 else 'nginx -t passed' }}" commit_sha: "{{ lookup('env', 'CI_COMMIT_SHA') | default('manual') }}" branch: "{{ lookup('env', 'CI_COMMIT_BRANCH') | default('unknown') }}" updated_by: "ansible" last_pipeline_run: "{{ ansible_date_time.iso8601 }}" ``` --- ## Woodpecker CI Integration ### .woodpecker.yml Example ```yaml pipeline: validate: image: rsyslog/rsyslog:latest commands: - rsyslogd -N1 -f config/rsyslog.conf when: branch: master deploy: image: ansible/ansible:latest secrets: [ansible_vault_password] commands: - ansible-playbook -i inventory/prod deploy.yml when: branch: master event: push report-status: image: curlimages/curl:latest environment: - API_URL=http://gitops-status-api:5000 commands: - | curl -X POST $API_URL/status/pipeline \ -H "Content-Type: application/json" \ -d "{ \"project_name\": \"gitops-for-servers\", \"repo_name\": \"rsyslog\", \"server_group\": \"rsyslog-prod\", \"server_type\": \"rsyslog\", \"environment\": \"prod\", \"host\": \"rsyslog-group\", \"status\": \"synced\", \"validation_status\": \"passed\", \"commit_sha\": \"$CI_COMMIT_SHA\", \"branch\": \"$CI_COMMIT_BRANCH\", \"updated_by\": \"woodpecker\", \"last_pipeline_run\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" }" when: status: success report-failure: image: curlimages/curl:latest environment: - API_URL=http://gitops-status-api:5000 commands: - | curl -X POST $API_URL/status/pipeline \ -H "Content-Type: application/json" \ -d "{ \"project_name\": \"gitops-for-servers\", \"repo_name\": \"rsyslog\", \"server_group\": \"rsyslog-prod\", \"server_type\": \"rsyslog\", \"environment\": \"prod\", \"host\": \"rsyslog-group\", \"status\": \"failed\", \"validation_status\": \"failed\", \"validation_message\": \"Pipeline failed\", \"commit_sha\": \"$CI_COMMIT_SHA\", \"branch\": \"$CI_COMMIT_BRANCH\", \"updated_by\": \"woodpecker\", \"last_pipeline_run\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" }" when: status: failure ``` --- ## GitLab CI Integration ### .gitlab-ci.yml Example ```yaml stages: - validate - deploy - report variables: API_URL: "http://gitops-status-api:5000" SERVER_GROUP: "rsyslog-prod" SERVER_TYPE: "rsyslog" validate: stage: validate image: rsyslog/rsyslog:latest script: - rsyslogd -N1 -f config/rsyslog.conf only: - master deploy: stage: deploy image: ansible/ansible:latest script: - ansible-playbook -i inventory/prod deploy.yml only: - master artifacts: reports: dotenv: deploy.env report_success: stage: report image: curlimages/curl:latest script: - | curl -X POST $API_URL/status/pipeline \ -H "Content-Type: application/json" \ -d "{ \"project_name\": \"gitops-for-servers\", \"repo_name\": \"$CI_PROJECT_NAME\", \"server_group\": \"$SERVER_GROUP\", \"server_type\": \"$SERVER_TYPE\", \"environment\": \"prod\", \"host\": \"${SERVER_GROUP}-group\", \"status\": \"synced\", \"validation_status\": \"passed\", \"commit_sha\": \"$CI_COMMIT_SHA\", \"branch\": \"$CI_COMMIT_BRANCH\", \"updated_by\": \"gitlab-ci\", \"last_pipeline_run\": \"$CI_PIPELINE_CREATED_AT\" }" when: on_success only: - master report_failure: stage: report image: curlimages/curl:latest script: - | curl -X POST $API_URL/status/pipeline \ -H "Content-Type: application/json" \ -d "{ \"project_name\": \"gitops-for-servers\", \"repo_name\": \"$CI_PROJECT_NAME\", \"server_group\": \"$SERVER_GROUP\", \"server_type\": \"$SERVER_TYPE\", \"environment\": \"prod\", \"host\": \"${SERVER_GROUP}-group\", \"status\": \"failed\", \"validation_status\": \"failed\", \"validation_message\": \"Pipeline failed at $CI_JOB_NAME\", \"commit_sha\": \"$CI_COMMIT_SHA\", \"branch\": \"$CI_COMMIT_BRANCH\", \"updated_by\": \"gitlab-ci\", \"last_pipeline_run\": \"$CI_PIPELINE_CREATED_AT\" }" when: on_failure only: - master ``` --- ## Cron Job for Drift Detection ### Bash Script: check_rsyslog_drift.sh ```bash #!/bin/bash # # Drift detection script for rsyslog # Run this via cron every 15 minutes # # Crontab entry: # */15 * * * * /usr/local/bin/check_rsyslog_drift.sh set -e API_URL="${API_URL:-http://gitops-status-api:5000}" SERVER_GROUP="rsyslog-prod" SERVER_TYPE="rsyslog" HOST=$(hostname) DEPLOYED_CONFIG_DIR="/opt/gitops/deployed/rsyslog" ACTIVE_CONFIG_DIR="/etc/rsyslog.d" # Compare deployed vs active configuration CHANGED_FILES=() for file in "$DEPLOYED_CONFIG_DIR"/*.conf; do filename=$(basename "$file") active_file="$ACTIVE_CONFIG_DIR/$filename" if [ ! -f "$active_file" ]; then CHANGED_FILES+=("$active_file") elif ! diff -q "$file" "$active_file" > /dev/null 2>&1; then CHANGED_FILES+=("$active_file") fi done # Determine status if [ ${#CHANGED_FILES[@]} -eq 0 ]; then STATUS="synced" else STATUS="out_of_sync" fi # Build JSON array of changed files CHANGED_JSON="[]" if [ ${#CHANGED_FILES[@]} -gt 0 ]; then CHANGED_JSON=$(printf '%s\n' "${CHANGED_FILES[@]}" | jq -R . | jq -s .) fi # Report to API TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) curl -X POST "$API_URL/status/cron" \ -H "Content-Type: application/json" \ -d "{ \"server_group\": \"$SERVER_GROUP\", \"host\": \"$HOST\", \"status\": \"$STATUS\", \"changed_files\": $CHANGED_JSON, \"last_cron_check\": \"$TIMESTAMP\" }" # Log the result logger -t gitops-drift "Drift check: $STATUS (${#CHANGED_FILES[@]} files changed)" # Exit with error if drift detected (for alerting) if [ "$STATUS" == "out_of_sync" ]; then exit 1 fi exit 0 ``` ### Python Script: check_drift.py ```python #!/usr/bin/env python3 """ Generic drift detection script Works with any server type """ import os import sys import json import hashlib import requests from datetime import datetime from pathlib import Path API_URL = os.environ.get('API_URL', 'http://gitops-status-api:5000') SERVER_GROUP = os.environ.get('SERVER_GROUP', 'unknown') HOST = os.environ.get('HOSTNAME', os.uname().nodename) DEPLOYED_DIR = os.environ.get('DEPLOYED_DIR', '/opt/gitops/deployed') ACTIVE_DIR = os.environ.get('ACTIVE_DIR', '/etc') def file_hash(filepath): """Calculate SHA256 hash of file""" sha256 = hashlib.sha256() with open(filepath, 'rb') as f: for chunk in iter(lambda: f.read(4096), b""): sha256.update(chunk) return sha256.hexdigest() def check_drift(): """Compare deployed vs active configuration""" changed_files = [] deployed_path = Path(DEPLOYED_DIR) active_path = Path(ACTIVE_DIR) if not deployed_path.exists(): print(f"Deployed directory not found: {DEPLOYED_DIR}") return changed_files for deployed_file in deployed_path.rglob('*'): if deployed_file.is_file(): # Calculate relative path rel_path = deployed_file.relative_to(deployed_path) active_file = active_path / rel_path # Check if active file exists if not active_file.exists(): changed_files.append(str(active_file)) continue # Compare file hashes if file_hash(deployed_file) != file_hash(active_file): changed_files.append(str(active_file)) return changed_files def report_status(status, changed_files): """Report status to API""" timestamp = datetime.utcnow().isoformat() + 'Z' payload = { 'server_group': SERVER_GROUP, 'host': HOST, 'status': status, 'changed_files': changed_files, 'last_cron_check': timestamp } try: response = requests.post( f'{API_URL}/status/cron', json=payload, timeout=10 ) response.raise_for_status() print(f"Status reported: {status}") return True except Exception as e: print(f"Failed to report status: {e}") return False def main(): changed_files = check_drift() if len(changed_files) == 0: status = 'synced' print("No drift detected") else: status = 'out_of_sync' print(f"Drift detected: {len(changed_files)} files changed") for f in changed_files: print(f" - {f}") if report_status(status, changed_files): sys.exit(0 if status == 'synced' else 1) else: sys.exit(2) if __name__ == '__main__': main() ``` ### Systemd Timer for Drift Detection **File: /etc/systemd/system/gitops-drift-check.service** ```ini [Unit] Description=GitOps Configuration Drift Check After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/check_drift.py Environment="API_URL=http://gitops-status-api:5000" Environment="SERVER_GROUP=rsyslog-prod" Environment="DEPLOYED_DIR=/opt/gitops/deployed/rsyslog" Environment="ACTIVE_DIR=/etc/rsyslog.d" User=root StandardOutput=journal StandardError=journal ``` **File: /etc/systemd/system/gitops-drift-check.timer** ```ini [Unit] Description=Run GitOps drift check every 15 minutes [Timer] OnBootSec=5min OnUnitActiveSec=15min AccuracySec=1min [Install] WantedBy=timers.target ``` **Enable the timer:** ```bash systemctl daemon-reload systemctl enable gitops-drift-check.timer systemctl start gitops-drift-check.timer systemctl status gitops-drift-check.timer ``` --- ## Grafana Dashboard JSON ### Simple Status Table Dashboard ```json { "dashboard": { "title": "GitOps Server Status", "panels": [ { "id": 1, "title": "Server Status Overview", "type": "table", "targets": [ { "refId": "A", "type": "json", "source": "url", "url": "/status", "method": "GET", "parser": "backend" } ], "fieldConfig": { "overrides": [ { "matcher": { "id": "byName", "options": "status" }, "properties": [ { "id": "mappings", "value": [ { "type": "value", "value": "synced", "text": "✓ Synced", "color": "green" }, { "type": "value", "value": "out_of_sync", "text": "⚠ Out of Sync", "color": "red" }, { "type": "value", "value": "failed", "text": "✗ Failed", "color": "dark-red" } ] } ] } ] } }, { "id": 2, "title": "Servers Out of Sync", "type": "stat", "targets": [ { "refId": "A", "type": "json", "source": "url", "url": "/status", "method": "GET", "parser": "backend", "filterExpression": "status == 'out_of_sync'" } ], "options": { "graphMode": "none", "colorMode": "value", "reduceOptions": { "values": false, "calcs": ["count"] } }, "fieldConfig": { "defaults": { "thresholds": { "mode": "absolute", "steps": [ { "value": 0, "color": "green" }, { "value": 1, "color": "red" } ] } } } } ] } } ``` --- ## Summary These integration examples show how to: 1. **Ansible**: Report deployment status from playbooks 2. **CI/CD**: Integrate with Woodpecker, GitLab, or any CI system 3. **Cron**: Detect configuration drift periodically 4. **Grafana**: Visualize status across all servers Adapt these examples to your specific environment and server types.