Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eacd1f873e | ||
|
|
fc8dc8df9c | ||
|
|
0d0169c97d | ||
|
|
44dd3a9831 | ||
|
|
4084aaea4c | ||
|
|
bdfbf40a75 | ||
|
|
5e38581b97 | ||
|
|
cf83072c38 | ||
|
|
8d55618ef9 | ||
|
|
55fc47f627 | ||
|
|
6fec4c3e5f | ||
|
|
215f09ebed | ||
|
|
2c27e6ade7 | ||
|
|
c83725a027 | ||
|
|
59204be5fe | ||
|
|
00f4c4b730 | ||
|
|
80e39650f9 | ||
|
|
8b62ba780d | ||
|
|
81263fc950 | ||
|
|
ea9004e382 | ||
|
|
b39537c3e3 | ||
| 7a83b68feb | |||
| 17a6311792 | |||
| db79f854f4 | |||
| 5f6f641bb4 | |||
| 5c52acf456 | |||
| 287bb60211 | |||
| fa9ab6e123 | |||
| 0b799bef84 | |||
| bbd675460f | |||
| dc1dce2e07 | |||
| 89dd8aeb40 | |||
| 05c190bb9b | |||
| 549fc97b54 | |||
| d2e747880c | |||
| c0b4cf27c6 | |||
| 1b1b9a1dfd | |||
| 1b9a7d85ce | |||
| d7b580c9aa | |||
| eddad9b770 | |||
| fa682da7a6 | |||
| 7958cf54b4 | |||
| 7ad21286e3 | |||
| 20a3c14d66 | |||
| f3c947c9c4 | |||
| 1968e787b1 | |||
| b16aa7cc7e | |||
| 4a2629ae0e | |||
|
|
16702cb408 | ||
|
|
66df6c74ec | ||
|
|
96bd931e2f | ||
|
|
2b6b3011a6 | ||
|
|
4d5880c232 | ||
|
|
ac4278a451 | ||
|
|
2b192dd26a | ||
|
|
1cbe3d4de7 | ||
|
|
57870e65ce | ||
|
|
380eaf175a | ||
|
|
d200914057 | ||
|
|
06827001e6 | ||
|
|
6420a584c4 | ||
|
|
5285f4c241 | ||
|
|
de99902959 | ||
|
|
cdec5ce87c | ||
| e8fed366b4 | |||
| 7d3dbf6395 | |||
| b9af1e4534 | |||
| 1837b7794b | |||
| e500e21fab | |||
| be352ae9c9 | |||
| 4212daba7a | |||
| 46b0bb449e | |||
| 57911b7f52 | |||
| 654259c7cd | |||
| 15b0e153f0 | |||
| 8e54f82e12 | |||
|
|
6fbadf73e3 | ||
|
|
fc8cb0c40e | ||
|
|
f926b7eb0d | ||
|
|
7aeb957eb3 | ||
|
|
7cf1380007 | ||
|
|
adc8d1e50c | ||
|
|
1bac032155 | ||
|
|
db28c9da82 | ||
|
|
c18acce05e | ||
| 082ed0a0a4 | |||
|
|
3282870e8f | ||
|
|
b3e96d343f | ||
|
|
197647fb15 | ||
|
|
5c7fdf101e | ||
| 5219f32d79 | |||
| 3a8ad2a1e2 | |||
| ea7e1ee066 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
custom-ansible.tar
|
||||||
108
.woodpecker.yml
108
.woodpecker.yml
@ -8,13 +8,15 @@
|
|||||||
# so a drift-check here would always be OUT_OF_SYNC by
|
# so a drift-check here would always be OUT_OF_SYNC by
|
||||||
# design and is meaningless as a failure signal.
|
# design and is meaningless as a failure signal.
|
||||||
#
|
#
|
||||||
# push (master) → syntax-check, validate, deploy, update-sync-metric
|
# push (master) → syntax-check, validate, deploy, update-gitops-status
|
||||||
# Deploys to the server, then verifies sync and pushes metric.
|
# 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)
|
# cron → gitops_sync_check (read-only drift check, no deploy)
|
||||||
# Continuously verifies that the live server still matches
|
# Continuously verifies that the live server still matches
|
||||||
# Git even when no push has happened. Detects manual edits
|
# Git even when no push has happened. Detects manual edits
|
||||||
# made directly on the server.
|
# 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.
|
# NOTE: Woodpecker does not support multiple YAML documents (---) in one file.
|
||||||
# All pipelines must live in a single document with step-level filtering.
|
# All pipelines must live in a single document with step-level filtering.
|
||||||
@ -80,44 +82,48 @@ steps:
|
|||||||
event: push
|
event: push
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# update-sync-metric: Post-deploy sync check + Prometheus metric push
|
# update-gitops-status: Post-deploy sync check + JSON status update
|
||||||
# Runs on push to master only, after deploy succeeds.
|
# Runs on push to master only, after deploy succeeds.
|
||||||
# STATUS=1 means SYNCED, STATUS=0 means OUT_OF_SYNC.
|
# Generates structured JSON with sync status, drift count, and changed files.
|
||||||
|
# Sends JSON to gitops-status-server for Grafana visualization.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
update-sync-metric:
|
update-gitops-status:
|
||||||
image: alpine/ansible:latest
|
image: alpine/ansible:latest
|
||||||
depends_on: [deploy]
|
depends_on: [deploy]
|
||||||
environment:
|
environment:
|
||||||
ANSIBLE_CONFIG: ansible.cfg
|
ANSIBLE_CONFIG: ansible.cfg
|
||||||
SSH_PRIVATE_KEY:
|
SSH_PRIVATE_KEY:
|
||||||
from_secret: SSH_PRIVATE_KEY
|
from_secret: SSH_PRIVATE_KEY
|
||||||
PUSHGATEWAY_URL: http://pushgateway.observability-stack.svc.cluster.local:9091
|
GITOPS_STATUS_SERVER_URL: http://gitops-status-server.observability-stack.svc.cluster.local:5000
|
||||||
|
REPO_NAME: rsyslog
|
||||||
|
SERVER_NAME: rsyslog-lab
|
||||||
|
MODE: post-deploy
|
||||||
|
# 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:
|
commands:
|
||||||
- |
|
- |
|
||||||
apk add --no-cache curl > /dev/null 2>&1
|
# Increase file descriptor limit for Ansible (max safe value)
|
||||||
|
ulimit -n 65536
|
||||||
|
|
||||||
|
# Install dependencies: git for detecting deployed files, curl for HTTP requests, jq for JSON formatting
|
||||||
|
apk add --no-cache git curl jq > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Setup SSH key for Ansible
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||||
chmod 600 ~/.ssh/id_rsa
|
chmod 600 ~/.ssh/id_rsa
|
||||||
|
|
||||||
echo "==> Verifying post-deploy sync status..."
|
echo "==> Running post-deploy GitOps status check..."
|
||||||
set +e
|
|
||||||
ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml
|
# Make script executable and run it
|
||||||
DRIFT_RC=$?
|
chmod +x update-gitops-status.sh
|
||||||
set -e
|
./update-gitops-status.sh
|
||||||
|
|
||||||
if [ "$DRIFT_RC" -eq 0 ]; then
|
echo "==> JSON status update complete. Pipeline always succeeds."
|
||||||
STATUS=1
|
|
||||||
echo "==> SYNCED (1) – server configuration matches Git"
|
|
||||||
else
|
|
||||||
STATUS=0
|
|
||||||
echo "==> OUT OF SYNC (0) – drift detected after deploy"
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf 'gitops_sync_status{repo="rsyslog",server="rsyslog-lab"} %s\n' "$STATUS" | \
|
|
||||||
curl --silent --show-error --fail --data-binary @- \
|
|
||||||
"$${PUSHGATEWAY_URL}/metrics/job/gitops_rsyslog/instance/rsyslog-lab"
|
|
||||||
|
|
||||||
echo "==> Metric pushed. Pipeline always succeeds; sync status is in Prometheus."
|
|
||||||
when:
|
when:
|
||||||
branch: master
|
branch: master
|
||||||
event: push
|
event: push
|
||||||
@ -125,13 +131,14 @@ steps:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# gitops_sync_check: ArgoCD-style cron drift check – read-only, no deploy
|
# gitops_sync_check: ArgoCD-style cron drift check – read-only, no deploy
|
||||||
# Detects manual changes made directly on the server between pushes.
|
# Detects manual changes made directly on the server between pushes.
|
||||||
# STATUS=1 → SYNCED, STATUS=0 → OUT_OF_SYNC
|
# Generates structured JSON with sync status, drift count, and changed files.
|
||||||
# Pipeline marked FAILED when drift found so it is visible in the UI.
|
# Sends JSON to gitops-status-server for continuous GitOps monitoring.
|
||||||
|
# Pipeline always succeeds - check Grafana dashboard for drift status.
|
||||||
#
|
#
|
||||||
# ─── Woodpecker Cron UI settings ──────────────────────────────────────────
|
# ─── Woodpecker Cron UI settings ──────────────────────────────────────────
|
||||||
# Name: gitops_sync_check
|
# Name: gitops_sync_check
|
||||||
# Branch: master
|
# Branch: master
|
||||||
# Schedule: */2 * * * *
|
# Schedule: */1 * * * * (every 1 minute for testing, adjust as needed)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
gitops_sync_check:
|
gitops_sync_check:
|
||||||
image: alpine/ansible:latest
|
image: alpine/ansible:latest
|
||||||
@ -139,34 +146,35 @@ steps:
|
|||||||
ANSIBLE_CONFIG: ansible.cfg
|
ANSIBLE_CONFIG: ansible.cfg
|
||||||
SSH_PRIVATE_KEY:
|
SSH_PRIVATE_KEY:
|
||||||
from_secret: SSH_PRIVATE_KEY
|
from_secret: SSH_PRIVATE_KEY
|
||||||
PUSHGATEWAY_URL: http://pushgateway.observability-stack.svc.cluster.local:9091
|
GITOPS_STATUS_SERVER_URL: http://gitops-status-server.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:
|
commands:
|
||||||
- |
|
- |
|
||||||
apk add --no-cache curl > /dev/null 2>&1
|
# 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
|
mkdir -p ~/.ssh
|
||||||
printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||||
chmod 600 ~/.ssh/id_rsa
|
chmod 600 ~/.ssh/id_rsa
|
||||||
|
|
||||||
echo "==> [cron] Running drift check against remote server..."
|
echo "==> [cron] Running continuous GitOps drift check..."
|
||||||
set +e
|
|
||||||
ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml
|
# Make script executable and run it (always succeeds - just updates JSON)
|
||||||
DRIFT_RC=$?
|
chmod +x update-gitops-status.sh
|
||||||
set -e
|
./update-gitops-status.sh
|
||||||
|
|
||||||
if [ "$DRIFT_RC" -eq 0 ]; then
|
echo "==> Cron drift check complete. JSON status updated successfully."
|
||||||
STATUS=1
|
echo " Pipeline always succeeds. Check gitops-status-server for sync status."
|
||||||
echo "==> STATUS: SYNCED (1) – server configuration matches Git"
|
|
||||||
else
|
|
||||||
STATUS=0
|
|
||||||
echo "==> STATUS: OUT OF SYNC (0) – manual drift detected on server"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "==> Pushing metric: gitops_sync_status{repo=\"rsyslog\",server=\"rsyslog-lab\"} $STATUS"
|
|
||||||
printf 'gitops_sync_status{repo="rsyslog",server="rsyslog-lab"} %s\n' "$STATUS" | \
|
|
||||||
curl --silent --show-error --fail --data-binary @- \
|
|
||||||
"$${PUSHGATEWAY_URL}/metrics/job/gitops_rsyslog/instance/rsyslog-lab"
|
|
||||||
|
|
||||||
echo "==> Metric pushed. Pipeline always succeeds; sync status is in Prometheus."
|
|
||||||
when:
|
when:
|
||||||
event: cron
|
event: cron
|
||||||
|
|||||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM alpine/ansible:latest
|
||||||
|
|
||||||
|
# Install required dependencies for GitOps status check
|
||||||
|
# - git: for detecting deployed files and git operations
|
||||||
|
# - curl: for HTTP requests to gitops-status-server
|
||||||
|
# - jq: for JSON formatting and parsing
|
||||||
|
# - bash: for script execution
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
jq \
|
||||||
|
bash
|
||||||
348
README.md
Normal file
348
README.md
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
# File Deployment & GitOps Management
|
||||||
|
|
||||||
|
A simple, generic Ansible-based system to deploy and manage files on multiple servers using Git as the single source of truth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This repository uses **Ansible** to:
|
||||||
|
- **Deploy** files from Git to target servers
|
||||||
|
- **Check Drift** to ensure servers stay in sync with the repository
|
||||||
|
- **Validate** that deployed files are correct
|
||||||
|
|
||||||
|
No rsyslog-specific code. Just simple file deployment that works for any file or service.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── README.md # This file
|
||||||
|
├── ansible.cfg # Ansible configuration
|
||||||
|
├── .woodpecker.yml # CI/CD pipeline configuration
|
||||||
|
│
|
||||||
|
├── ansible/
|
||||||
|
│ ├── inventory/
|
||||||
|
│ │ ├── hosts.yml # Define target servers
|
||||||
|
│ │ └── group_vars/
|
||||||
|
│ │ └── all.yml # Global variables (SSH credentials, etc.)
|
||||||
|
│ │
|
||||||
|
│ └── playbooks/
|
||||||
|
│ ├── apply.yml # Deploy file to servers
|
||||||
|
│ ├── drift-check.yml # Check if servers are in sync with repo
|
||||||
|
│ └── validate.yml # Verify file exists on server
|
||||||
|
│
|
||||||
|
└── files/
|
||||||
|
└── dvir.txt # The file to deploy (edit this to your needs)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works (Simple Version)
|
||||||
|
|
||||||
|
1. **You edit the file** in `files/dvir.txt`
|
||||||
|
2. **You commit to Git** (the source of truth)
|
||||||
|
3. **Run `apply.yml`** to deploy to all servers
|
||||||
|
4. **Run `drift-check.yml`** anytime to verify servers match Git
|
||||||
|
5. **If drift is detected**, run `apply.yml` again to fix it
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Edit the file you want to deploy
|
||||||
|
|
||||||
|
Open `files/dvir.txt` and add your content:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano files/dvir.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
To deploy a **different file**, rename it or update the paths in the playbooks.
|
||||||
|
|
||||||
|
### 2. Add target servers
|
||||||
|
|
||||||
|
Edit `ansible/inventory/hosts.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
all:
|
||||||
|
children:
|
||||||
|
servers:
|
||||||
|
hosts:
|
||||||
|
server1:
|
||||||
|
ansible_host: 192.168.10.161
|
||||||
|
server2:
|
||||||
|
ansible_host: 192.168.10.162
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Run the playbooks
|
||||||
|
|
||||||
|
#### Deploy the file:
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/apply.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Check if servers are in sync:
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/drift-check.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Validate file on server:
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/validate.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Playbook Breakdown
|
||||||
|
|
||||||
|
### `apply.yml` - Deploy Files
|
||||||
|
**What it does:**
|
||||||
|
- Copies `files/dvir.txt` to `/tmp/dvir.txt` on all target servers
|
||||||
|
- Sets file ownership to `root:root` with permissions `0644`
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Initial deployment of the file
|
||||||
|
- After updating the file in Git
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/apply.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `drift-check.yml` - Detect Configuration Drift
|
||||||
|
**What it does:**
|
||||||
|
- Reads the file from the Git repository (local)
|
||||||
|
- Reads the file from each target server (`/tmp/dvir.txt`)
|
||||||
|
- Compares the content byte-for-byte
|
||||||
|
- Reports `SYNCED` or `OUT_OF_SYNC`
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Verify servers match the repository state
|
||||||
|
- Detect if someone manually changed the file on the server
|
||||||
|
- Run periodically (via cron or CI/CD) to monitor compliance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/drift-check.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```
|
||||||
|
✓ dvir.txt is synced # Files match
|
||||||
|
✗ dvir.txt is out of sync # Files differ or file missing
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `validate.yml` - Validate Deployment
|
||||||
|
**What it does:**
|
||||||
|
- Checks that `/tmp/dvir.txt` exists on the server
|
||||||
|
- Verifies the file is readable
|
||||||
|
- Fails if the file is missing or not readable
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- After running `apply.yml` to verify success
|
||||||
|
- To confirm the deployment was successful
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/validate.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Inventory: `ansible/inventory/hosts.yml`
|
||||||
|
Define which servers to manage:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
all:
|
||||||
|
children:
|
||||||
|
servers:
|
||||||
|
hosts:
|
||||||
|
server1:
|
||||||
|
ansible_host: 192.168.10.161
|
||||||
|
server2:
|
||||||
|
ansible_host: 192.168.10.162
|
||||||
|
```
|
||||||
|
|
||||||
|
### Global Variables: `ansible/inventory/group_vars/all.yml`
|
||||||
|
SSH and connection settings:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ansible_user: root
|
||||||
|
ansible_ssh_private_key_file: "~/.ssh/id_rsa"
|
||||||
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ansible Config: `ansible.cfg`
|
||||||
|
Global Ansible settings (host checking, plugins, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow Examples
|
||||||
|
|
||||||
|
### Example 1: Deploy a new file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Edit the file
|
||||||
|
echo "new config content" > files/dvir.txt
|
||||||
|
|
||||||
|
# 2. Commit to Git
|
||||||
|
git add files/dvir.txt
|
||||||
|
git commit -m "Update deployment file"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# 3. Deploy to servers
|
||||||
|
ansible-playbook ansible/playbooks/apply.yml
|
||||||
|
|
||||||
|
# 4. Verify success
|
||||||
|
ansible-playbook ansible/playbooks/validate.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Monitor for drift (continuous compliance)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run drift check periodically (cron job)
|
||||||
|
0 */6 * * * cd /path/to/repo && ansible-playbook ansible/playbooks/drift-check.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Detect and fix manual changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Someone manually edited /tmp/dvir.txt on server1
|
||||||
|
# Check for drift
|
||||||
|
ansible-playbook ansible/playbooks/drift-check.yml
|
||||||
|
# Output: ✗ dvir.txt is out of sync
|
||||||
|
|
||||||
|
# Restore from Git
|
||||||
|
ansible-playbook ansible/playbooks/apply.yml
|
||||||
|
|
||||||
|
# Verify it's fixed
|
||||||
|
ansible-playbook ansible/playbooks/drift-check.yml
|
||||||
|
# Output: ✓ dvir.txt is synced
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Ansible** installed on your machine
|
||||||
|
- **SSH access** to all target servers (password or key-based)
|
||||||
|
- **Root or sudo access** on target servers (for writing to `/tmp`)
|
||||||
|
|
||||||
|
### Install Ansible
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install ansible
|
||||||
|
|
||||||
|
# Ubuntu/Debian
|
||||||
|
apt-get install ansible
|
||||||
|
|
||||||
|
# RHEL/CentOS
|
||||||
|
yum install ansible
|
||||||
|
|
||||||
|
# Python pip
|
||||||
|
pip install ansible
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Deploy to a different path
|
||||||
|
|
||||||
|
Edit `ansible/playbooks/apply.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Copy file to destination
|
||||||
|
copy:
|
||||||
|
src: ../../files/dvir.txt
|
||||||
|
dest: /your/custom/path/filename.txt # ← Change this
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy multiple files
|
||||||
|
|
||||||
|
Add more tasks to `apply.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Copy file 1
|
||||||
|
copy:
|
||||||
|
src: ../../files/dvir.txt
|
||||||
|
dest: /tmp/dvir.txt
|
||||||
|
|
||||||
|
- name: Copy file 2
|
||||||
|
copy:
|
||||||
|
src: ../../files/another.txt
|
||||||
|
dest: /tmp/another.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use different target servers
|
||||||
|
|
||||||
|
Edit `hosts.yml` and use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/playbooks/apply.yml -i ansible/inventory/hosts.yml --limit "server2"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "SSH connection refused"
|
||||||
|
- Check `ansible_host` is correct in `hosts.yml`
|
||||||
|
- Verify SSH key in `group_vars/all.yml`
|
||||||
|
- Test manually: `ssh -i ~/.ssh/id_rsa root@192.168.10.161`
|
||||||
|
|
||||||
|
### "Permission denied" on deployment
|
||||||
|
- Ensure `become: true` is in the playbook
|
||||||
|
- Verify user has sudo access
|
||||||
|
- Check SSH key has correct permissions: `chmod 600 ~/.ssh/id_rsa`
|
||||||
|
|
||||||
|
### Drift check shows "out of sync" but I didn't change anything
|
||||||
|
- File permissions or ownership might have changed
|
||||||
|
- Line endings (CRLF vs LF) might differ
|
||||||
|
- The server file might be missing
|
||||||
|
|
||||||
|
### Can't read file on server
|
||||||
|
- Check `/tmp/dvir.txt` exists: `ls -la /tmp/dvir.txt`
|
||||||
|
- Verify permissions: `stat /tmp/dvir.txt`
|
||||||
|
- Ensure file is readable: `cat /tmp/dvir.txt`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips & Best Practices
|
||||||
|
|
||||||
|
1. **Always commit before deploying**
|
||||||
|
- Use Git as the single source of truth
|
||||||
|
- Never manually edit `/tmp/dvir.txt` on servers
|
||||||
|
|
||||||
|
2. **Run drift-check regularly**
|
||||||
|
- Use cron or CI/CD to monitor compliance
|
||||||
|
- Alert on `OUT_OF_SYNC` status
|
||||||
|
|
||||||
|
3. **Test in dev first**
|
||||||
|
- Add a `dev` group in `hosts.yml`
|
||||||
|
- Test playbooks on dev servers before prod
|
||||||
|
|
||||||
|
4. **Use version control for everything**
|
||||||
|
- Keep all changes in Git
|
||||||
|
- Easy rollback: just revert and re-run `apply.yml`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT (or your preferred license)
|
||||||
29
ansible/deploy-config.yml
Normal file
29
ansible/deploy-config.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
# =============================================================================
|
||||||
|
# Deploy Configuration
|
||||||
|
# Purpose: Centralized list of files to deploy to target servers
|
||||||
|
# Usage: Referenced by apply.yml and drift-check.yml playbooks
|
||||||
|
#
|
||||||
|
# To add a new file:
|
||||||
|
# 1. Create the file in the repo (e.g., files/myfile.conf)
|
||||||
|
# 2. Add an entry below with: name, src, dest
|
||||||
|
# 3. Commit and push - pipeline handles the rest!
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# All files use the same owner/group (root) and permissions (0644)
|
||||||
|
# Defaults apply to all items - override per-file if needed
|
||||||
|
|
||||||
|
deploy_items:
|
||||||
|
- name: "dvir.txt"
|
||||||
|
src: "files/dvir.txt"
|
||||||
|
dest: "/tmp/dvir.txt"
|
||||||
|
|
||||||
|
# Example: To add another file, just uncomment and update the paths:
|
||||||
|
# - name: "myapp.conf"
|
||||||
|
# src: "files/myapp.conf"
|
||||||
|
# dest: "/etc/myapp/myapp.conf"
|
||||||
|
#
|
||||||
|
# - name: "rsyslog.conf"
|
||||||
|
# src: "files/rsyslog.conf"
|
||||||
|
# dest: "/etc/rsyslog.d/99-custom.conf"
|
||||||
|
|
||||||
@ -1,17 +1,7 @@
|
|||||||
---
|
---
|
||||||
# Global variables for rsyslog configuration management
|
# Global variables for deployment
|
||||||
|
|
||||||
# Ansible connection settings
|
# Ansible connection settings
|
||||||
ansible_user: root
|
ansible_user: root
|
||||||
ansible_ssh_private_key_file: "~/.ssh/id_rsa"
|
ansible_ssh_private_key_file: "~/.ssh/id_rsa"
|
||||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||||
|
|
||||||
# Root directory of the rsyslog repository
|
|
||||||
repo_root: /root/rsyslog
|
|
||||||
|
|
||||||
# rsyslog service name
|
|
||||||
rsyslog_service: rsyslog
|
|
||||||
|
|
||||||
# Configuration paths
|
|
||||||
rsyslog_main_config: /etc/rsyslog.conf
|
|
||||||
rsyslog_config_dir: /etc/rsyslog.d
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
all:
|
all:
|
||||||
children:
|
children:
|
||||||
rsyslog_servers:
|
servers:
|
||||||
hosts:
|
hosts:
|
||||||
rsyslog-lab:
|
server1:
|
||||||
ansible_host: 192.168.10.161
|
ansible_host: 192.168.10.161
|
||||||
# Future servers can be added here:
|
# Add more servers here:
|
||||||
# rsyslog-prod:
|
# server2:
|
||||||
# ansible_host: 192.168.10.162
|
# ansible_host: 192.168.10.162
|
||||||
# rsyslog-backup:
|
# server3:
|
||||||
# ansible_host: 192.168.10.163
|
# ansible_host: 192.168.10.163
|
||||||
|
|||||||
@ -1,145 +1,78 @@
|
|||||||
---
|
---
|
||||||
- name: Apply rsyslog configuration (safe staged deployment)
|
# =============================================================================
|
||||||
hosts: rsyslog_servers
|
# APPLY PLAYBOOK
|
||||||
become: true
|
# Purpose: Deploy files from deploy-config.yml to target servers
|
||||||
|
# Usage: ansible-playbook apply.yml
|
||||||
|
# Note: Uses Ansible's register mechanism to capture deployed files
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
vars:
|
- name: Deploy files to servers
|
||||||
backup_dir: /var/backups/rsyslog-ansible
|
hosts: all
|
||||||
backup_conf: "{{ backup_dir }}/rsyslog.conf.bak"
|
become: true
|
||||||
backup_confd: "{{ backup_dir }}/rsyslog.d.bak"
|
vars_files:
|
||||||
|
- ../deploy-config.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
# -------------------------------------------------------------------------
|
# TASK 1: Deploy each configured file/folder
|
||||||
# STAGE 1 — Backup current working configuration
|
# Loops through deploy_items and copies each to destination
|
||||||
# -------------------------------------------------------------------------
|
# Registers which files were successfully deployed
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
- name: Ensure backup directory exists
|
- name: Deploy configured files
|
||||||
file:
|
|
||||||
path: "{{ backup_dir }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0700"
|
|
||||||
|
|
||||||
- name: Backup current rsyslog.conf
|
|
||||||
copy:
|
copy:
|
||||||
src: "{{ rsyslog_main_config }}"
|
src: "{{ playbook_dir }}/{{ '../../' + item.src }}"
|
||||||
dest: "{{ backup_conf }}"
|
dest: "{{ item.dest }}"
|
||||||
remote_src: true
|
owner: "{{ item.owner | default('root') }}"
|
||||||
mode: "0600"
|
group: "{{ item.group | default('root') }}"
|
||||||
|
mode: "{{ item.mode | default('0644') }}"
|
||||||
|
backup: yes
|
||||||
|
register: deploy_results
|
||||||
|
loop: "{{ deploy_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: item
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
- name: Remove stale rsyslog.d backup
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
file:
|
# TASK 2: Extract deployed file information
|
||||||
path: "{{ backup_confd }}"
|
# Builds a list of deployed files from the copy task results
|
||||||
state: absent
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Extract deployed file information
|
||||||
|
set_fact:
|
||||||
|
deployed_file_list: |
|
||||||
|
[
|
||||||
|
{%- for result in deploy_results.results -%}
|
||||||
|
{
|
||||||
|
"name": "{{ result.item.name }}",
|
||||||
|
"src": "{{ result.item.src }}",
|
||||||
|
"dest": "{{ result.item.dest }}",
|
||||||
|
"changed": {{ result.changed | lower }}
|
||||||
|
}{{ "," if not loop.last else "" }}
|
||||||
|
{%- endfor -%}
|
||||||
|
]
|
||||||
|
|
||||||
- name: Backup current rsyslog.d directory
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 3: Save deployed files list to a temporary file
|
||||||
|
# This file will be read by the GitOps status script
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Save deployed files list
|
||||||
copy:
|
copy:
|
||||||
src: "{{ rsyslog_config_dir }}/"
|
content: "{{ deployed_file_list }}"
|
||||||
dest: "{{ backup_confd }}/"
|
dest: "/tmp/deployed_files_{{ inventory_hostname }}.json"
|
||||||
remote_src: true
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# STAGE 2 — Deploy new configuration files from repo
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- name: Copy new rsyslog.conf from repo
|
|
||||||
copy:
|
|
||||||
src: ../../files/rsyslog.conf
|
|
||||||
dest: "{{ rsyslog_main_config }}"
|
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Copy new rsyslog.d configs from repo
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
copy:
|
# TASK 4: Confirm deployment success
|
||||||
src: ../../files/rsyslog.d/
|
# Displays summary of deployed files
|
||||||
dest: "{{ rsyslog_config_dir }}/"
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
owner: root
|
- name: Display deployment summary
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# STAGE 3 — Validate against the full real config tree on the remote host
|
|
||||||
# Runs rsyslogd -N1 against the actual /etc/rsyslog.conf so all includes,
|
|
||||||
# modules, and templates are resolved in the real environment.
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- name: Validate new configuration on remote host
|
|
||||||
command: rsyslogd -N1 -f "{{ rsyslog_main_config }}"
|
|
||||||
register: validation_result
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false # We handle failure manually below
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# STAGE 4a — Validation FAILED: restore backup and abort
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- name: Print validation error output
|
|
||||||
debug:
|
debug:
|
||||||
msg: |
|
msg: "✅ Deployed {{ deploy_results.results | length }} file(s) to {{ inventory_hostname }}"
|
||||||
##################################################
|
|
||||||
❌ RSYSLOG VALIDATION FAILED
|
|
||||||
##################################################
|
|
||||||
|
|
||||||
HOST : {{ inventory_hostname }} ({{ ansible_host }})
|
- name: Display deployed files
|
||||||
RC : {{ validation_result.rc }}
|
|
||||||
|
|
||||||
--- STDOUT ----------------------------------
|
|
||||||
{{ validation_result.stdout | default('(empty)') }}
|
|
||||||
|
|
||||||
--- STDERR ----------------------------------
|
|
||||||
{{ validation_result.stderr | default('(empty)') }}
|
|
||||||
|
|
||||||
##################################################
|
|
||||||
⚠ Rolling back to previous working configuration
|
|
||||||
##################################################
|
|
||||||
when: validation_result.rc != 0
|
|
||||||
|
|
||||||
- name: Restore rsyslog.conf from backup
|
|
||||||
copy:
|
|
||||||
src: "{{ backup_conf }}"
|
|
||||||
dest: "{{ rsyslog_main_config }}"
|
|
||||||
remote_src: true
|
|
||||||
mode: "0644"
|
|
||||||
when: validation_result.rc != 0
|
|
||||||
|
|
||||||
- name: Restore rsyslog.d from backup
|
|
||||||
copy:
|
|
||||||
src: "{{ backup_confd }}/"
|
|
||||||
dest: "{{ rsyslog_config_dir }}/"
|
|
||||||
remote_src: true
|
|
||||||
when: validation_result.rc != 0
|
|
||||||
|
|
||||||
- name: Fail pipeline — config restored to previous working state
|
|
||||||
fail:
|
|
||||||
msg: |
|
|
||||||
##################################################
|
|
||||||
❌ PIPELINE FAILED — rsyslog validation error
|
|
||||||
##################################################
|
|
||||||
Previous working config has been restored.
|
|
||||||
rsyslog was NOT restarted.
|
|
||||||
rc={{ validation_result.rc }}
|
|
||||||
stderr: {{ validation_result.stderr | default('(empty)') }}
|
|
||||||
##################################################
|
|
||||||
when: validation_result.rc != 0
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# STAGE 4b — Validation PASSED: restart rsyslog and report success
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- name: Restart rsyslog service
|
|
||||||
service:
|
|
||||||
name: "{{ rsyslog_service }}"
|
|
||||||
state: restarted
|
|
||||||
when: validation_result.rc == 0
|
|
||||||
|
|
||||||
- name: Print success status
|
|
||||||
debug:
|
debug:
|
||||||
msg: |
|
msg: " - {{ item.item.name }} → {{ item.item.dest }} ({{ 'changed' if item.changed else 'unchanged' }})"
|
||||||
##################################################
|
loop: "{{ deploy_results.results }}"
|
||||||
✅ RSYSLOG CONFIGURATION DEPLOYED SUCCESSFULLY
|
loop_control:
|
||||||
##################################################
|
label: "{{ item.item.name }}"
|
||||||
HOST : {{ inventory_hostname }} ({{ ansible_host }})
|
|
||||||
STATUS : Validation passed. Service restarted.
|
|
||||||
##################################################
|
|
||||||
when: validation_result.rc == 0
|
|
||||||
@ -1,101 +1,136 @@
|
|||||||
---
|
---
|
||||||
- name: Check rsyslog configuration drift
|
# =============================================================================
|
||||||
hosts: rsyslog_servers
|
# DRIFT-CHECK PLAYBOOK
|
||||||
gather_facts: false
|
# Purpose: Check file drift and output detailed file-level JSON report
|
||||||
|
# Usage: ansible-playbook drift-check.yml
|
||||||
|
# Output: Structured JSON with sync status and drifted files info
|
||||||
|
# Registers: drift_detected, drifted_files_json for consumption by update script
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
# NOTE: src paths below resolve relative to the Ansible controller (the
|
- name: Check file drift
|
||||||
# Woodpecker CI container), so they always reflect the latest Git commit –
|
hosts: all
|
||||||
# NOT the server's local clone, which may be stale.
|
gather_facts: false
|
||||||
|
vars_files:
|
||||||
|
- ../deploy-config.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
# -------------------------------------------------------------------------
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
# Use Ansible copy in check_mode so it compares controller files (Git)
|
# TASK 1: Initialize drift tracking variables
|
||||||
# against live server files without actually writing anything.
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
# changed=true → file differs → drift
|
- name: Initialize drift tracking
|
||||||
# changed=false → files match → synced
|
set_fact:
|
||||||
# -------------------------------------------------------------------------
|
drift_detected: false
|
||||||
- name: Check main rsyslog.conf
|
drifted_items: []
|
||||||
ansible.builtin.copy:
|
|
||||||
src: "{{ playbook_dir }}/../../files/rsyslog.conf"
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
dest: "{{ rsyslog_main_config }}"
|
# TASK 2: Read local files from repository
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Read local files
|
||||||
|
slurp:
|
||||||
|
src: "{{ playbook_dir }}/{{ '../../' + item.src }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
loop: "{{ deploy_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: item
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
register: local_files
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 3: Read server files
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Read server files
|
||||||
|
slurp:
|
||||||
|
src: "{{ item.dest }}"
|
||||||
|
loop: "{{ deploy_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: item
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
register: server_files
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 4: Compare files and detect drift
|
||||||
|
# Builds list of drifted files by comparing local vs server
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Detect drift by comparing files
|
||||||
|
set_fact:
|
||||||
|
drifted_items: "{{ drifted_items | default([]) + [drift_item] }}"
|
||||||
|
vars:
|
||||||
|
local_result: "{{ local_files.results[item_index] }}"
|
||||||
|
server_result: "{{ server_files.results[item_index] }}"
|
||||||
|
item_index: "{{ loop_index0 }}"
|
||||||
|
drift_item: |
|
||||||
|
{%- if server_result.rc != 0 -%}
|
||||||
|
{
|
||||||
|
"name": "{{ item.name }}",
|
||||||
|
"destination": "{{ item.dest }}",
|
||||||
|
"status": "MISSING",
|
||||||
|
"reason": "File not found on server"
|
||||||
|
}
|
||||||
|
{%- elif local_result.content | b64decode != server_result.content | b64decode -%}
|
||||||
|
{
|
||||||
|
"name": "{{ item.name }}",
|
||||||
|
"destination": "{{ item.dest }}",
|
||||||
|
"status": "CONTENT_DIFFERS",
|
||||||
|
"reason": "File content differs from repository"
|
||||||
|
}
|
||||||
|
{%- endif -%}
|
||||||
|
loop: "{{ deploy_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: item
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 5: Update drift detection flag and filter results
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Set drift_detected flag
|
||||||
|
set_fact:
|
||||||
|
drifted_items: "{{ drifted_items | map('from_json') | selectattr('status', 'defined') | list }}"
|
||||||
|
drift_detected: "{{ (drifted_items | map('from_json') | selectattr('status', 'defined') | list | length) > 0 }}"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 6: Generate JSON report with drift details
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Generate drift detection JSON report
|
||||||
|
set_fact:
|
||||||
|
drifted_files_json: "{{ drifted_items | to_nice_json }}"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 7: Save drift report to file for script consumption
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
- name: Save drift report to file
|
||||||
|
copy:
|
||||||
|
content: "{{ drifted_files_json }}"
|
||||||
|
dest: "/tmp/drifted_files_{{ inventory_hostname }}.json"
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: '0644'
|
mode: "0644"
|
||||||
check_mode: true
|
delegate_to: localhost
|
||||||
diff: true
|
|
||||||
register: main_config_check
|
|
||||||
|
|
||||||
- name: Check rsyslog.d config files
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
ansible.builtin.copy:
|
# TASK 8: Output status summary
|
||||||
src: "{{ playbook_dir }}/../../files/rsyslog.d/"
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
dest: "{{ rsyslog_config_dir }}/"
|
- name: Output SYNCED status
|
||||||
owner: root
|
debug:
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
check_mode: true
|
|
||||||
diff: true
|
|
||||||
register: rsyslogd_check
|
|
||||||
|
|
||||||
- name: Check for extra files on server not present in Git
|
|
||||||
block:
|
|
||||||
- name: Find config files on server
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: "{{ rsyslog_config_dir }}"
|
|
||||||
patterns: "*.conf"
|
|
||||||
register: server_configs
|
|
||||||
|
|
||||||
- name: Find config files in Git (controller)
|
|
||||||
ansible.builtin.find:
|
|
||||||
paths: "{{ playbook_dir }}/../../files/rsyslog.d"
|
|
||||||
patterns: "*.conf"
|
|
||||||
delegate_to: localhost
|
|
||||||
register: repo_configs
|
|
||||||
|
|
||||||
- name: Build list of Git-managed filenames
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
git_filenames: "{{ repo_configs.files | map(attribute='path') | map('basename') | list }}"
|
|
||||||
|
|
||||||
- name: Build list of server filenames
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
server_filenames: "{{ server_configs.files | map(attribute='path') | map('basename') | list }}"
|
|
||||||
|
|
||||||
- name: Find server files that are managed by Git but missing on server
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
missing_on_server: "{{ git_filenames | difference(server_filenames) }}"
|
|
||||||
|
|
||||||
- name: Flag if any Git-managed file is missing from server
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
extra_files_on_server: true
|
|
||||||
when: missing_on_server | length > 0
|
|
||||||
|
|
||||||
- name: Show missing files
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "Files in Git but missing on server: {{ missing_on_server }}"
|
|
||||||
when: missing_on_server | length > 0
|
|
||||||
|
|
||||||
- name: Set overall drift flag
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
drift_detected: "{{ main_config_check.changed or rsyslogd_check.changed or (extra_files_on_server | default(false)) }}"
|
|
||||||
|
|
||||||
- name: Print SYNCED status
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: |
|
msg: |
|
||||||
╭─────────────────────────────╮
|
✓ All files are in sync
|
||||||
│ ✓ SYNCED │
|
Total files monitored: {{ deploy_items | length }}
|
||||||
│ Configuration is up-to-date │
|
|
||||||
╰─────────────────────────────╯
|
|
||||||
when: not drift_detected
|
when: not drift_detected
|
||||||
|
|
||||||
- name: Print OUT OF SYNC status
|
- name: Output OUT_OF_SYNC status with details
|
||||||
ansible.builtin.debug:
|
debug:
|
||||||
msg: |
|
msg: |
|
||||||
╭─────────────────────────────╮
|
✗ Configuration drift detected!
|
||||||
│ ✗ OUT OF SYNC │
|
Drifted files: {{ drifted_items | length }}
|
||||||
│ Configuration has drifted │
|
Details: {{ drifted_files_json }}
|
||||||
╰─────────────────────────────╯
|
|
||||||
when: drift_detected
|
when: drift_detected
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 9: Fail if drift detected (for CI/CD pipeline)
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
- name: Fail if drift detected
|
- name: Fail if drift detected
|
||||||
ansible.builtin.fail:
|
fail:
|
||||||
msg: "Configuration drift detected. Live system does not match repository."
|
msg: "Configuration drift detected. {{ drifted_items | length }} file(s) out of sync."
|
||||||
when: drift_detected
|
when: drift_detected
|
||||||
|
|
||||||
|
|||||||
@ -1,48 +1,49 @@
|
|||||||
---
|
---
|
||||||
- name: Validate rsyslog configuration
|
# =============================================================================
|
||||||
hosts: rsyslog_servers
|
# VALIDATE PLAYBOOK
|
||||||
gather_facts: false
|
# Purpose: Verify that all configured files exist and are readable on servers
|
||||||
|
# Usage: ansible-playbook validate.yml
|
||||||
|
# Output: Success if all files exist and readable, Failure if any are missing
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
vars:
|
- name: Validate deployed files
|
||||||
validate_dir: /tmp/rsyslog-validate
|
hosts: all
|
||||||
validate_conf: /tmp/rsyslog-validate/rsyslog.conf
|
gather_facts: false
|
||||||
|
vars_files:
|
||||||
|
- ../deploy-config.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Create temp validation directory
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
file:
|
# TASK 1: Check each configured file status
|
||||||
path: "{{ validate_dir }}/rsyslog.d"
|
# Gathers file stats (exists, readable, permissions, etc.)
|
||||||
state: directory
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
mode: "0700"
|
- name: Check if file is readable ({{ item.name }})
|
||||||
|
stat:
|
||||||
|
path: "{{ item.dest }}"
|
||||||
|
register: file_stats
|
||||||
|
loop: "{{ deploy_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: item
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
- name: Copy main config to temp location
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
copy:
|
# TASK 2: Verify each file exists and is readable
|
||||||
src: "{{ repo_root }}/files/rsyslog.conf"
|
# Fails if any configured file is missing or not readable
|
||||||
dest: "{{ validate_conf }}"
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
remote_src: true
|
- name: Verify all files exist and are readable
|
||||||
|
assert:
|
||||||
- name: Copy rsyslog.d configs to temp location
|
that:
|
||||||
copy:
|
- item.stat.exists
|
||||||
src: "{{ repo_root }}/files/rsyslog.d/"
|
- item.stat.readable
|
||||||
dest: "{{ validate_dir }}/rsyslog.d/"
|
msg: "{{ item.item.name }} ({{ item.item.dest }}) is missing or not readable"
|
||||||
remote_src: true
|
loop: "{{ file_stats.results }}"
|
||||||
|
loop_control:
|
||||||
- name: Update IncludeConfig path to point to temp dir
|
label: "{{ item.item.name }}"
|
||||||
replace:
|
|
||||||
path: "{{ validate_conf }}"
|
|
||||||
regexp: '^\$IncludeConfig\s+.*$'
|
|
||||||
replace: '$IncludeConfig {{ validate_dir }}/rsyslog.d/*.conf'
|
|
||||||
|
|
||||||
- name: Validate repo rsyslog configuration
|
|
||||||
command: rsyslogd -N1 -f "{{ validate_conf }}"
|
|
||||||
register: validate_result
|
|
||||||
failed_when: validate_result.rc != 0
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# TASK 3: Display validation result
|
||||||
|
# Shows success message if all files passed validation
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
- name: Display validation result
|
- name: Display validation result
|
||||||
debug:
|
debug:
|
||||||
msg: "✓ rsyslog configuration is valid"
|
msg: "✓ All {{ deploy_items | length }} file(s) are valid and readable on {{ inventory_hostname }}"
|
||||||
|
|
||||||
- name: Clean up temp validation directory
|
|
||||||
file:
|
|
||||||
path: "{{ validate_dir }}"
|
|
||||||
state: absent
|
|
||||||
|
|||||||
16
apply.sh
16
apply.sh
@ -1,16 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Applying rsyslog config from git repo..."
|
|
||||||
|
|
||||||
cp rsyslog.conf /etc/rsyslog.conf
|
|
||||||
mkdir -p /etc/rsyslog.d
|
|
||||||
cp rsyslog.d/*.conf /etc/rsyslog.d/
|
|
||||||
|
|
||||||
echo "Validating config..."
|
|
||||||
rsyslogd -N1
|
|
||||||
|
|
||||||
echo "Restarting rsyslog..."
|
|
||||||
systemctl restart rsyslog
|
|
||||||
|
|
||||||
echo "Done."
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Checking drift between git repo and live server..."
|
|
||||||
|
|
||||||
DIFF_FOUND=0
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Comparing /etc/rsyslog.conf"
|
|
||||||
if ! diff -u rsyslog.conf /etc/rsyslog.conf; then
|
|
||||||
DIFF_FOUND=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Comparing rsyslog.d configs"
|
|
||||||
for file in rsyslog.d/*.conf; do
|
|
||||||
base=$(basename "$file")
|
|
||||||
target="/etc/rsyslog.d/$base"
|
|
||||||
|
|
||||||
if [ ! -f "$target" ]; then
|
|
||||||
echo "Missing on server: $target"
|
|
||||||
DIFF_FOUND=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! diff -u "$file" "$target"; then
|
|
||||||
DIFF_FOUND=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$DIFF_FOUND" -eq 0 ]; then
|
|
||||||
echo
|
|
||||||
echo "No drift detected."
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo
|
|
||||||
echo "Drift detected!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
4
files/dvir.txt
Normal file
4
files/dvir.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
This is a generic deployment file.
|
||||||
|
It can be used for any service or configuration.
|
||||||
|
Simply replace this with your own content as needed.
|
||||||
|
TESTTT
|
||||||
@ -1,60 +0,0 @@
|
|||||||
# /etc/rsyslog.conf configuration file for rsyslog
|
|
||||||
#
|
|
||||||
# For more information install rsyslog-doc and see
|
|
||||||
# /usr/share/doc/rsyslog-doc/html/configuration/index.html
|
|
||||||
#
|
|
||||||
# Default logging rules can be found in /etc/rsyslog.d/50-default.conf
|
|
||||||
|
|
||||||
# test branch
|
|
||||||
#################
|
|
||||||
#### MODULES ####
|
|
||||||
#################
|
|
||||||
|
|
||||||
module(load="imuxsock") # provides support for local system logging
|
|
||||||
#module(load="immark") # provides --MARK-- message capability
|
|
||||||
|
|
||||||
# provides UDP syslog reception
|
|
||||||
#module(load="imudp")
|
|
||||||
#input(type="imudp" port="514")
|
|
||||||
|
|
||||||
# provides TCP syslog reception
|
|
||||||
#module(load="imtcp")
|
|
||||||
#input(type="imtcp" port="514")
|
|
||||||
|
|
||||||
# provides kernel logging support and enable non-kernel klog messages
|
|
||||||
module(load="imklog" permitnonkernelfacility="on")
|
|
||||||
|
|
||||||
###########################
|
|
||||||
#### GLOBAL DIRECTIVES ####
|
|
||||||
###########################
|
|
||||||
|
|
||||||
# Use traditional timestamp format.
|
|
||||||
# To enable high precision timestamps, comment out the following line.
|
|
||||||
|
|
||||||
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
|
|
||||||
|
|
||||||
# Filter duplicated messages
|
|
||||||
$RepeatedMsgReduction on
|
|
||||||
|
|
||||||
#
|
|
||||||
# Set the default permissions for all log files.
|
|
||||||
#
|
|
||||||
$FileOwner syslog
|
|
||||||
$FileGroup adm
|
|
||||||
$FileCreateMode 0640
|
|
||||||
$DirCreateMode 0755
|
|
||||||
$Umask 0022
|
|
||||||
$PrivDropToUser syslog
|
|
||||||
$PrivDropToGroup syslog
|
|
||||||
|
|
||||||
#
|
|
||||||
# Where to place spool and state files
|
|
||||||
#
|
|
||||||
$WorkDirectory /var/spool/rsyslog
|
|
||||||
|
|
||||||
#
|
|
||||||
# Include all config files in /etc/rsyslog.d/
|
|
||||||
#
|
|
||||||
$IncludeConfig /etc/rsyslog.d/*.conf
|
|
||||||
|
|
||||||
# Dvir was here
|
|
||||||
@ -1 +0,0 @@
|
|||||||
local0.* /var/log/lab.log
|
|
||||||
189
update-gitops-status.sh
Normal file
189
update-gitops-status.sh
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# GitOps Status Update Script
|
||||||
|
# Checks Ansible sync status and sends JSON update to gitops-status-server API
|
||||||
|
#
|
||||||
|
# This script:
|
||||||
|
# 1. Runs drift-check.yml to detect configuration drift
|
||||||
|
# 2. Extracts deployed files from deploy-config.yml
|
||||||
|
# 3. Reads structured JSON outputs from Ansible playbooks
|
||||||
|
# 4. Sends comprehensive status to gitops-status-server API
|
||||||
|
#
|
||||||
|
# Usage: ./update-gitops-status.sh
|
||||||
|
# Environment variables:
|
||||||
|
# - GITOPS_STATUS_SERVER_URL: API endpoint URL
|
||||||
|
# - REPO_NAME: Repository name
|
||||||
|
# - SERVER_NAME: Server name
|
||||||
|
# - MODE: post-deploy or cron (optional, for logging)
|
||||||
|
# - ANSIBLE_CONFIG: Path to ansible.cfg
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Get configuration from environment variables
|
||||||
|
API_URL="${GITOPS_STATUS_SERVER_URL}/api/status"
|
||||||
|
REPO_NAME="${REPO_NAME:-unknown}"
|
||||||
|
SERVER_NAME="${SERVER_NAME:-unknown}"
|
||||||
|
MODE="${MODE:-check}"
|
||||||
|
ANSIBLE_CONFIG="${ANSIBLE_CONFIG:-ansible.cfg}"
|
||||||
|
PLAYBOOK_DIR="ansible/playbooks"
|
||||||
|
INVENTORY="ansible/inventory/hosts.yml"
|
||||||
|
DEPLOY_CONFIG="ansible/deploy-config.yml"
|
||||||
|
|
||||||
|
echo "==> GitOps Status Update: $REPO_NAME / $SERVER_NAME"
|
||||||
|
echo " API URL: $API_URL"
|
||||||
|
echo " Mode: $MODE"
|
||||||
|
|
||||||
|
# Verify required environment variables
|
||||||
|
if [[ -z "$GITOPS_STATUS_SERVER_URL" || -z "$REPO_NAME" || -z "$SERVER_NAME" ]]; then
|
||||||
|
echo "ERROR: Missing required environment variables (GITOPS_STATUS_SERVER_URL, REPO_NAME, SERVER_NAME)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify files exist
|
||||||
|
if [[ ! -f "$INVENTORY" ]]; then
|
||||||
|
echo "ERROR: Inventory file not found: $INVENTORY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$DEPLOY_CONFIG" ]]; then
|
||||||
|
echo "ERROR: Deploy config not found: $DEPLOY_CONFIG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize variables
|
||||||
|
SYNC_STATUS="UNKNOWN"
|
||||||
|
DRIFT_COUNT=0
|
||||||
|
DEPLOYED_FILES="[]"
|
||||||
|
DRIFTED_FILES="[]"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Extract deployed files from deploy-config.yml
|
||||||
|
# Only extract active items (lines starting with " - name:"), not comments
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
echo "==> Extracting deployed files from deploy-config.yml..."
|
||||||
|
DEPLOYED_FILES=$(grep "^ - name:" "$DEPLOY_CONFIG" | \
|
||||||
|
sed 's/.*name: "\([^"]*\)".*/\1/' | \
|
||||||
|
jq -R -s 'split("\n") | map(select(length > 0) | {name: .})')
|
||||||
|
|
||||||
|
if [[ "$DEPLOYED_FILES" == "[]" ]] || [[ -z "$DEPLOYED_FILES" ]]; then
|
||||||
|
echo " WARNING: Could not extract deployed files, using empty array"
|
||||||
|
DEPLOYED_FILES="[]"
|
||||||
|
else
|
||||||
|
echo " ✓ Found deployed files: $(echo "$DEPLOYED_FILES" | jq '.[] | .name' | wc -l) file(s)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Run drift-check playbook to detect configuration drift
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
echo "==> Running drift-check playbook..."
|
||||||
|
ANSIBLE_OUTPUT=$(mktemp)
|
||||||
|
DRIFT_CHECK_SUCCESS=true
|
||||||
|
|
||||||
|
# Run drift-check and capture output
|
||||||
|
if ansible-playbook -i "$INVENTORY" \
|
||||||
|
-c local \
|
||||||
|
"$PLAYBOOK_DIR/drift-check.yml" \
|
||||||
|
> "$ANSIBLE_OUTPUT" 2>&1; then
|
||||||
|
|
||||||
|
SYNC_STATUS="SYNCED"
|
||||||
|
DRIFT_COUNT=0
|
||||||
|
DRIFTED_FILES="[]"
|
||||||
|
echo " ✓ All servers are SYNCED with Git"
|
||||||
|
else
|
||||||
|
# Drift detected
|
||||||
|
DRIFT_CHECK_SUCCESS=false
|
||||||
|
SYNC_STATUS="OUT_OF_SYNC"
|
||||||
|
|
||||||
|
echo " ✗ Configuration drift detected on one or more servers"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Extract drift information from JSON output files
|
||||||
|
# The drift-check.yml playbook creates /tmp/drifted_files_<hostname>.json
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
echo "==> Extracting drift information from playbook output..."
|
||||||
|
TEMP_DRIFTED_FILES=$(mktemp)
|
||||||
|
echo "[]" > "$TEMP_DRIFTED_FILES"
|
||||||
|
|
||||||
|
# Collect drifted files from all servers
|
||||||
|
for drift_file in /tmp/drifted_files_*.json; do
|
||||||
|
if [[ -f "$drift_file" ]]; then
|
||||||
|
echo " Reading drift report: $drift_file"
|
||||||
|
cat "$drift_file" >> "$TEMP_DRIFTED_FILES"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If we have drift files, use them; otherwise try to extract from Ansible output
|
||||||
|
if [[ -f "$TEMP_DRIFTED_FILES" ]] && [[ -s "$TEMP_DRIFTED_FILES" ]] && [[ "$(cat "$TEMP_DRIFTED_FILES")" != "[]" ]]; then
|
||||||
|
# Merge all drifted files into a single array
|
||||||
|
DRIFTED_FILES=$(jq -s 'add' "$TEMP_DRIFTED_FILES" 2>/dev/null || echo "[]")
|
||||||
|
DRIFT_COUNT=$(echo "$DRIFTED_FILES" | jq '. | length')
|
||||||
|
echo " ✓ Found $DRIFT_COUNT drifted file(s)"
|
||||||
|
else
|
||||||
|
# Fallback: parse Ansible output for drift indicators
|
||||||
|
echo " Analyzing Ansible output for drift indicators..."
|
||||||
|
DRIFT_INDICATORS=$(grep -E "CHANGED|changed:|failed:|drifted|drift" "$ANSIBLE_OUTPUT" | wc -l || true)
|
||||||
|
|
||||||
|
if [[ $DRIFT_INDICATORS -gt 0 ]]; then
|
||||||
|
DRIFT_COUNT=$DRIFT_INDICATORS
|
||||||
|
# Build minimal drift entries from grep results
|
||||||
|
DRIFTED_FILES=$(grep -E "CHANGED|changed:|failed:" "$ANSIBLE_OUTPUT" | \
|
||||||
|
head -10 | \
|
||||||
|
jq -R -s 'split("\n") | map(select(length > 0) | {name: ., status: "UNKNOWN", reason: "Detected from Ansible output"})' || echo "[]")
|
||||||
|
echo " Found $DRIFT_COUNT drift indicator(s)"
|
||||||
|
else
|
||||||
|
DRIFTED_FILES="[]"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Get current timestamp in ISO 8601 format
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Build comprehensive JSON payload
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
JSON_PAYLOAD=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"repo": "$REPO_NAME",
|
||||||
|
"server": "$SERVER_NAME",
|
||||||
|
"sync_status": "$SYNC_STATUS",
|
||||||
|
"drift_count": $DRIFT_COUNT,
|
||||||
|
"deployed_files": $DEPLOYED_FILES,
|
||||||
|
"drifted_files": $DRIFTED_FILES,
|
||||||
|
"last_check": "$TIMESTAMP"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Sending status update to API..."
|
||||||
|
echo "$JSON_PAYLOAD" | jq .
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Send to API using curl
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
HTTP_CODE=$(curl -s -o /tmp/api_response.json -w "%{http_code}" \
|
||||||
|
-X POST "$API_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$JSON_PAYLOAD")
|
||||||
|
|
||||||
|
if [[ "$HTTP_CODE" == "200" ]]; then
|
||||||
|
echo " ✓ Status update sent successfully (HTTP $HTTP_CODE)"
|
||||||
|
cat /tmp/api_response.json | jq . 2>/dev/null || cat /tmp/api_response.json
|
||||||
|
else
|
||||||
|
echo " ✗ Failed to send status update (HTTP $HTTP_CODE)"
|
||||||
|
cat /tmp/api_response.json 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
# Cleanup temporary files
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
rm -f "$ANSIBLE_OUTPUT" /tmp/api_response.json "$TEMP_DRIFTED_FILES"
|
||||||
|
|
||||||
|
# Exit with success regardless of sync status
|
||||||
|
# (The script's job is to report status, not to fail on drift)
|
||||||
|
echo "==> GitOps status update complete"
|
||||||
|
exit 0
|
||||||
Loading…
x
Reference in New Issue
Block a user