Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e62da6e3e6 |
104
.woodpecker.yml
104
.woodpecker.yml
@ -8,15 +8,13 @@
|
||||
# so a drift-check here would always be OUT_OF_SYNC by
|
||||
# design and is meaningless as a failure signal.
|
||||
#
|
||||
# push (master) → syntax-check, validate, deploy, update-gitops-status
|
||||
# Deploys to the server, then verifies sync and sends
|
||||
# JSON status snapshot to gitops-status-server for Grafana.
|
||||
# push (master) → syntax-check, validate, deploy, update-sync-metric
|
||||
# Deploys to the server, then verifies sync and pushes metric.
|
||||
#
|
||||
# cron → gitops_sync_check (read-only drift check, no deploy)
|
||||
# Continuously verifies that the live server still matches
|
||||
# Git even when no push has happened. Detects manual edits
|
||||
# made directly on the server. Sends JSON status with
|
||||
# detailed file-level drift information to gitops-status-server.
|
||||
# made directly on the server.
|
||||
#
|
||||
# NOTE: Woodpecker does not support multiple YAML documents (---) in one file.
|
||||
# All pipelines must live in a single document with step-level filtering.
|
||||
@ -82,48 +80,44 @@ steps:
|
||||
event: push
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# update-gitops-status: Post-deploy sync check + JSON status update
|
||||
# update-sync-metric: Post-deploy sync check + Prometheus metric push
|
||||
# Runs on push to master only, after deploy succeeds.
|
||||
# Generates structured JSON with sync status, drift count, and changed files.
|
||||
# Sends JSON to gitops-status-server for Grafana visualization.
|
||||
# STATUS=1 means SYNCED, STATUS=0 means OUT_OF_SYNC.
|
||||
# ---------------------------------------------------------------------------
|
||||
update-gitops-status:
|
||||
update-sync-metric:
|
||||
image: alpine/ansible:latest
|
||||
depends_on: [deploy]
|
||||
environment:
|
||||
ANSIBLE_CONFIG: ansible.cfg
|
||||
SSH_PRIVATE_KEY:
|
||||
from_secret: SSH_PRIVATE_KEY
|
||||
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"
|
||||
PUSHGATEWAY_URL: http://pushgateway.observability-stack.svc.cluster.local:9091
|
||||
commands:
|
||||
- |
|
||||
# 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
|
||||
apk add --no-cache curl > /dev/null 2>&1
|
||||
mkdir -p ~/.ssh
|
||||
printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
echo "==> Running post-deploy GitOps status check..."
|
||||
echo "==> Verifying post-deploy sync status..."
|
||||
set +e
|
||||
ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml
|
||||
DRIFT_RC=$?
|
||||
set -e
|
||||
|
||||
# Make script executable and run it
|
||||
chmod +x update-gitops-status.sh
|
||||
./update-gitops-status.sh
|
||||
if [ "$DRIFT_RC" -eq 0 ]; then
|
||||
STATUS=1
|
||||
echo "==> SYNCED (1) – server configuration matches Git"
|
||||
else
|
||||
STATUS=0
|
||||
echo "==> OUT OF SYNC (0) – drift detected after deploy"
|
||||
fi
|
||||
|
||||
echo "==> JSON status update complete. Pipeline always succeeds."
|
||||
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:
|
||||
branch: master
|
||||
event: push
|
||||
@ -131,14 +125,13 @@ steps:
|
||||
# ---------------------------------------------------------------------------
|
||||
# gitops_sync_check: ArgoCD-style cron drift check – read-only, no deploy
|
||||
# Detects manual changes made directly on the server between pushes.
|
||||
# Generates structured JSON with sync status, drift count, and changed files.
|
||||
# Sends JSON to gitops-status-server for continuous GitOps monitoring.
|
||||
# Pipeline always succeeds - check Grafana dashboard for drift status.
|
||||
# STATUS=1 → SYNCED, STATUS=0 → OUT_OF_SYNC
|
||||
# Pipeline marked FAILED when drift found so it is visible in the UI.
|
||||
#
|
||||
# ─── Woodpecker Cron UI settings ──────────────────────────────────────────
|
||||
# Name: gitops_sync_check
|
||||
# Branch: master
|
||||
# Schedule: */1 * * * * (every 1 minute for testing, adjust as needed)
|
||||
# Schedule: */2 * * * *
|
||||
# ---------------------------------------------------------------------------
|
||||
gitops_sync_check:
|
||||
image: alpine/ansible:latest
|
||||
@ -146,35 +139,34 @@ steps:
|
||||
ANSIBLE_CONFIG: ansible.cfg
|
||||
SSH_PRIVATE_KEY:
|
||||
from_secret: SSH_PRIVATE_KEY
|
||||
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"
|
||||
PUSHGATEWAY_URL: http://pushgateway.observability-stack.svc.cluster.local:9091
|
||||
commands:
|
||||
- |
|
||||
# Increase file descriptor limit for Ansible (max safe value)
|
||||
ulimit -n 65536
|
||||
apk add --no-cache curl > /dev/null 2>&1
|
||||
|
||||
# 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
|
||||
printf '%s\n' "$${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
echo "==> [cron] Running continuous GitOps drift check..."
|
||||
echo "==> [cron] Running drift check against remote server..."
|
||||
set +e
|
||||
ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/drift-check.yml
|
||||
DRIFT_RC=$?
|
||||
set -e
|
||||
|
||||
# Make script executable and run it (always succeeds - just updates JSON)
|
||||
chmod +x update-gitops-status.sh
|
||||
./update-gitops-status.sh
|
||||
if [ "$DRIFT_RC" -eq 0 ]; then
|
||||
STATUS=1
|
||||
echo "==> STATUS: SYNCED (1) – server configuration matches Git"
|
||||
else
|
||||
STATUS=0
|
||||
echo "==> STATUS: OUT OF SYNC (0) – manual drift detected on server"
|
||||
fi
|
||||
|
||||
echo "==> Cron drift check complete. JSON status updated successfully."
|
||||
echo " Pipeline always succeeds. Check gitops-status-server for sync status."
|
||||
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:
|
||||
event: cron
|
||||
|
||||
348
README.md
348
README.md
@ -1,348 +0,0 @@
|
||||
# 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)
|
||||
@ -1,7 +1,17 @@
|
||||
---
|
||||
# Global variables for deployment
|
||||
# Global variables for rsyslog configuration management
|
||||
|
||||
# Ansible connection settings
|
||||
ansible_user: root
|
||||
ansible_ssh_private_key_file: "~/.ssh/id_rsa"
|
||||
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:
|
||||
children:
|
||||
servers:
|
||||
rsyslog_servers:
|
||||
hosts:
|
||||
server1:
|
||||
rsyslog-lab:
|
||||
ansible_host: 192.168.10.161
|
||||
# Add more servers here:
|
||||
# server2:
|
||||
# Future servers can be added here:
|
||||
# rsyslog-prod:
|
||||
# ansible_host: 192.168.10.162
|
||||
# server3:
|
||||
# rsyslog-backup:
|
||||
# ansible_host: 192.168.10.163
|
||||
|
||||
@ -1,31 +1,145 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# APPLY PLAYBOOK
|
||||
# Purpose: Deploy dvir.txt file to target servers at /tmp/dvir.txt
|
||||
# Usage: ansible-playbook apply.yml
|
||||
# =============================================================================
|
||||
|
||||
- name: Deploy file to servers
|
||||
hosts: all
|
||||
- name: Apply rsyslog configuration (safe staged deployment)
|
||||
hosts: rsyslog_servers
|
||||
become: true
|
||||
|
||||
vars:
|
||||
backup_dir: /var/backups/rsyslog-ansible
|
||||
backup_conf: "{{ backup_dir }}/rsyslog.conf.bak"
|
||||
backup_confd: "{{ backup_dir }}/rsyslog.d.bak"
|
||||
|
||||
tasks:
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 1: Copy file to destination
|
||||
# Copies the dvir.txt from the repo to /tmp/dvir.txt on target servers
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Copy file to destination
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# STAGE 1 — Backup current working configuration
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
- name: Ensure backup directory exists
|
||||
file:
|
||||
path: "{{ backup_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Backup current rsyslog.conf
|
||||
copy:
|
||||
src: ../../files/dvir.txt
|
||||
dest: /tmp/dvir.txt
|
||||
src: "{{ rsyslog_main_config }}"
|
||||
dest: "{{ backup_conf }}"
|
||||
remote_src: true
|
||||
mode: "0600"
|
||||
|
||||
- name: Remove stale rsyslog.d backup
|
||||
file:
|
||||
path: "{{ backup_confd }}"
|
||||
state: absent
|
||||
|
||||
- name: Backup current rsyslog.d directory
|
||||
copy:
|
||||
src: "{{ rsyslog_config_dir }}/"
|
||||
dest: "{{ backup_confd }}/"
|
||||
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
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 2: Confirm deployment success
|
||||
# Displays success message with the hostname for verification
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Confirm deployment
|
||||
- name: Copy new rsyslog.d configs from repo
|
||||
copy:
|
||||
src: ../../files/rsyslog.d/
|
||||
dest: "{{ rsyslog_config_dir }}/"
|
||||
owner: root
|
||||
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:
|
||||
msg: "✅ File deployed successfully to /tmp/dvir.txt on {{ inventory_hostname }}"
|
||||
msg: |
|
||||
##################################################
|
||||
❌ RSYSLOG VALIDATION FAILED
|
||||
##################################################
|
||||
|
||||
HOST : {{ inventory_hostname }} ({{ ansible_host }})
|
||||
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:
|
||||
msg: |
|
||||
##################################################
|
||||
✅ RSYSLOG CONFIGURATION DEPLOYED SUCCESSFULLY
|
||||
##################################################
|
||||
HOST : {{ inventory_hostname }} ({{ ansible_host }})
|
||||
STATUS : Validation passed. Service restarted.
|
||||
##################################################
|
||||
when: validation_result.rc == 0
|
||||
@ -1,76 +1,101 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# DRIFT-CHECK PLAYBOOK
|
||||
# Purpose: Compare file on repo vs server to detect if they're in sync
|
||||
# Usage: ansible-playbook drift-check.yml
|
||||
# Output: SYNCED or OUT_OF_SYNC status
|
||||
# =============================================================================
|
||||
|
||||
- name: Check file drift
|
||||
hosts: all
|
||||
- name: Check rsyslog configuration drift
|
||||
hosts: rsyslog_servers
|
||||
gather_facts: false
|
||||
|
||||
# NOTE: src paths below resolve relative to the Ansible controller (the
|
||||
# Woodpecker CI container), so they always reflect the latest Git commit –
|
||||
# NOT the server's local clone, which may be stale.
|
||||
|
||||
tasks:
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 1: Read local file from repo
|
||||
# Reads dvir.txt from the local repository using base64 encoding
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Read local file
|
||||
slurp:
|
||||
src: "{{ playbook_dir }}/../../files/dvir.txt"
|
||||
delegate_to: localhost
|
||||
register: local_file
|
||||
# -------------------------------------------------------------------------
|
||||
# Use Ansible copy in check_mode so it compares controller files (Git)
|
||||
# against live server files without actually writing anything.
|
||||
# changed=true → file differs → drift
|
||||
# changed=false → files match → synced
|
||||
# -------------------------------------------------------------------------
|
||||
- name: Check main rsyslog.conf
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../files/rsyslog.conf"
|
||||
dest: "{{ rsyslog_main_config }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
check_mode: true
|
||||
diff: true
|
||||
register: main_config_check
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 2: Read file from server
|
||||
# Attempts to read dvir.txt from /tmp on the target server
|
||||
# Failure is allowed here (we'll handle it in next task)
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Read server file
|
||||
slurp:
|
||||
src: /tmp/dvir.txt
|
||||
register: server_file
|
||||
failed_when: false
|
||||
- name: Check rsyslog.d config files
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../files/rsyslog.d/"
|
||||
dest: "{{ rsyslog_config_dir }}/"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
check_mode: true
|
||||
diff: true
|
||||
register: rsyslogd_check
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 3: Compare file contents (if server file exists)
|
||||
# Decodes base64 and compares content between repo and server
|
||||
# Sets drift_detected to true if content differs
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Compare file contents
|
||||
set_fact:
|
||||
drift_detected: "{{ (local_file.content | b64decode) != (server_file.content | b64decode) }}"
|
||||
when: server_file.rc == 0
|
||||
- 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
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 4: Mark as drift if server file is missing
|
||||
# If the server file doesn't exist, it's also considered drift
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Mark as drift if server file missing
|
||||
set_fact:
|
||||
drift_detected: true
|
||||
when: server_file.rc != 0
|
||||
- name: Find config files in Git (controller)
|
||||
ansible.builtin.find:
|
||||
paths: "{{ playbook_dir }}/../../files/rsyslog.d"
|
||||
patterns: "*.conf"
|
||||
delegate_to: localhost
|
||||
register: repo_configs
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 5: Output SYNCED status
|
||||
# Displayed when file on server matches repo file exactly
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Output SYNCED status
|
||||
debug:
|
||||
msg: "✓ dvir.txt is synced"
|
||||
- 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: |
|
||||
╭─────────────────────────────╮
|
||||
│ ✓ SYNCED │
|
||||
│ Configuration is up-to-date │
|
||||
╰─────────────────────────────╯
|
||||
when: not drift_detected
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 6: Output OUT_OF_SYNC status
|
||||
# Displayed when file on server differs from repo or is missing
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Output OUT_OF_SYNC status
|
||||
debug:
|
||||
msg: "✗ dvir.txt is out of sync"
|
||||
- name: Print OUT OF SYNC status
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
╭─────────────────────────────╮
|
||||
│ ✗ OUT OF SYNC │
|
||||
│ Configuration has drifted │
|
||||
╰─────────────────────────────╯
|
||||
when: drift_detected
|
||||
|
||||
- name: Fail if drift detected
|
||||
fail:
|
||||
msg: "Configuration drift detected."
|
||||
ansible.builtin.fail:
|
||||
msg: "Configuration drift detected. Live system does not match repository."
|
||||
when: drift_detected
|
||||
|
||||
|
||||
@ -1,41 +1,48 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# VALIDATE PLAYBOOK
|
||||
# Purpose: Verify that dvir.txt exists and is readable on target servers
|
||||
# Usage: ansible-playbook validate.yml
|
||||
# Output: Success if file exists and is readable, Failure if not
|
||||
# =============================================================================
|
||||
|
||||
- name: Validate file exists
|
||||
hosts: all
|
||||
- name: Validate rsyslog configuration
|
||||
hosts: rsyslog_servers
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
validate_dir: /tmp/rsyslog-validate
|
||||
validate_conf: /tmp/rsyslog-validate/rsyslog.conf
|
||||
|
||||
tasks:
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 1: Check file status
|
||||
# Gathers file stats (exists, readable, permissions, etc.)
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Check if file is readable
|
||||
stat:
|
||||
path: /tmp/dvir.txt
|
||||
register: file_stat
|
||||
- name: Create temp validation directory
|
||||
file:
|
||||
path: "{{ validate_dir }}/rsyslog.d"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# TASK 2: Assert file requirements
|
||||
# Verifies that the file exists and is readable
|
||||
# Fails the playbook if either condition is false
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Verify file exists and is readable
|
||||
assert:
|
||||
that:
|
||||
- file_stat.stat.exists
|
||||
- file_stat.stat.readable
|
||||
msg: "dvir.txt not found or not readable"
|
||||
- name: Copy main config to temp location
|
||||
copy:
|
||||
src: "{{ repo_root }}/files/rsyslog.conf"
|
||||
dest: "{{ validate_conf }}"
|
||||
remote_src: true
|
||||
|
||||
- name: Copy rsyslog.d configs to temp location
|
||||
copy:
|
||||
src: "{{ repo_root }}/files/rsyslog.d/"
|
||||
dest: "{{ validate_dir }}/rsyslog.d/"
|
||||
remote_src: true
|
||||
|
||||
- name: Update IncludeConfig path to point to temp dir
|
||||
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 checks passed
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
- name: Display validation result
|
||||
debug:
|
||||
msg: "✓ dvir.txt is valid and readable"
|
||||
msg: "✓ rsyslog configuration is valid"
|
||||
|
||||
- name: Clean up temp validation directory
|
||||
file:
|
||||
path: "{{ validate_dir }}"
|
||||
state: absent
|
||||
|
||||
16
apply.sh
Executable file
16
apply.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/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."
|
||||
39
drift-check.sh
Executable file
39
drift-check.sh
Executable file
@ -0,0 +1,39 @@
|
||||
#!/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
|
||||
@ -1,3 +0,0 @@
|
||||
This is a generic deployment file.
|
||||
It can be used for any service or configuration.
|
||||
Simply replace this with your own content as needed.
|
||||
60
files/rsyslog.conf
Normal file
60
files/rsyslog.conf
Normal file
@ -0,0 +1,60 @@
|
||||
# /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
files/rsyslog.d/30-lab.conf
Normal file
1
files/rsyslog.d/30-lab.conf
Normal file
@ -0,0 +1 @@
|
||||
local0.* /var/log/lab.log
|
||||
Loading…
x
Reference in New Issue
Block a user