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

657 lines
17 KiB
Markdown

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