657 lines
17 KiB
Markdown
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.
|