From 38b8365a68fff8334bc3661ecb2d1e0003588c31 Mon Sep 17 00:00:00 2001 From: Jack Carter <128555021+SunsetDrifter@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:03:42 +0200 Subject: [PATCH] feat: add safe_apply_netbird playbook for backup-preview-apply workflow New playbook that wraps the export and configure roles into a safe IaC pipeline: 1. BACKUP - exports current live state to timestamped backups/ dir 2. PREVIEW - shows read-only diff of what config changes would do 3. APPLY - applies changes (gated behind -e apply=true) Safety features: - Preview-only by default (no changes without explicit opt-in) - Automatic backup before any apply (relative to config_dir, not playbook_dir, so backups don't land inside the collection tree) - Strict mode opt-in via -e use_strict=true - Preview step skippable with -e preview=false to halve API calls - Rollback by re-applying from a backup directory - Input validation for required variables Updated README Quick Start to recommend the safe workflow, and added api_url clarification note. --- README.md | 44 ++++++++-- playbooks/safe_apply_netbird.yml | 143 +++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 playbooks/safe_apply_netbird.yml diff --git a/README.md b/README.md index b17da18..947f08d 100644 --- a/README.md +++ b/README.md @@ -603,7 +603,9 @@ The `examples/` directory contains complete playbook examples: This collection includes roles and playbooks for managing your entire NetBird configuration as YAML files in Git — with preview/diff, strict mode, and automatic name-to-ID resolution. -### Quick Start +### Quick Start (Safe Workflow) + +The recommended workflow uses the `safe_apply_netbird` playbook, which automatically backs up the current state before making any changes: ```bash # 1. Export current state to YAML files @@ -611,18 +613,44 @@ ansible-playbook community.ansible_netbird.export_netbird_config \ -e "netbird_api_url=https://netbird.example.com" \ -e "netbird_api_token=your-token" -# 2. Preview changes (default — read-only, no modifications) -ansible-playbook community.ansible_netbird.configure_netbird \ - -e "config_dir=/tmp/netbird_config_export" \ +# 2. Preview changes (default — backup + read-only diff, no modifications) +ansible-playbook community.ansible_netbird.safe_apply_netbird \ + -e "config_dir=/path/to/your/config" \ -e "netbird_api_url=https://netbird.example.com" \ -e "netbird_api_token=your-token" -# 3. Apply changes -ansible-playbook community.ansible_netbird.configure_netbird \ - -e "config_dir=/tmp/netbird_config_export" \ +# 3. Apply changes (backup + preview + apply) +ansible-playbook community.ansible_netbird.safe_apply_netbird \ + -e "config_dir=/path/to/your/config" \ -e "netbird_api_url=https://netbird.example.com" \ -e "netbird_api_token=your-token" \ - -e "commit=true" + -e "apply=true" + +# 4. Apply with strict mode (also removes unmanaged resources) +ansible-playbook community.ansible_netbird.safe_apply_netbird \ + -e "config_dir=/path/to/your/config" \ + -e "netbird_api_url=https://netbird.example.com" \ + -e "netbird_api_token=your-token" \ + -e "apply=true" -e "use_strict=true" + +# 5. Rollback to a backup +ansible-playbook community.ansible_netbird.safe_apply_netbird \ + -e "config_dir=/path/to/backups/20260330T120000" \ + -e "netbird_api_url=https://netbird.example.com" \ + -e "netbird_api_token=your-token" \ + -e "apply=true" +``` + +The `safe_apply_netbird` playbook always creates a timestamped backup in `backups/` before doing anything. You can also use the individual playbooks directly: + +```bash +# Export only +ansible-playbook community.ansible_netbird.export_netbird_config \ + -e "netbird_api_url=..." -e "netbird_api_token=..." + +# Configure only (preview by default, add -e commit=true to apply) +ansible-playbook community.ansible_netbird.configure_netbird \ + -e "config_dir=..." -e "netbird_api_url=..." -e "netbird_api_token=..." ``` ### Using Roles Directly diff --git a/playbooks/safe_apply_netbird.yml b/playbooks/safe_apply_netbird.yml new file mode 100644 index 0000000..e230394 --- /dev/null +++ b/playbooks/safe_apply_netbird.yml @@ -0,0 +1,143 @@ +--- +# Playbook: Safe NetBird Configuration Pipeline +# +# Wraps the export and configure roles into a safe workflow: +# 1. BACKUP — exports current live state to a timestamped directory +# 2. PREVIEW — shows a read-only diff of what would change +# 3. APPLY — applies changes (only when explicitly requested) +# +# Preview (default — no changes): +# ansible-playbook community.ansible_netbird.safe_apply_netbird \ +# -e "config_dir=/path/to/netbird_config" \ +# -e "netbird_api_url=https://netbird.example.com" \ +# -e "netbird_api_token=your-token" +# +# Apply: +# ... -e "apply=true" +# +# Apply + strict (remove unmanaged resources): +# ... -e "apply=true" -e "use_strict=true" +# +# Rollback to a backup: +# ... -e "config_dir=/path/to/backups/20260330T120000" -e "apply=true" +# +# Optional variables: +# - target_hosts: Host or group to run on (default: localhost) +# - backup_dir: Override backup location (default: /../backups/) +# - apply: Set to true to apply changes (default: false) +# - use_strict: Set to true to remove unmanaged resources (default: false) + +- name: "NetBird Safe Apply — backup, preview, apply" + hosts: "{{ target_hosts | default('localhost') }}" + gather_facts: true + vars: + apply: false + use_strict: false + backup_dir: "{{ config_dir }}/../backups/{{ ansible_date_time.iso8601_basic_short }}" + + tasks: + # ================================================================== + # Validation + # ================================================================== + - name: Validate required variables + ansible.builtin.assert: + that: + - netbird_api_url is defined and netbird_api_url | length > 0 + - netbird_api_token is defined and netbird_api_token | length > 0 + - config_dir is defined and config_dir | length > 0 + fail_msg: >- + Missing required variables. Provide: + -e "netbird_api_url=https://..." -e "netbird_api_token=..." -e "config_dir=/path/to/config" + run_once: true + + - name: Validate config directory exists + ansible.builtin.stat: + path: "{{ config_dir }}" + register: _config_check + run_once: true + + - name: Fail if config directory missing + ansible.builtin.fail: + msg: "Config directory not found: {{ config_dir }}" + when: not _config_check.stat.exists + run_once: true + + - name: Show operation mode + ansible.builtin.debug: + msg: | + === NetBird Safe Apply === + API: {{ netbird_api_url }} + Config: {{ config_dir }} + Mode: {{ 'APPLY' if apply | bool else 'PREVIEW (read-only)' }}{{ ' + STRICT' if use_strict | bool else '' }} + Backup to: {{ backup_dir }} + run_once: true + + # ================================================================== + # Step 1: Backup current state + # ================================================================== + - name: "BACKUP | Export current live state" + ansible.builtin.include_role: + name: community.ansible_netbird.export + vars: + export_dir: "{{ backup_dir }}" + run_once: true + + - name: "BACKUP | Verify backup" + ansible.builtin.find: + paths: "{{ backup_dir }}" + recurse: true + patterns: "*.yml" + register: _backup_files + run_once: true + + - name: "BACKUP | Complete" + ansible.builtin.debug: + msg: "Backed up {{ _backup_files.matched }} files to {{ backup_dir }}" + run_once: true + + # ================================================================== + # Step 2: Preview (shows diff report before any changes) + # Note: In apply mode this adds extra API calls but ensures you + # always see what's about to change. Skip with -e preview=false + # if API call volume is a concern. + # ================================================================== + - name: "PREVIEW | Show what would change" + ansible.builtin.include_role: + name: community.ansible_netbird.configure + vars: + commit: false + strict: "{{ use_strict | bool }}" + when: preview | default(true) | bool + run_once: true + + # ================================================================== + # Step 3: Apply (only if apply=true) + # ================================================================== + - name: "APPLY | Apply configuration" + ansible.builtin.include_role: + name: community.ansible_netbird.configure + vars: + commit: true + strict: "{{ use_strict | bool }}" + when: apply | bool + run_once: true + + # ================================================================== + # Summary + # ================================================================== + - name: "DONE | Next steps" + ansible.builtin.debug: + msg: | + {% if apply | bool %} + === APPLY COMPLETE === + Changes applied to {{ netbird_api_url }}. + Backup at: {{ backup_dir }} + To rollback: re-run with -e "config_dir={{ backup_dir }}" -e "apply=true" + {% else %} + === PREVIEW COMPLETE (no changes made) === + Review the diff report above. + Backup at: {{ backup_dir }} + To apply: re-run with -e "apply=true" + To strict: re-run with -e "apply=true" -e "use_strict=true" + {% endif %} + run_once: true