gitops-status-api/INTEGRATION_EXAMPLES.md
dvirlabs f8fa847c11
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
refactor the project to make it generic
2026-04-26 01:08:15 +03:00

17 KiB

Integration Examples

This document provides integration examples for common CI/CD tools and automation frameworks.

Table of Contents


Ansible Integration

Example: rsyslog Deployment Playbook

---
- 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

---
- 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

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

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

#!/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

#!/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

[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

[Unit]
Description=Run GitOps drift check every 15 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
AccuracySec=1min

[Install]
WantedBy=timers.target

Enable the timer:

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

{
  "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.