From 5a3f010afc87f37fd6fe9f049d478150a84cd020 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 19 Dec 2025 23:17:51 -0800 Subject: [PATCH 01/20] feat(vyos): migrate image build from Packer to vyos-build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Packer-based VyOS image build with the official vyos-build toolchain. This approach bakes the gateway configuration directly into the image via build flavors, eliminating the need for KVM/QEMU nested virtualization and brittle keystroke automation. New files: - vyos-build/build-flavors/gateway.toml: Build flavor with config - vyos-build/scripts/generate-flavor.sh: Injects SSH credentials - vyos-build/scripts/build.sh: Container build orchestration - .github/workflows/vyos-build.yml: New CI workflow The Packer workflow is deprecated but preserved for reference. Closes HOM-23 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/packer-vyos.yml | 51 +-- .github/workflows/vyos-build.yml | 150 ++++++++ .../appendices/A_repository_structure.md | 40 +- .../network/vyos/vyos-build/README.md | 87 +++++ .../vyos-build/build-flavors/gateway.toml | 345 ++++++++++++++++++ .../network/vyos/vyos-build/scripts/build.sh | 81 ++++ .../vyos-build/scripts/generate-flavor.sh | 68 ++++ 7 files changed, 780 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/vyos-build.yml create mode 100644 infrastructure/network/vyos/vyos-build/README.md create mode 100644 infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml create mode 100755 infrastructure/network/vyos/vyos-build/scripts/build.sh create mode 100755 infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh diff --git a/.github/workflows/packer-vyos.yml b/.github/workflows/packer-vyos.yml index 6b009f8..f54fe89 100644 --- a/.github/workflows/packer-vyos.yml +++ b/.github/workflows/packer-vyos.yml @@ -1,40 +1,41 @@ -# TODO: Migrate to vyos-build (https://github.com/vyos/vyos-build) for building -# VyOS images instead of Packer. The vyos-build approach provides better support -# for customization and doesn't require nested virtualization in CI. +# DEPRECATED: This Packer-based workflow has been replaced by vyos-build.yml # -# This workflow is currently disabled pending the migration. - -name: Build VyOS Image +# The new vyos-build approach: +# - Uses official vyos/vyos-build Docker container +# - Bakes configuration directly into the image (no keystroke automation) +# - Doesn't require KVM/QEMU nested virtualization +# - Produces identical raw disk output for Tinkerbell/NAS deployment +# +# See: .github/workflows/vyos-build.yml +# See: infrastructure/network/vyos/vyos-build/README.md +# +# This file is preserved for historical reference only. -# Disabled: triggers commented out pending vyos-build migration -# on: -# push: -# branches: [master] -# paths: -# - 'infrastructure/network/vyos/packer/**' -# pull_request: -# paths: -# - 'infrastructure/network/vyos/packer/**' -# workflow_dispatch: +name: "[DEPRECATED] Build VyOS Image (Packer)" on: workflow_dispatch: inputs: note: - description: 'Workflow disabled - see TODO comment at top of file' + description: 'This workflow is deprecated. Use vyos-build.yml instead.' type: string - default: 'disabled' - -# concurrency: -# group: packer-vyos-${{ github.ref }} -# cancel-in-progress: false + default: 'deprecated' jobs: - disabled: - if: false + deprecated: runs-on: ubuntu-latest steps: - - run: echo "Workflow disabled pending vyos-build migration" + - name: Workflow deprecated + run: | + echo "This Packer-based workflow has been replaced by vyos-build.yml" + echo "" + echo "The new workflow uses the official vyos-build toolchain which:" + echo " - Doesn't require KVM/QEMU nested virtualization" + echo " - Bakes configuration directly into the image" + echo " - Is more reliable than keystroke-based automation" + echo "" + echo "To build VyOS images, use the 'Build VyOS Image' workflow instead." + exit 1 # Historical reference - Packer-based build workflow: # diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml new file mode 100644 index 0000000..859a4e0 --- /dev/null +++ b/.github/workflows/vyos-build.yml @@ -0,0 +1,150 @@ +name: Build VyOS Image + +on: + push: + branches: [master] + paths: + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' + pull_request: + paths: + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' + workflow_dispatch: + inputs: + upload: + description: 'Upload image to e2 storage' + type: boolean + default: true + +concurrency: + group: vyos-build-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate flavor template + run: | + # Check that the template file exists and contains required placeholders + TEMPLATE="infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml" + + if [[ ! -f "${TEMPLATE}" ]]; then + echo "ERROR: Template file not found: ${TEMPLATE}" + exit 1 + fi + + if ! grep -q '%%SSH_KEY_TYPE%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_KEY_TYPE%% placeholder" + exit 1 + fi + + if ! grep -q '%%SSH_PUBLIC_KEY%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_PUBLIC_KEY%% placeholder" + exit 1 + fi + + echo "Template validation passed" + + - name: Check scripts are executable + run: | + for script in infrastructure/network/vyos/vyos-build/scripts/*.sh; do + if [[ ! -x "${script}" ]]; then + echo "ERROR: Script not executable: ${script}" + exit 1 + fi + echo "OK: ${script}" + done + + build: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: warp-ubuntu-latest-x64-8x + needs: validate + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: tools/labctl/go.sum + + - name: Build labctl + run: | + cd tools/labctl + go build -o ../../labctl . + + - name: Install SOPS + run: | + curl -LO https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 + chmod +x sops-v3.9.2.linux.amd64 + sudo mv sops-v3.9.2.linux.amd64 /usr/local/bin/sops + + - name: Write SOPS age key + run: | + echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/age-key.txt + chmod 600 /tmp/age-key.txt + + - name: Extract SSH public key + env: + SOPS_AGE_KEY_FILE: /tmp/age-key.txt + run: | + sops --decrypt \ + --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub + echo "SSH key extracted" + + - name: Clone vyos-build + run: | + git clone -b current --single-branch --depth 1 \ + https://github.com/vyos/vyos-build.git /tmp/vyos-build + + - name: Generate build flavor + run: | + ./infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh \ + "$(cat /tmp/ssh_key.pub)" \ + /tmp/vyos-build/data/build-flavors/gateway.toml + + - name: Build VyOS image + run: | + # Generate version string + VERSION="lab-$(date +%Y%m%d%H%M%S)" + + docker run --rm --privileged \ + -v /tmp/vyos-build:/vyos \ + -v /dev:/dev \ + -e VYOS_BUILD_BY="ci@lab.gilman.io" \ + -w /vyos \ + vyos/vyos-build:current \ + bash -c "sudo ./build-vyos-image --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" + + # Find and move the output image + find /tmp/vyos-build/build -name "*.raw" -exec cp {} /tmp/vyos-gateway.raw \; + + if [[ ! -f /tmp/vyos-gateway.raw ]]; then + echo "ERROR: Build failed - no raw image found" + ls -la /tmp/vyos-build/build/ || true + exit 1 + fi + + echo "Build complete: /tmp/vyos-gateway.raw" + ls -lah /tmp/vyos-gateway.raw + + - name: Upload to e2 + if: inputs.upload != false + run: | + ./labctl images upload \ + --credentials images/e2.sops.yaml \ + --sops-age-key-file /tmp/age-key.txt \ + --source /tmp/vyos-gateway.raw \ + --destination vyos/vyos-gateway.raw + + - name: Upload build artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: vyos-gateway-image + path: /tmp/vyos-gateway.raw + retention-days: 7 + if-no-files-found: warn diff --git a/docs/architecture/appendices/A_repository_structure.md b/docs/architecture/appendices/A_repository_structure.md index 60fb564..0733fba 100644 --- a/docs/architecture/appendices/A_repository_structure.md +++ b/docs/architecture/appendices/A_repository_structure.md @@ -28,6 +28,7 @@ lab/ ├── .github/ │ └── workflows/ │ ├── crossplane-build.yml # Build Crossplane packages on tag +│ ├── vyos-build.yml # Build VyOS image (vyos-build) │ ├── vyos-validate.yml # PR validation for VyOS │ └── vyos-deploy.yml # Deploy VyOS on merge │ @@ -41,13 +42,14 @@ lab/ │ │ └── vyos/ │ │ ├── configs/ │ │ │ └── gateway.conf -│ │ ├── packer/ -│ │ │ ├── vyos.pkr.hcl # Packer template for VyOS image -│ │ │ ├── variables.pkr.hcl # Packer variables -│ │ │ ├── http/ -│ │ │ │ └── preseed.cfg # VyOS preseed configuration +│ │ ├── vyos-build/ +│ │ │ ├── build-flavors/ +│ │ │ │ └── gateway.toml # Build flavor with baked-in config │ │ │ └── scripts/ -│ │ │ └── provision.sh # Post-install provisioning script +│ │ │ ├── generate-flavor.sh # Injects SSH credentials +│ │ │ └── build.sh # Runs inside vyos-build container +│ │ ├── packer/ # DEPRECATED - see vyos-build/ +│ │ │ └── ... │ │ └── ansible/ │ │ ├── playbooks/ │ │ │ └── deploy.yml @@ -564,18 +566,21 @@ infrastructure/ VyOS provides the lab's core networking: routing, firewall, DHCP, and VPN. -**Bootstrap Image (Packer):** +**Bootstrap Image (vyos-build):** - VyOS is provisioned via Tinkerbell during genesis bootstrap -- Packer builds a raw disk image with the initial configuration baked in -- Image includes: VLANs, DHCP relay, BGP peering config, and firewall rules +- The `vyos-build` toolchain builds a raw disk image with configuration baked in +- Image includes: VLANs, DHCP relay, BGP peering config, firewall rules, and SSH credentials - Built once during initial bootstrap; stored on NAS for Tinkerbell to serve -- Future configuration changes use the Ansible CI/CD pipeline (not Packer rebuild) +- Future configuration changes use the Ansible CI/CD pipeline (not image rebuild) -**Packer Build (`infrastructure/network/vyos/packer/`):** -- `vyos.pkr.hcl` - Packer template defining image build -- `variables.pkr.hcl` - Variables (VyOS version, output format, etc.) -- `http/preseed.cfg` - VyOS preseed for automated installation -- `scripts/provision.sh` - Applies initial config from `configs/gateway.conf` +**VyOS Build (`infrastructure/network/vyos/vyos-build/`):** +- `build-flavors/gateway.toml` - Build flavor defining config.boot content +- `scripts/generate-flavor.sh` - Injects SSH credentials from SOPS secrets +- `scripts/build.sh` - Orchestrates the build inside the vyos-build container + +**Legacy Packer Build (`infrastructure/network/vyos/packer/`):** +- DEPRECATED - replaced by vyos-build approach +- Uses keystroke automation which is brittle and requires KVM/QEMU **Ongoing Management:** - Configuration stored as declarative VyOS config file @@ -584,6 +589,7 @@ VyOS provides the lab's core networking: routing, firewall, DHCP, and VPN. - GitHub Action deploys config on merge to main **Workflow Files:** +- `.github/workflows/vyos-build.yml` - Builds VyOS image via vyos-build - `.github/workflows/vyos-validate.yml` - Validates VyOS config on PR - `.github/workflows/vyos-deploy.yml` - Deploys VyOS config on merge @@ -705,7 +711,7 @@ Step-by-step runbooks and scripts for bootstrapping the lab from scratch. **Runbooks (in order):** -1. `01-build-vyos-image.md` - Build VyOS image with Packer (bakes in initial config) +1. `01-build-vyos-image.md` - Build VyOS image with vyos-build (bakes in initial config) 2. `02-seed-cluster.md` - Create Talos VM on NAS 3. `03-deploy-argocd.md` - Install Argo CD manually via Helm 4. `04-apply-bootstrap.md` - Apply bootstrap Application pointing to `bootstrap/seed/` @@ -718,7 +724,7 @@ Step-by-step runbooks and scripts for bootstrapping the lab from scratch. **Scripts:** -- `build-vyos-image.sh` - Runs Packer to build VyOS raw disk image +- `build-vyos-image.sh` - Runs vyos-build to create VyOS raw disk image - `generate-talos-config.sh` - Runs talhelper to generate machine configs - `create-seed-vm.sh` - Creates Talos VM on NAS - `install-argocd.sh` - Installs Argo CD via Helm diff --git a/infrastructure/network/vyos/vyos-build/README.md b/infrastructure/network/vyos/vyos-build/README.md new file mode 100644 index 0000000..0b322d7 --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/README.md @@ -0,0 +1,87 @@ +# VyOS Gateway Image Build + +This directory contains the configuration and scripts for building custom VyOS gateway images using the official `vyos-build` toolchain. + +## Overview + +Instead of using Packer with QEMU keystroke automation (brittle and slow), this approach: + +1. Uses the official `vyos/vyos-build` Docker container +2. Bakes the gateway configuration directly into the image via build flavors +3. Produces a raw disk image suitable for Tinkerbell/NAS deployment +4. Injects SSH credentials from SOPS secrets at build time + +## Directory Structure + +``` +vyos-build/ +├── build-flavors/ +│ └── gateway.toml # Build flavor template with config.boot +├── scripts/ +│ ├── generate-flavor.sh # Injects SSH credentials into flavor +│ └── build.sh # Runs inside vyos-build container +└── README.md +``` + +## Build Process + +The GitHub Actions workflow (`.github/workflows/vyos-build.yml`) handles the full build: + +1. Decrypts SSH public key from `images/packer-ssh.sops.yaml` +2. Generates the final flavor TOML with credentials injected +3. Clones `vyos-build` repository +4. Runs the build in the `vyos/vyos-build:current` container +5. Uploads the resulting image to iDrive e2 + +### Local Build (for testing) + +```bash +# 1. Clone vyos-build +git clone -b current --single-branch https://github.com/vyos/vyos-build.git /tmp/vyos-build + +# 2. Generate flavor with SSH key +./scripts/generate-flavor.sh "ssh-ed25519 AAAA..." /tmp/vyos-build/data/build-flavors/gateway.toml + +# 3. Run build in container +docker run --rm -it --privileged \ + -v /tmp/vyos-build:/vyos \ + -v /dev:/dev \ + vyos/vyos-build:current bash + +# Inside container: +cd /vyos +sudo ./build-vyos-image --architecture amd64 --build-by "local@test" gateway + +# Output: /vyos/build/vyos-*.raw +``` + +## Configuration + +The `gateway.toml` flavor file contains: + +- **`image_format = "raw"`**: Output format for Tinkerbell deployment +- **`disk_size = 8`**: 8GB disk image +- **`default_config`**: Full VyOS configuration embedded in the image + +The configuration matches `infrastructure/network/vyos/configs/gateway.conf` with SSH credentials added via placeholders: +- `%%SSH_KEY_TYPE%%` - SSH key type (e.g., `ssh-ed25519`) +- `%%SSH_PUBLIC_KEY%%` - SSH public key body + +## Relationship to Other Files + +| File | Purpose | +|------|---------| +| `configs/gateway.conf` | Source of truth for VyOS config (Ansible applies updates) | +| `vyos-build/build-flavors/gateway.toml` | Build-time config with SSH credentials | +| `packer/` | Legacy build (deprecated) | + +## Migration from Packer + +The Packer-based build (`infrastructure/network/vyos/packer/`) is deprecated. Key differences: + +| Aspect | Packer | vyos-build | +|--------|--------|------------| +| Build time | ~10 minutes | ~5 minutes | +| Dependencies | QEMU + KVM | Docker only | +| Config injection | SSH provisioner | Baked in image | +| Reliability | Keystroke-dependent | Deterministic | diff --git a/infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml b/infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml new file mode 100644 index 0000000..f4dbdd3 --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml @@ -0,0 +1,345 @@ +# VyOS Gateway Build Flavor +# Produces a raw disk image with lab configuration baked in +# Target: VP6630 (Minisforum) - Lab Gateway Router +# +# Usage: +# This is a template file - use generate-flavor.sh to create the final TOML +# with SSH credentials injected from SOPS secrets. + +# Output format: raw disk image for Tinkerbell/NAS deployment +image_format = "raw" + +# Image settings +disk_size = 8 # GB + +# Include QEMU guest agent for VM environments +packages = ["qemu-guest-agent"] + +# Boot settings - serial console for headless operation +[boot_settings] +console_type = "serial" +serial_console = "ttyS0,115200n8" + +# Default configuration - will be merged with SSH key at build time +# This serves as the base config.boot content +# +# IMPORTANT: The SSH key placeholder %%SSH_KEY_TYPE%% and %%SSH_PUBLIC_KEY%% +# will be replaced by the build script with actual values from SOPS secrets. +# +# The configuration below is based on infrastructure/network/vyos/configs/gateway.conf +# converted to VyOS config.boot format (nested curly braces) +default_config = ''' +firewall { + group { + network-group HOME_NETWORK { + network 192.168.0.0/24 + } + network-group LAB_NETWORKS { + network 10.10.0.0/16 + } + network-group RFC1918 { + network 10.0.0.0/8 + network 172.16.0.0/12 + network 192.168.0.0/16 + } + } + interface eth4 { + in { + name WAN_TO_LAB + } + local { + name LOCAL + } + out { + name LAB_TO_WAN + } + } + ipv4 { + name LAB_TO_WAN { + default-action accept + rule 10 { + action accept + description "Allow established/related" + state { + established + related + } + } + rule 20 { + action drop + description "Block new connections to home network" + destination { + group { + network-group HOME_NETWORK + } + } + state { + new + } + } + } + name LOCAL { + default-action drop + rule 10 { + action accept + state { + established + related + } + } + rule 20 { + action accept + description "Allow ICMP" + protocol icmp + } + rule 30 { + action accept + description "Allow SSH from lab" + destination { + port 22 + } + protocol tcp + source { + group { + network-group LAB_NETWORKS + } + } + } + rule 31 { + action accept + description "Allow SSH from home" + destination { + port 22 + } + protocol tcp + source { + group { + network-group HOME_NETWORK + } + } + } + rule 40 { + action accept + description "Allow DNS from lab" + destination { + port 53 + } + protocol udp + source { + group { + network-group LAB_NETWORKS + } + } + } + rule 50 { + action accept + description "Allow DHCP from lab" + destination { + port 67 + } + protocol udp + source { + group { + network-group LAB_NETWORKS + } + } + } + rule 60 { + action accept + description "Allow BGP from lab" + destination { + port 179 + } + protocol tcp + source { + group { + network-group LAB_NETWORKS + } + } + } + } + name WAN_TO_LAB { + default-action drop + rule 10 { + action accept + description "Allow established/related" + state { + established + related + } + } + rule 20 { + action accept + description "Allow from home network" + source { + group { + network-group HOME_NETWORK + } + } + } + } + } +} +interfaces { + ethernet eth4 { + address 192.168.0.2/24 + description "WAN - Transit to Home (CCR2004)" + } + ethernet eth5 { + description "TRUNK - Lab Switch (CRS)" + vif 10 { + address 10.10.10.1/24 + description "LAB_MGMT - Infrastructure Management" + } + vif 20 { + address 10.10.20.1/24 + description "LAB_PROV - Provisioning (PXE)" + } + vif 30 { + address 10.10.30.1/24 + description "LAB_PLATFORM - Platform Cluster" + } + vif 40 { + address 10.10.40.1/24 + description "LAB_CLUSTER - Tenant Clusters" + } + vif 50 { + address 10.10.50.1/24 + description "LAB_SERVICE - Service VIPs (BGP)" + } + vif 60 { + address 10.10.60.1/24 + description "LAB_STORAGE - Storage Replication" + } + } +} +nat { + source { + rule 100 { + outbound-interface { + name eth4 + } + source { + address 10.10.0.0/16 + } + translation { + address masquerade + } + } + } +} +protocols { + bgp { + address-family { + ipv4-unicast { + network 10.10.50.0/24 { + } + } + } + neighbor 10.10.30.10 { + address-family { + ipv4-unicast { + } + } + description "platform-cp-1 (UM760)" + remote-as 64513 + shutdown + } + neighbor 10.10.30.11 { + address-family { + ipv4-unicast { + } + } + description "platform-cp-2" + remote-as 64513 + shutdown + } + neighbor 10.10.30.12 { + address-family { + ipv4-unicast { + } + } + description "platform-cp-3" + remote-as 64513 + shutdown + } + parameters { + bestpath { + as-path { + multipath-relax + } + } + router-id 10.10.50.1 + } + system-as 64512 + } + static { + route 0.0.0.0/0 { + next-hop 192.168.0.1 { + } + } + } +} +service { + dhcp-relay { + interface eth5.30 + interface eth5.40 + relay-options { + relay-agents-packets discard + } + server 10.10.20.10 + } + dhcp-server { + shared-network-name LAB_MGMT { + subnet 10.10.10.0/24 { + lease 86400 + option { + default-router 10.10.10.1 + name-server 10.10.10.1 + } + range 0 { + start 10.10.10.200 + stop 10.10.10.250 + } + subnet-id 10 + } + } + } + dns { + forwarding { + allow-from 10.10.0.0/16 + listen-address 10.10.10.1 + listen-address 10.10.20.1 + listen-address 10.10.30.1 + listen-address 10.10.40.1 + listen-address 10.10.50.1 + system + } + } + ssh { + disable-password-authentication + port 22 + } +} +system { + domain-name lab.gilman.io + host-name gateway + login { + user vyos { + authentication { + public-keys admin { + key "%%SSH_PUBLIC_KEY%%" + type %%SSH_KEY_TYPE%% + } + } + } + } + name-server 1.1.1.1 + name-server 8.8.8.8 + ntp { + server time.cloudflare.com { + } + } + time-zone America/Los_Angeles +} +''' diff --git a/infrastructure/network/vyos/vyos-build/scripts/build.sh b/infrastructure/network/vyos/vyos-build/scripts/build.sh new file mode 100755 index 0000000..72c84e5 --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/scripts/build.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# VyOS Gateway Image Build Script +# +# Builds a raw disk image using the vyos-build Docker container. +# This script is designed to run inside the vyos-build container. +# +# Usage (inside container): +# ./build.sh +# +# Environment variables (must be set before running): +# VYOS_BUILD_BY - Builder identifier (e.g., "ci@lab.gilman.io") +# VYOS_VERSION - Version string (optional, defaults to timestamp) +# +# Prerequisites: +# - Running inside vyos/vyos-build:current container +# - Flavor TOML with SSH credentials already generated at /vyos/build-flavors/gateway.toml +# - Privileged container with /dev access for raw image creation + +set -euo pipefail + +# Configuration +BUILD_BY="${VYOS_BUILD_BY:-ci@lab.gilman.io}" +VERSION="${VYOS_VERSION:-$(date +%Y%m%d%H%M%S)}" +FLAVOR_NAME="gateway" +OUTPUT_DIR="/vyos/build" + +echo "=== VyOS Gateway Image Build ===" +echo "Build By: ${BUILD_BY}" +echo "Version: ${VERSION}" +echo "Flavor: ${FLAVOR_NAME}" +echo "" + +# Verify we're in the right environment +if [[ ! -f "/vyos/build-vyos-image" ]]; then + echo "ERROR: build-vyos-image not found. Are you inside the vyos-build container?" + echo "" + echo "Run this script inside the container:" + echo " docker run --rm -it --privileged \\" + echo " -v \$(pwd):/vyos-lab \\" + echo " -v /dev:/dev \\" + echo " vyos/vyos-build:current bash" + exit 1 +fi + +# Verify flavor file exists +FLAVOR_FILE="/vyos/data/build-flavors/${FLAVOR_NAME}.toml" +if [[ ! -f "${FLAVOR_FILE}" ]]; then + echo "ERROR: Flavor file not found: ${FLAVOR_FILE}" + echo "Make sure to copy the generated flavor TOML to this location." + exit 1 +fi + +echo "Using flavor: ${FLAVOR_FILE}" +echo "" + +# Clean previous builds +echo "Cleaning previous builds..." +sudo make clean || true + +# Build the image +echo "" +echo "=== Starting VyOS Image Build ===" +echo "" + +sudo ./build-vyos-image \ + --architecture amd64 \ + --build-by "${BUILD_BY}" \ + --build-type release \ + --version "${VERSION}" \ + "${FLAVOR_NAME}" + +# Check for output +echo "" +echo "=== Build Complete ===" + +if [[ -d "${OUTPUT_DIR}" ]]; then + echo "Output files:" + ls -lah "${OUTPUT_DIR}/" +else + echo "WARNING: Output directory not found: ${OUTPUT_DIR}" +fi diff --git a/infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh b/infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh new file mode 100755 index 0000000..398429c --- /dev/null +++ b/infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Generate VyOS build flavor with SSH credentials from SOPS secrets +# +# Usage: +# ./generate-flavor.sh +# +# Arguments: +# ssh_public_key - Full SSH public key (e.g., "ssh-ed25519 AAAAC3Nz... comment") +# output_file - Path to write the generated flavor TOML +# +# Example: +# ./generate-flavor.sh "$(sops -d --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml)" gateway-final.toml + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMPLATE_FILE="${SCRIPT_DIR}/../build-flavors/gateway.toml" + +usage() { + echo "Usage: $0 " + echo "" + echo "Arguments:" + echo " ssh_public_key - Full SSH public key string" + echo " output_file - Path for generated flavor TOML" + exit 1 +} + +if [[ $# -ne 2 ]]; then + usage +fi + +SSH_PUBLIC_KEY="$1" +OUTPUT_FILE="$2" + +# Validate inputs +if [[ -z "${SSH_PUBLIC_KEY}" ]]; then + echo "ERROR: SSH public key is required" + exit 1 +fi + +if [[ ! -f "${TEMPLATE_FILE}" ]]; then + echo "ERROR: Template file not found: ${TEMPLATE_FILE}" + exit 1 +fi + +# Parse SSH public key: "type key comment" -> extract type and key +SSH_KEY_TYPE=$(echo "${SSH_PUBLIC_KEY}" | awk '{print $1}') +SSH_KEY_BODY=$(echo "${SSH_PUBLIC_KEY}" | awk '{print $2}') + +if [[ -z "${SSH_KEY_TYPE}" ]] || [[ -z "${SSH_KEY_BODY}" ]]; then + echo "ERROR: Could not parse SSH public key" + echo "Expected format: 'type key [comment]'" + echo "Got: '${SSH_PUBLIC_KEY}'" + exit 1 +fi + +echo "=== Generating VyOS Build Flavor ===" +echo "SSH Key Type: ${SSH_KEY_TYPE}" +echo "SSH Key Length: ${#SSH_KEY_BODY} characters" +echo "Output: ${OUTPUT_FILE}" + +# Generate the final flavor by replacing placeholders +sed \ + -e "s|%%SSH_KEY_TYPE%%|${SSH_KEY_TYPE}|g" \ + -e "s|%%SSH_PUBLIC_KEY%%|${SSH_KEY_BODY}|g" \ + "${TEMPLATE_FILE}" > "${OUTPUT_FILE}" + +echo "=== Flavor generated successfully ===" From af6fba505a8da5e6dca934aab0da2eb58d9ad905 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 19 Dec 2025 23:26:56 -0800 Subject: [PATCH 02/20] docs: update documentation to reference vyos-build instead of Packer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates all architecture documentation and bootstrap scripts to reflect the migration from Packer to the vyos-build toolchain for VyOS gateway image creation. - B_bootstrap_procedure.md: Updated prerequisites, workflow steps - 06_runtime_view.md: Updated VyOS image source reference - 07_deployment_view.md: Updated deployment method reference - 02_tinkerbell_provisioning.md: Updated provisioning description - 007_image_pipeline_s3_intermediary.md: Updated ADR context - build-vyos-image.sh: Rewrote to use Docker-based vyos-build 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- bootstrap/genesis/scripts/build-vyos-image.sh | 218 +++++++++--------- .../02_tinkerbell_provisioning.md | 2 +- docs/architecture/06_runtime_view.md | 2 +- docs/architecture/07_deployment_view.md | 2 +- .../007_image_pipeline_s3_intermediary.md | 6 +- .../appendices/B_bootstrap_procedure.md | 26 +-- 6 files changed, 125 insertions(+), 131 deletions(-) diff --git a/bootstrap/genesis/scripts/build-vyos-image.sh b/bootstrap/genesis/scripts/build-vyos-image.sh index ebd06fa..8a9624d 100755 --- a/bootstrap/genesis/scripts/build-vyos-image.sh +++ b/bootstrap/genesis/scripts/build-vyos-image.sh @@ -1,42 +1,37 @@ #!/usr/bin/env bash # Build VyOS Gateway Image -# Creates a raw disk image for the VP6630 gateway router +# Creates a raw disk image for the VP6630 gateway router using vyos-build # # Prerequisites: -# - Packer >= 1.9.0 -# - QEMU with KVM support -# - VyOS ISO (downloaded automatically or provided) +# - Docker +# - SSH public key # # Usage: # ./build-vyos-image.sh [options] # # Options: -# -i, --iso PATH Path to VyOS ISO (skips download) -# -o, --output DIR Output directory (default: output-vyos) +# -o, --output DIR Output directory (default: ./output-vyos) # -k, --ssh-key PATH SSH public key file (default: ~/.ssh/id_rsa.pub) +# -v, --version VER VyOS version string (default: timestamp) # -h, --help Show this help message # -# Network configuration is defined in: -# infrastructure/network/vyos/configs/gateway.conf +# Network configuration is embedded in the build flavor at: +# infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" -PACKER_DIR="${REPO_ROOT}/infrastructure/network/vyos/packer" +VYOS_BUILD_DIR="${REPO_ROOT}/infrastructure/network/vyos/vyos-build" # Defaults -VYOS_ISO="" -OUTPUT_DIR="${PACKER_DIR}/output-vyos" +OUTPUT_DIR="${SCRIPT_DIR}/output-vyos" SSH_KEY_FILE="${HOME}/.ssh/id_rsa.pub" - -# VyOS download settings -VYOS_VERSION="1.5-rolling-202412190007" -VYOS_URL="https://github.com/vyos/vyos-rolling-nightly-builds/releases/download/${VYOS_VERSION}/vyos-${VYOS_VERSION}-amd64.iso" -VYOS_CACHE_DIR="${HOME}/.cache/vyos" +VERSION="$(date +%Y%m%d%H%M%S)" +BUILD_BY="genesis@lab.gilman.io" usage() { - head -30 "$0" | grep -E '^#' | sed 's/^# \?//' + head -20 "$0" | grep -E '^#' | sed 's/^# \?//' exit 0 } @@ -52,17 +47,12 @@ error() { check_prerequisites() { log "Checking prerequisites..." - if ! command -v packer &>/dev/null; then - error "Packer not found. Install with: brew install packer" - fi - - if ! command -v qemu-system-x86_64 &>/dev/null; then - error "QEMU not found. Install with: brew install qemu" + if ! command -v docker &>/dev/null; then + error "Docker not found. Install Docker to continue." fi - # Check KVM availability (Linux only) - if [[ "$(uname)" == "Linux" ]] && [[ ! -r /dev/kvm ]]; then - error "KVM not available. Ensure virtualization is enabled and you have access to /dev/kvm" + if ! docker info &>/dev/null; then + error "Docker daemon not running or not accessible." fi # Check SSH key exists @@ -73,89 +63,85 @@ check_prerequisites() { log "Prerequisites satisfied" } -download_vyos_iso() { - if [[ -n "${VYOS_ISO}" ]]; then - if [[ ! -f "${VYOS_ISO}" ]]; then - error "Specified ISO not found: ${VYOS_ISO}" - fi - log "Using provided ISO: ${VYOS_ISO}" - return - fi - - mkdir -p "${VYOS_CACHE_DIR}" - VYOS_ISO="${VYOS_CACHE_DIR}/vyos-${VYOS_VERSION}-amd64.iso" - - if [[ -f "${VYOS_ISO}" ]]; then - log "Using cached ISO: ${VYOS_ISO}" - return - fi +generate_flavor() { + log "Generating build flavor with SSH credentials..." - log "Downloading VyOS ${VYOS_VERSION}..." - log "URL: ${VYOS_URL}" + # Extract SSH key components + SSH_KEY_TYPE=$(awk '{print $1}' "${SSH_KEY_FILE}") + SSH_KEY_BODY=$(awk '{print $2}' "${SSH_KEY_FILE}") - if ! curl -fSL -o "${VYOS_ISO}.tmp" "${VYOS_URL}"; then - rm -f "${VYOS_ISO}.tmp" - error "Failed to download VyOS ISO" + if [[ -z "${SSH_KEY_TYPE}" ]] || [[ -z "${SSH_KEY_BODY}" ]]; then + error "Invalid SSH public key format in ${SSH_KEY_FILE}" fi - mv "${VYOS_ISO}.tmp" "${VYOS_ISO}" - log "Downloaded: ${VYOS_ISO}" -} - -get_ssh_key_type() { - # Extract key type (first field: ssh-rsa, ssh-ed25519, etc.) - awk '{print $1}' "${SSH_KEY_FILE}" -} - -get_ssh_key_body() { - # Extract key body (second field: base64 encoded key) - awk '{print $2}' "${SSH_KEY_FILE}" -} - -run_packer_build() { - log "Starting Packer build..." - - cd "${PACKER_DIR}" + log " SSH Key Type: ${SSH_KEY_TYPE}" - # Initialize Packer plugins - log "Initializing Packer plugins..." - packer init . + # Create temp directory for build files + BUILD_TEMP=$(mktemp -d) + trap "rm -rf ${BUILD_TEMP}" EXIT - # Get SSH key type and body - SSH_KEY_TYPE=$(get_ssh_key_type) - SSH_KEY_BODY=$(get_ssh_key_body) + # Generate flavor from template + TEMPLATE_FILE="${VYOS_BUILD_DIR}/build-flavors/gateway.toml" + GENERATED_FLAVOR="${BUILD_TEMP}/gateway.toml" - # Calculate ISO checksum - log "Calculating ISO checksum..." - if command -v sha256sum &>/dev/null; then - ISO_CHECKSUM="sha256:$(sha256sum "${VYOS_ISO}" | awk '{print $1}')" - else - ISO_CHECKSUM="sha256:$(shasum -a 256 "${VYOS_ISO}" | awk '{print $1}')" + if [[ ! -f "${TEMPLATE_FILE}" ]]; then + error "Flavor template not found: ${TEMPLATE_FILE}" fi - # Determine accelerator based on platform - if [[ "$(uname)" == "Darwin" ]]; then - ACCELERATOR="hvf" - else - ACCELERATOR="kvm" - fi + sed -e "s|%%SSH_KEY_TYPE%%|${SSH_KEY_TYPE}|g" \ + -e "s|%%SSH_PUBLIC_KEY%%|${SSH_KEY_BODY}|g" \ + "${TEMPLATE_FILE}" > "${GENERATED_FLAVOR}" - log "Building VyOS image..." - log " ISO: ${VYOS_ISO}" + log "Generated flavor: ${GENERATED_FLAVOR}" +} + +run_vyos_build() { + log "Starting vyos-build..." + log " Version: ${VERSION}" + log " Build By: ${BUILD_BY}" log " Output: ${OUTPUT_DIR}" - log " SSH Key: ${SSH_KEY_FILE} (${SSH_KEY_TYPE})" - log " Accelerator: ${ACCELERATOR}" - - # Run Packer build - PACKER_LOG=1 packer build \ - -var "vyos_iso_url=file://${VYOS_ISO}" \ - -var "vyos_iso_checksum=${ISO_CHECKSUM}" \ - -var "output_directory=${OUTPUT_DIR}" \ - -var "ssh_key_type=${SSH_KEY_TYPE}" \ - -var "ssh_public_key=${SSH_KEY_BODY}" \ - . - - log "Packer build completed successfully!" + + mkdir -p "${OUTPUT_DIR}" + + # Pull the vyos-build container + log "Pulling vyos-build container..." + docker pull vyos/vyos-build:current + + # Run the build inside the container + # The container needs: + # - Privileged mode for raw disk image creation + # - /dev access for disk operations + # - Generated flavor file copied to build-flavors directory + log "Running VyOS image build..." + + docker run --rm --privileged \ + -v "${BUILD_TEMP}/gateway.toml:/vyos/data/build-flavors/gateway.toml:ro" \ + -v "${OUTPUT_DIR}:/output" \ + -v /dev:/dev \ + -e VYOS_BUILD_BY="${BUILD_BY}" \ + -e VYOS_VERSION="${VERSION}" \ + vyos/vyos-build:current \ + bash -c " + set -e + echo 'Building VyOS gateway image...' + cd /vyos + sudo ./build-vyos-image \ + --architecture amd64 \ + --build-by '${BUILD_BY}' \ + --build-type release \ + --version '${VERSION}' \ + gateway + + echo 'Copying output files...' + if [ -d /vyos/build ]; then + cp -v /vyos/build/*.raw /output/ 2>/dev/null || true + cp -v /vyos/build/*.qcow2 /output/ 2>/dev/null || true + fi + + echo 'Build complete!' + " + + log "vyos-build completed successfully!" } show_results() { @@ -164,27 +150,31 @@ show_results() { echo "VyOS Gateway Image Build Complete" echo "==============================================" echo "" - echo "Output image: ${OUTPUT_DIR}/vyos-lab.raw" + echo "Output directory: ${OUTPUT_DIR}" + if [[ -d "${OUTPUT_DIR}" ]]; then + echo "" + echo "Files:" + ls -lah "${OUTPUT_DIR}/" + fi echo "" echo "Next steps:" - echo " 1. Copy image to Tinkerbell NAS:" - echo " scp ${OUTPUT_DIR}/vyos-lab.raw nas:/volume1/images/vyos-lab.raw" + echo " 1. Upload image to e2 storage for Synology Cloud Sync:" + echo " labctl images upload ${OUTPUT_DIR}/vyos-*.raw" + echo "" + echo " 2. Or copy directly to NAS:" + echo " scp ${OUTPUT_DIR}/vyos-*.raw nas:/volume1/images/vyos/" echo "" - echo " 2. Or write directly to USB/SSD for manual install:" - echo " sudo dd if=${OUTPUT_DIR}/vyos-lab.raw of=/dev/sdX bs=4M status=progress" + echo " 3. Or write directly to USB/SSD for manual install:" + echo " sudo dd if=${OUTPUT_DIR}/vyos-*.raw of=/dev/sdX bs=4M status=progress" echo "" - echo "To update network configuration, edit:" - echo " infrastructure/network/vyos/configs/gateway.conf" + echo "Network configuration is embedded in the build flavor at:" + echo " infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml" echo "" } main() { while [[ $# -gt 0 ]]; do case $1 in - -i|--iso) - VYOS_ISO="$2" - shift 2 - ;; -o|--output) OUTPUT_DIR="$2" shift 2 @@ -193,6 +183,10 @@ main() { SSH_KEY_FILE="$2" shift 2 ;; + -v|--version) + VERSION="$2" + shift 2 + ;; -h|--help) usage ;; @@ -202,12 +196,12 @@ main() { esac done - log "VyOS Gateway Image Builder" + log "VyOS Gateway Image Builder (vyos-build)" log "Repository root: ${REPO_ROOT}" check_prerequisites - download_vyos_iso - run_packer_build + generate_flavor + run_vyos_build show_results } diff --git a/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md b/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md index c7ab8b3..9e955a5 100644 --- a/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md +++ b/docs/architecture/05_building_blocks/02_tinkerbell_provisioning.md @@ -9,7 +9,7 @@ Tinkerbell handles **Day Zero** operations — the initial bootstrap of physical | Target | What Tinkerbell Installs | Result | |:---|:---|:---| -| **VP6630** | VyOS (Packer-built image) | Lab router with VLANs and DHCP relay | +| **VP6630** | VyOS (vyos-build image) | Lab router with VLANs and DHCP relay | | **UM760** | Talos Linux | Node joins the Platform Cluster | | **MS-02 (x3)** | Harvester OS | Nodes join the Harvester HCI cluster | diff --git a/docs/architecture/06_runtime_view.md b/docs/architecture/06_runtime_view.md index 77ffdb3..d090591 100644 --- a/docs/architecture/06_runtime_view.md +++ b/docs/architecture/06_runtime_view.md @@ -10,7 +10,7 @@ The "Genesis" sequence bootstraps the entire infrastructure from bare metal to a ### Prerequisites - Physical hardware cabled and powered -- VyOS image built with Packer (baked-in configuration) +- VyOS image built with vyos-build (baked-in configuration) - Synology NAS available with Talos VM capability ### Sequence diff --git a/docs/architecture/07_deployment_view.md b/docs/architecture/07_deployment_view.md index a73b880..aceb089 100644 --- a/docs/architecture/07_deployment_view.md +++ b/docs/architecture/07_deployment_view.md @@ -79,7 +79,7 @@ This section describes the physical and virtual infrastructure topology — how | Node | Operating System | Deployment Method | |:---|:---|:---| -| **VP6630** | VyOS | Tinkerbell PXE (Packer-built image) | +| **VP6630** | VyOS | Tinkerbell PXE (vyos-build image) | | **MS-02 (x3)** | Harvester (Elemental OS) | Tinkerbell PXE | | **UM760** | Talos Linux | Tinkerbell PXE | | **Platform VMs (x2)** | Talos Linux | CAPI + Harvester | diff --git a/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md b/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md index 9dbcb74..e02397c 100644 --- a/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md +++ b/docs/architecture/09_design_decisions/007_image_pipeline_s3_intermediary.md @@ -7,8 +7,8 @@ The lab requires machine images (Talos, VyOS, Harvester) to be available on the Synology NAS for PXE provisioning via Tinkerbell. Images come from two sources: -1. **HTTP downloads** — Pre-built images from vendors (Talos Factory, Rancher, VyOS) -2. **Packer builds** — Custom images built from ISO + configuration (VyOS gateway) +1. **HTTP downloads** — Pre-built images from vendors (Talos Factory, Rancher) +2. **vyos-build** — Custom VyOS images built via Docker with configuration baked in (VyOS gateway) We need a GitOps-friendly pipeline to: - Declaratively define required images in Git @@ -61,7 +61,7 @@ Store images as GitHub Release assets. Script on NAS polls for new releases. │ │ 2. Parse images/images.yaml │ │ │ │ 3. For each image: │ │ │ │ - HTTP: Download → Verify → Decompress │ │ -│ │ - Packer: Build → Collect artifact │ │ +│ │ - vyos-build: Build in container → Collect artifact │ │ │ │ 4. Upload to iDrive e2 │ │ │ └──────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────┬──────────────────────────────────────┘ diff --git a/docs/architecture/appendices/B_bootstrap_procedure.md b/docs/architecture/appendices/B_bootstrap_procedure.md index 2891130..4267fa0 100644 --- a/docs/architecture/appendices/B_bootstrap_procedure.md +++ b/docs/architecture/appendices/B_bootstrap_procedure.md @@ -114,28 +114,28 @@ Phase 4: Full Platform (3-Node HA) **Purpose:** Create a bootable VyOS disk image with the initial lab configuration baked in. **Mechanism:** -- Run Packer against `infrastructure/network/vyos/packer/vyos.pkr.hcl` -- Packer downloads VyOS ISO, installs to virtual disk, applies initial config -- Configuration sourced from `infrastructure/network/vyos/configs/gateway.conf` -- Output: Raw disk image stored on NAS for Tinkerbell to serve +- Use the official `vyos-build` toolchain via Docker (`vyos/vyos-build:current`) +- Build flavor in `infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml` defines the configuration +- Configuration sourced from `infrastructure/network/vyos/configs/gateway.conf` (embedded in flavor) +- SSH credentials injected from SOPS secrets at build time +- Output: Raw disk image uploaded to iDrive e2, synced to NAS via Cloud Sync **Why Now:** - VyOS image must exist before Tinkerbell can serve it - Baking config into image avoids manual configuration during bootstrap -- Packer runs on admin workstation (not in cluster) +- Build runs in CI (GitHub Actions) or locally via Docker **Image Contents:** -- VyOS LTS release +- VyOS rolling release - Pre-configured VLANs (10, 20, 30, 40, 50, 60) - DHCP relay for VLANs 30 and 40 (points to Tinkerbell) - BGP peering configuration for service VIPs - Firewall rules for lab isolation - SSH keys for initial access -**Output:** +**Build Workflow:** ``` -infrastructure/network/vyos/packer/output/ -└── vyos-lab.raw # Raw disk image (~2GB) +.github/workflows/vyos-build.yml → vyos/vyos-build container → iDrive e2 → NAS Cloud Sync ``` ### Step 2: Generate Talos Configs @@ -746,7 +746,7 @@ spec: ┌─────────────────────────────────────────────────────────────────────────┐ │ PHASE 1: SEED (NAS) │ │ │ -│ Step 1: Build VyOS Image (Packer) │ +│ Step 1: Build VyOS Image (vyos-build) │ │ ↓ │ │ Step 2: Generate Talos Configs (talhelper) │ │ ↓ │ @@ -825,7 +825,7 @@ spec: | Phase | Step | Name | Duration | Purpose | |:------|:-----|:-----|:---------|:--------| -| 1 | 1 | Build VyOS Image | 10 min | Create VyOS disk image with Packer | +| 1 | 1 | Build VyOS Image | 10 min | Create VyOS disk image with vyos-build | | 1 | 2 | Generate Talos Configs | 2 min | Create machine configs for all platform nodes | | 1 | 3 | Create Seed Talos VM | 15 min | Bootstrap initial Kubernetes cluster on NAS | | 1 | 4 | Deploy Argo CD | 5 min | Install GitOps controller | @@ -875,7 +875,7 @@ Before beginning the bootstrap, ensure the following are in place: | 40 | 10.10.40.0/24 | Tenant clusters | DHCP (Tinkerbell) | | 60 | 10.10.60.0/24 | Storage replication | Static IPs | -**Note:** VyOS is provisioned via Tinkerbell during bootstrap (Step 7). The Packer-built image includes: +**Note:** VyOS is provisioned via Tinkerbell during bootstrap (Step 7). The vyos-build image includes: - VLANs configured and routing enabled - DHCP relay enabled for VLANs 30 and 40 (points to Tinkerbell) - DNS forwarding configured @@ -885,7 +885,7 @@ Before beginning the bootstrap, ensure the following are in place: | Tool | Version | Purpose | |:-----|:--------|:--------| -| Packer | v1.11.0+ | Build VyOS disk image | +| Docker | v24.0.0+ | Run vyos-build container for VyOS image | | talhelper | v3.0.0+ | Generate Talos machine configs | | SOPS | v3.9.0+ | Encrypt Talos secrets | | kubectl | v1.31.0+ | Kubernetes CLI | From 41a2bb9e7b40a3b02fc20fcda5998481bc36f71f Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 19 Dec 2025 23:30:10 -0800 Subject: [PATCH 03/20] docs: update remaining Packer references in Appendix A MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates runbook and script descriptions in the directory tree to reference vyos-build instead of Packer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/architecture/appendices/A_repository_structure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/appendices/A_repository_structure.md b/docs/architecture/appendices/A_repository_structure.md index 0733fba..20de813 100644 --- a/docs/architecture/appendices/A_repository_structure.md +++ b/docs/architecture/appendices/A_repository_structure.md @@ -209,7 +209,7 @@ lab/ │ ├── genesis/ # Runbooks and scripts │ ├── README.md # Overview and prerequisites - │ ├── 01-build-vyos-image.md # Build VyOS image with Packer + │ ├── 01-build-vyos-image.md # Build VyOS image with vyos-build │ ├── 02-seed-cluster.md # Create Talos VM on NAS │ ├── 03-deploy-argocd.md # Manual Argo CD install │ ├── 04-apply-bootstrap.md # Apply bootstrap Application @@ -220,7 +220,7 @@ lab/ │ ├── 09-provision-harvester.md # Tinkerbell provisions MS-02s │ ├── 10-expand-platform.md # Add CP-2, CP-3 VMs │ └── scripts/ - │ ├── build-vyos-image.sh # Runs Packer to build VyOS image + │ ├── build-vyos-image.sh # Runs vyos-build to create VyOS image │ ├── generate-talos-config.sh # Runs talhelper │ ├── create-seed-vm.sh # Creates Talos VM on NAS │ └── install-argocd.sh # Helm install Argo CD From a3dd4b3b32716766abaad4e46eabba5215b7b041 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 08:09:09 -0800 Subject: [PATCH 04/20] ci: temporarily enable build job on PRs for testing --- .github/workflows/vyos-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 859a4e0..33459b9 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -60,7 +60,8 @@ jobs: done build: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + # TODO: Remove pull_request after testing - temporary for functional validation + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' runs-on: warp-ubuntu-latest-x64-8x needs: validate steps: From 9147f9af95f2b81c7b6f08b4a0f390958f694f1f Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 08:18:08 -0800 Subject: [PATCH 05/20] fix(ci): handle permission errors when finding vyos raw image --- .github/workflows/vyos-build.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 33459b9..1548abe 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -120,15 +120,29 @@ jobs: vyos/vyos-build:current \ bash -c "sudo ./build-vyos-image --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" - # Find and move the output image - find /tmp/vyos-build/build -name "*.raw" -exec cp {} /tmp/vyos-gateway.raw \; + # Find and move the output image (suppress permission denied errors) + echo "Looking for .raw image in build output..." + ls -la /tmp/vyos-build/ || true - if [[ ! -f /tmp/vyos-gateway.raw ]]; then + # The raw image is created in the vyos-build root directory + RAW_FILE=$(find /tmp/vyos-build -maxdepth 1 -name "*.raw" -type f 2>/dev/null | head -1) + + if [[ -z "${RAW_FILE}" ]]; then + echo "No .raw file in root, checking build directory..." + RAW_FILE=$(find /tmp/vyos-build/build -name "*.raw" -type f 2>/dev/null | head -1) + fi + + if [[ -z "${RAW_FILE}" || ! -f "${RAW_FILE}" ]]; then echo "ERROR: Build failed - no raw image found" - ls -la /tmp/vyos-build/build/ || true + echo "Contents of /tmp/vyos-build:" + ls -la /tmp/vyos-build/ || true + echo "Contents of /tmp/vyos-build/build:" + ls -la /tmp/vyos-build/build/ 2>/dev/null || true exit 1 fi + echo "Found raw image: ${RAW_FILE}" + cp "${RAW_FILE}" /tmp/vyos-gateway.raw echo "Build complete: /tmp/vyos-gateway.raw" ls -lah /tmp/vyos-gateway.raw From cbe9f96728c6a1667aa306a4b117a20e765ddf85 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 08:25:42 -0800 Subject: [PATCH 06/20] fix(ci): improve vyos-build raw image discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix permission denied errors when finding .raw output file - Search in vyos-build root directory first (where build-vyos-image places it) - Add better debugging output for troubleshooting - Remove temporary PR trigger used for testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/vyos-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 1548abe..0805a54 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -60,8 +60,7 @@ jobs: done build: - # TODO: Remove pull_request after testing - temporary for functional validation - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: warp-ubuntu-latest-x64-8x needs: validate steps: From 2ac9c75e8c1a5255f0d82d48152e5b890b5e834a Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 08:36:05 -0800 Subject: [PATCH 07/20] fix(ci): always upload vyos image on push, respect input on dispatch --- .github/workflows/vyos-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 0805a54..c7242a5 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -146,7 +146,7 @@ jobs: ls -lah /tmp/vyos-gateway.raw - name: Upload to e2 - if: inputs.upload != false + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.upload) run: | ./labctl images upload \ --credentials images/e2.sops.yaml \ From 985b3ce229abbcdbee51fcece2b371e307979acb Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 08:40:17 -0800 Subject: [PATCH 08/20] chore: remove unused build.sh script --- .../appendices/A_repository_structure.md | 4 +- .../network/vyos/vyos-build/README.md | 3 +- .../network/vyos/vyos-build/scripts/build.sh | 81 ------------------- 3 files changed, 2 insertions(+), 86 deletions(-) delete mode 100755 infrastructure/network/vyos/vyos-build/scripts/build.sh diff --git a/docs/architecture/appendices/A_repository_structure.md b/docs/architecture/appendices/A_repository_structure.md index 20de813..13e3608 100644 --- a/docs/architecture/appendices/A_repository_structure.md +++ b/docs/architecture/appendices/A_repository_structure.md @@ -46,8 +46,7 @@ lab/ │ │ │ ├── build-flavors/ │ │ │ │ └── gateway.toml # Build flavor with baked-in config │ │ │ └── scripts/ -│ │ │ ├── generate-flavor.sh # Injects SSH credentials -│ │ │ └── build.sh # Runs inside vyos-build container +│ │ │ └── generate-flavor.sh # Injects SSH credentials │ │ ├── packer/ # DEPRECATED - see vyos-build/ │ │ │ └── ... │ │ └── ansible/ @@ -576,7 +575,6 @@ VyOS provides the lab's core networking: routing, firewall, DHCP, and VPN. **VyOS Build (`infrastructure/network/vyos/vyos-build/`):** - `build-flavors/gateway.toml` - Build flavor defining config.boot content - `scripts/generate-flavor.sh` - Injects SSH credentials from SOPS secrets -- `scripts/build.sh` - Orchestrates the build inside the vyos-build container **Legacy Packer Build (`infrastructure/network/vyos/packer/`):** - DEPRECATED - replaced by vyos-build approach diff --git a/infrastructure/network/vyos/vyos-build/README.md b/infrastructure/network/vyos/vyos-build/README.md index 0b322d7..94b33e7 100644 --- a/infrastructure/network/vyos/vyos-build/README.md +++ b/infrastructure/network/vyos/vyos-build/README.md @@ -18,8 +18,7 @@ vyos-build/ ├── build-flavors/ │ └── gateway.toml # Build flavor template with config.boot ├── scripts/ -│ ├── generate-flavor.sh # Injects SSH credentials into flavor -│ └── build.sh # Runs inside vyos-build container +│ └── generate-flavor.sh # Injects SSH credentials into flavor └── README.md ``` diff --git a/infrastructure/network/vyos/vyos-build/scripts/build.sh b/infrastructure/network/vyos/vyos-build/scripts/build.sh deleted file mode 100755 index 72c84e5..0000000 --- a/infrastructure/network/vyos/vyos-build/scripts/build.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash -# VyOS Gateway Image Build Script -# -# Builds a raw disk image using the vyos-build Docker container. -# This script is designed to run inside the vyos-build container. -# -# Usage (inside container): -# ./build.sh -# -# Environment variables (must be set before running): -# VYOS_BUILD_BY - Builder identifier (e.g., "ci@lab.gilman.io") -# VYOS_VERSION - Version string (optional, defaults to timestamp) -# -# Prerequisites: -# - Running inside vyos/vyos-build:current container -# - Flavor TOML with SSH credentials already generated at /vyos/build-flavors/gateway.toml -# - Privileged container with /dev access for raw image creation - -set -euo pipefail - -# Configuration -BUILD_BY="${VYOS_BUILD_BY:-ci@lab.gilman.io}" -VERSION="${VYOS_VERSION:-$(date +%Y%m%d%H%M%S)}" -FLAVOR_NAME="gateway" -OUTPUT_DIR="/vyos/build" - -echo "=== VyOS Gateway Image Build ===" -echo "Build By: ${BUILD_BY}" -echo "Version: ${VERSION}" -echo "Flavor: ${FLAVOR_NAME}" -echo "" - -# Verify we're in the right environment -if [[ ! -f "/vyos/build-vyos-image" ]]; then - echo "ERROR: build-vyos-image not found. Are you inside the vyos-build container?" - echo "" - echo "Run this script inside the container:" - echo " docker run --rm -it --privileged \\" - echo " -v \$(pwd):/vyos-lab \\" - echo " -v /dev:/dev \\" - echo " vyos/vyos-build:current bash" - exit 1 -fi - -# Verify flavor file exists -FLAVOR_FILE="/vyos/data/build-flavors/${FLAVOR_NAME}.toml" -if [[ ! -f "${FLAVOR_FILE}" ]]; then - echo "ERROR: Flavor file not found: ${FLAVOR_FILE}" - echo "Make sure to copy the generated flavor TOML to this location." - exit 1 -fi - -echo "Using flavor: ${FLAVOR_FILE}" -echo "" - -# Clean previous builds -echo "Cleaning previous builds..." -sudo make clean || true - -# Build the image -echo "" -echo "=== Starting VyOS Image Build ===" -echo "" - -sudo ./build-vyos-image \ - --architecture amd64 \ - --build-by "${BUILD_BY}" \ - --build-type release \ - --version "${VERSION}" \ - "${FLAVOR_NAME}" - -# Check for output -echo "" -echo "=== Build Complete ===" - -if [[ -d "${OUTPUT_DIR}" ]]; then - echo "Output files:" - ls -lah "${OUTPUT_DIR}/" -else - echo "WARNING: Output directory not found: ${OUTPUT_DIR}" -fi From 48ac6c1cdae964c39a25899a703e809d91535052 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 08:52:59 -0800 Subject: [PATCH 09/20] chore: remove deprecated Packer build code and references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove all Packer-based VyOS image build code now that vyos-build toolchain is fully implemented and tested. This includes deleting the packer directory, workflow, and updating all documentation references to reflect the new build approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/packer-vyos.yml | 139 ------------------ .../appendices/A_repository_structure.md | 6 - docs/design/image-pipeline.md | 139 +++++++++--------- images/images.yaml | 7 - .../network/vyos/configs/gateway.conf | 4 +- .../network/vyos/packer/scripts/provision.sh | 69 --------- .../vyos/packer/source.auto.pkrvars.hcl | 2 - .../network/vyos/packer/variables.pkr.hcl | 69 --------- .../network/vyos/packer/vyos.pkr.hcl | 136 ----------------- .../network/vyos/vyos-build/README.md | 14 +- tools/labctl/cmd/images/upload.go | 2 +- tools/labctl/internal/config/manifest_test.go | 2 +- tools/labctl/internal/store/s3_test.go | 2 +- tools/labctl/internal/updater/file_test.go | 2 +- 14 files changed, 77 insertions(+), 516 deletions(-) delete mode 100644 .github/workflows/packer-vyos.yml delete mode 100644 infrastructure/network/vyos/packer/scripts/provision.sh delete mode 100644 infrastructure/network/vyos/packer/source.auto.pkrvars.hcl delete mode 100644 infrastructure/network/vyos/packer/variables.pkr.hcl delete mode 100644 infrastructure/network/vyos/packer/vyos.pkr.hcl diff --git a/.github/workflows/packer-vyos.yml b/.github/workflows/packer-vyos.yml deleted file mode 100644 index f54fe89..0000000 --- a/.github/workflows/packer-vyos.yml +++ /dev/null @@ -1,139 +0,0 @@ -# DEPRECATED: This Packer-based workflow has been replaced by vyos-build.yml -# -# The new vyos-build approach: -# - Uses official vyos/vyos-build Docker container -# - Bakes configuration directly into the image (no keystroke automation) -# - Doesn't require KVM/QEMU nested virtualization -# - Produces identical raw disk output for Tinkerbell/NAS deployment -# -# See: .github/workflows/vyos-build.yml -# See: infrastructure/network/vyos/vyos-build/README.md -# -# This file is preserved for historical reference only. - -name: "[DEPRECATED] Build VyOS Image (Packer)" - -on: - workflow_dispatch: - inputs: - note: - description: 'This workflow is deprecated. Use vyos-build.yml instead.' - type: string - default: 'deprecated' - -jobs: - deprecated: - runs-on: ubuntu-latest - steps: - - name: Workflow deprecated - run: | - echo "This Packer-based workflow has been replaced by vyos-build.yml" - echo "" - echo "The new workflow uses the official vyos-build toolchain which:" - echo " - Doesn't require KVM/QEMU nested virtualization" - echo " - Bakes configuration directly into the image" - echo " - Is more reliable than keystroke-based automation" - echo "" - echo "To build VyOS images, use the 'Build VyOS Image' workflow instead." - exit 1 - -# Historical reference - Packer-based build workflow: -# -# validate: -# runs-on: warp-ubuntu-latest-x64-8x -# steps: -# - uses: actions/checkout@v4 -# -# - uses: hashicorp/setup-packer@v3.1.0 -# with: -# version: '1.11.2' -# -# - name: Packer Init -# working-directory: infrastructure/network/vyos/packer -# run: packer init . -# -# - name: Packer Validate -# working-directory: infrastructure/network/vyos/packer -# run: | -# # Validate with dummy values for required vars without defaults -# # Note: vyos_iso_url/checksum come from source.auto.pkrvars.hcl (auto-loaded) -# packer validate \ -# -var "ssh_key_type=ssh-ed25519" \ -# -var "ssh_public_key=AAAA" \ -# . -# -# build: -# if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' -# runs-on: warp-ubuntu-latest-x64-8x -# needs: validate -# steps: -# - uses: actions/checkout@v4 -# -# - uses: actions/setup-go@v5 -# with: -# go-version: '1.23' -# cache-dependency-path: tools/labctl/go.sum -# -# - name: Build labctl -# run: | -# cd tools/labctl -# go build -o ../../labctl . -# -# - name: Install SOPS -# run: | -# curl -LO https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 -# chmod +x sops-v3.9.2.linux.amd64 -# sudo mv sops-v3.9.2.linux.amd64 /usr/local/bin/sops -# -# - name: Write SOPS age key -# run: | -# echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/age-key.txt -# chmod 600 /tmp/age-key.txt -# -# - name: Extract SSH public key -# env: -# SOPS_AGE_KEY_FILE: /tmp/age-key.txt -# run: | -# sops --decrypt \ -# --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub -# -# - name: Install QEMU -# run: | -# sudo apt-get update -# sudo apt-get install -y qemu-system-x86 qemu-utils -# -# - name: Enable KVM access -# run: | -# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules -# sudo udevadm control --reload-rules -# sudo udevadm trigger --name-match=kvm -# sudo usermod -aG kvm $USER -# sudo chmod 666 /dev/kvm || true -# -# - uses: hashicorp/setup-packer@v3.1.0 -# with: -# version: '1.11.2' -# -# - name: Packer Init -# working-directory: infrastructure/network/vyos/packer -# run: packer init . -# -# - name: Packer Build -# working-directory: infrastructure/network/vyos/packer -# run: | -# # Extract key type and body from public key -# # Note: vyos_iso_url/checksum auto-loaded from source.auto.pkrvars.hcl -# SSH_KEY_TYPE=$(awk '{print $1}' /tmp/ssh_key.pub) -# SSH_KEY_BODY=$(awk '{print $2}' /tmp/ssh_key.pub) -# packer build \ -# -var "ssh_key_type=${SSH_KEY_TYPE}" \ -# -var "ssh_public_key=${SSH_KEY_BODY}" \ -# . -# -# - name: Upload to e2 -# run: | -# ./labctl images upload \ -# --credentials images/e2.sops.yaml \ -# --sops-age-key-file /tmp/age-key.txt \ -# --source infrastructure/network/vyos/packer/output/vyos-lab.raw \ -# --destination vyos/vyos-gateway.raw diff --git a/docs/architecture/appendices/A_repository_structure.md b/docs/architecture/appendices/A_repository_structure.md index 13e3608..4b9b606 100644 --- a/docs/architecture/appendices/A_repository_structure.md +++ b/docs/architecture/appendices/A_repository_structure.md @@ -47,8 +47,6 @@ lab/ │ │ │ │ └── gateway.toml # Build flavor with baked-in config │ │ │ └── scripts/ │ │ │ └── generate-flavor.sh # Injects SSH credentials -│ │ ├── packer/ # DEPRECATED - see vyos-build/ -│ │ │ └── ... │ │ └── ansible/ │ │ ├── playbooks/ │ │ │ └── deploy.yml @@ -576,10 +574,6 @@ VyOS provides the lab's core networking: routing, firewall, DHCP, and VPN. - `build-flavors/gateway.toml` - Build flavor defining config.boot content - `scripts/generate-flavor.sh` - Injects SSH credentials from SOPS secrets -**Legacy Packer Build (`infrastructure/network/vyos/packer/`):** -- DEPRECATED - replaced by vyos-build approach -- Uses keystroke automation which is brittle and requires KVM/QEMU - **Ongoing Management:** - Configuration stored as declarative VyOS config file - Deployed via Ansible playbook diff --git a/docs/design/image-pipeline.md b/docs/design/image-pipeline.md index 6bfabc2..8a71e55 100644 --- a/docs/design/image-pipeline.md +++ b/docs/design/image-pipeline.md @@ -5,13 +5,12 @@ * **Goal:** Create a GitOps-driven pipeline that manages source images (ISOs, raw, qcow2) and distributes them to the lab via NAS/NFS. * **Input:** Declarative YAML configuration defining image sources, validation rules, and optional file updates. * **Output:** Validated images in iDrive e2 (S3-compatible), synced to Synology NAS via Cloud Sync. -* **Key Constraint:** Downstream builds (Packer) are triggered via Git changes, not direct invocation. ## 2. Existing Context * **Language/Stack:** Go 1.23+, GitHub Actions, iDrive e2, Synology Cloud Sync, Mergify * **Relevant Files:** - * `infrastructure/network/vyos/packer/` - Existing Packer build (consumes source images) + * `infrastructure/network/vyos/vyos-build/` - VyOS image build using vyos-build toolchain * `docs/architecture/08_concepts/storage.md` - NFS storage architecture * **Style Guide:** * Configuration files use YAML @@ -40,19 +39,12 @@ spec: algorithm: sha256 expected: sha256:def456... # Post-decompression checksum - # Source image that triggers downstream build + # VyOS ISO for reference/manual builds - name: vyos-iso source: url: https://github.com/vyos/vyos-rolling-nightly-builds/releases/download/1.5-rolling-202412190007/vyos-1.5-rolling-202412190007-amd64.iso checksum: sha256:abc123... destination: vyos/vyos-1.5-rolling-202412190007.iso - updateFile: - path: infrastructure/network/vyos/packer/source.auto.pkrvars.hcl - replacements: - - pattern: 'vyos_iso_url\s*=\s*"[^"]*"' - value: 'vyos_iso_url = "{{ .Source.URL }}"' - - pattern: 'vyos_iso_checksum\s*=\s*"[^"]*"' - value: 'vyos_iso_checksum = "{{ .Source.Checksum }}"' # Harvester ISO (no transformation) - name: harvester-1.4.0 @@ -144,12 +136,12 @@ tools/ images/ ├── images.yaml # Image manifest ├── e2.sops.yaml # e2 credentials (SOPS encrypted) -├── packer-ssh.sops.yaml # Packer SSH keypair (SOPS encrypted) +├── packer-ssh.sops.yaml # SSH keypair for image builds (SOPS encrypted) └── .sops.yaml # SOPS config (age + PGP keys) .github/workflows/ ├── images-sync.yml # Source image pipeline -└── packer-vyos.yml # VyOS image build (triggered by file change) +└── vyos-build.yml # VyOS image build using vyos-build toolchain ``` ## 4. CLI Interface @@ -182,7 +174,7 @@ labctl images prune [flags] --dry-run Show what would be removed labctl images upload [flags] - Upload a local file to e2. Used by Packer workflows to upload built images. + Upload a local file to e2. Used by build workflows to upload built images. Computes SHA256 checksum and writes metadata JSON (same format as sync). --source PATH Path to local file to upload (required) @@ -242,9 +234,9 @@ echo "files_changed=true" >> "$GITHUB_OUTPUT" │ Derived Images │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ -│ 5. PACKER WORKFLOW (packer-vyos.yml) │ -│ └─> Triggered by changes to source.auto.pkrvars.hcl │ -│ ├─> packer init && packer build │ +│ 5. VYOS BUILD WORKFLOW (vyos-build.yml) │ +│ └─> Triggered by changes to vyos-build/ or configs/ │ +│ ├─> Run vyos-build in Docker container │ │ ├─> Upload built image to e2 │ │ └─> Cloud Sync pulls to NAS │ │ │ @@ -277,7 +269,7 @@ echo "files_changed=true" >> "$GITHUB_OUTPUT" } } -// For upload (local files, e.g., Packer output) +// For upload (local files, e.g., vyos-build output) { "name": "vyos-gateway", "checksum": "sha256:def456...", @@ -285,7 +277,7 @@ echo "files_changed=true" >> "$GITHUB_OUTPUT" "uploadedAt": "2024-12-20T12:00:00Z", "source": { "type": "local", - "path": "infrastructure/network/vyos/packer/output/vyos-lab.raw" + "path": "/tmp/vyos-gateway.raw" } } ``` @@ -299,7 +291,7 @@ lab-images/ │ │ └── talos-1.9.1-amd64.raw │ ├── vyos/ │ │ ├── vyos-1.5-rolling-202412190007.iso # Source ISO -│ │ └── vyos-gateway.raw # Built by Packer +│ │ └── vyos-gateway.raw # Built by vyos-build │ └── harvester/ │ └── harvester-1.4.0-amd64.iso └── metadata/ @@ -407,49 +399,54 @@ jobs: --sops-age-key-file /tmp/age-key.txt ``` -### 8.2 Packer Build (packer-vyos.yml) +### 8.2 VyOS Build (vyos-build.yml) ```yaml name: Build VyOS Image on: push: - branches: [main] + branches: [master] paths: - - 'infrastructure/network/vyos/packer/**' + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' pull_request: paths: - - 'infrastructure/network/vyos/packer/**' + - 'infrastructure/network/vyos/vyos-build/**' + - 'infrastructure/network/vyos/configs/gateway.conf' workflow_dispatch: + inputs: + upload: + description: 'Upload image to e2 storage' + type: boolean + default: true concurrency: - group: packer-vyos-${{ github.ref }} + group: vyos-build-${{ github.ref }} cancel-in-progress: false jobs: - # Validate on PRs (fast, no build) validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: hashicorp/setup-packer@v3.1.0 - with: - version: '1.11.2' - - - name: Packer Init - run: packer init infrastructure/network/vyos/packer - - - name: Packer Validate + - name: Validate flavor template run: | - # Validate with dummy values for required vars without defaults - # Note: vyos_iso_url/checksum come from source.auto.pkrvars.hcl (auto-loaded) - packer validate \ - -var "ssh_key_type=ssh-ed25519" \ - -var "ssh_public_key=AAAA" \ - infrastructure/network/vyos/packer - - # Build only on merge to main + TEMPLATE="infrastructure/network/vyos/vyos-build/build-flavors/gateway.toml" + if [[ ! -f "${TEMPLATE}" ]]; then + echo "ERROR: Template file not found" + exit 1 + fi + if ! grep -q '%%SSH_KEY_TYPE%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_KEY_TYPE%% placeholder" + exit 1 + fi + if ! grep -q '%%SSH_PUBLIC_KEY%%' "${TEMPLATE}"; then + echo "ERROR: Template missing %%SSH_PUBLIC_KEY%% placeholder" + exit 1 + fi + build: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest @@ -476,40 +473,49 @@ jobs: chmod 600 /tmp/age-key.txt - name: Extract SSH public key + env: + SOPS_AGE_KEY_FILE: /tmp/age-key.txt run: | - sops --decrypt --age-key-file /tmp/age-key.txt \ + sops --decrypt \ --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub - - uses: hashicorp/setup-packer@v3.1.0 - with: - version: '1.11.2' + - name: Clone vyos-build + run: | + git clone -b current --single-branch --depth 1 \ + https://github.com/vyos/vyos-build.git /tmp/vyos-build - - name: Packer Init - run: packer init infrastructure/network/vyos/packer + - name: Generate build flavor + run: | + ./infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh \ + "$(cat /tmp/ssh_key.pub)" \ + /tmp/vyos-build/data/build-flavors/gateway.toml - - name: Packer Build + - name: Build VyOS image run: | - # Extract key type and body from public key - # Note: vyos_iso_url/checksum auto-loaded from source.auto.pkrvars.hcl - SSH_KEY_TYPE=$(awk '{print $1}' /tmp/ssh_key.pub) - SSH_KEY_BODY=$(awk '{print $2}' /tmp/ssh_key.pub) - packer build \ - -var "ssh_key_type=${SSH_KEY_TYPE}" \ - -var "ssh_public_key=${SSH_KEY_BODY}" \ - infrastructure/network/vyos/packer + VERSION="lab-$(date +%Y%m%d%H%M%S)" + docker run --rm --privileged \ + -v /tmp/vyos-build:/vyos \ + -v /dev:/dev \ + -w /vyos \ + vyos/vyos-build:current \ + bash -c "sudo ./build-vyos-image --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" + + RAW_FILE=$(find /tmp/vyos-build -maxdepth 1 -name "*.raw" -type f 2>/dev/null | head -1) + cp "${RAW_FILE}" /tmp/vyos-gateway.raw - name: Upload to e2 + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.upload) run: | ./labctl images upload \ --credentials images/e2.sops.yaml \ --sops-age-key-file /tmp/age-key.txt \ - --source infrastructure/network/vyos/packer/output/vyos-lab.raw \ + --source /tmp/vyos-gateway.raw \ --destination vyos/vyos-gateway.raw ``` -**Packer Variable Loading:** Packer automatically loads `*.auto.pkrvars.hcl` files from the -template directory. The `source.auto.pkrvars.hcl` file (updated by `labctl images sync`) provides -`vyos_iso_url` and `vyos_iso_checksum` without explicit `-var-file` flags. +**VyOS Build Process:** The workflow uses the official `vyos/vyos-build` Docker container +with build flavors. The `gateway.toml` flavor embeds the VyOS configuration directly into +the image, with SSH credentials injected via placeholder replacement. ### 8.3 Mergify Configuration (.mergify.yml) @@ -533,11 +539,6 @@ pull_request_rules: **Check Name Format:** `Workflow Name / Job Name` -**Why `Build VyOS Image / validate`?** The bot PR from `updateFile` modifies -`infrastructure/.../source.auto.pkrvars.hcl`, which triggers `packer-vyos.yml` -(not `images-sync.yml`). Using the Packer validate check ensures the PR is -tested before auto-merge. - ## 9. Security ### SOPS-Encrypted Credentials @@ -558,15 +559,15 @@ sops: ### SOPS-Encrypted SSH Keypair -Used by Packer builds for VM provisioning. The public key is baked into the image; the private key is stored for future use (e.g., post-build testing). +Used by VyOS builds for image provisioning. The public key is baked into the image; the private key is stored for future use (e.g., post-build testing). ```bash -# Generate keypair -ssh-keygen -t ed25519 -f packer-ssh -N "" -C "packer-ci" +# Generate keypair (filename kept as packer-ssh for compatibility) +ssh-keygen -t ed25519 -f packer-ssh -N "" -C "vyos-ci" # Create SOPS file cat > images/packer-ssh.sops.yaml << 'EOF' -ssh_public_key: "ssh-ed25519 AAAA... packer-ci" +ssh_public_key: "ssh-ed25519 AAAA... vyos-ci" ssh_private_key: | -----BEGIN OPENSSH PRIVATE KEY----- ... diff --git a/images/images.yaml b/images/images.yaml index 6322f65..474d15b 100644 --- a/images/images.yaml +++ b/images/images.yaml @@ -11,10 +11,3 @@ spec: url: https://github.com/vyos/vyos-nightly-build/releases/download/2025.12.20-0020-rolling/vyos-2025.12.20-0020-rolling-generic-amd64.iso checksum: sha256:7f9eb1d6d9aacbd8fb684bb384cf2251d987097993fe7dbead8653ffbde31d04 destination: vyos/vyos-2025.12.20-0020-rolling-generic-amd64.iso - updateFile: - path: infrastructure/network/vyos/packer/source.auto.pkrvars.hcl - replacements: - - pattern: 'vyos_iso_url\s*=\s*"[^"]*"' - value: 'vyos_iso_url = "{{ .Source.URL }}"' - - pattern: 'vyos_iso_checksum\s*=\s*"[^"]*"' - value: 'vyos_iso_checksum = "{{ .Source.Checksum }}"' diff --git a/infrastructure/network/vyos/configs/gateway.conf b/infrastructure/network/vyos/configs/gateway.conf index 3b228d2..d49e7e5 100644 --- a/infrastructure/network/vyos/configs/gateway.conf +++ b/infrastructure/network/vyos/configs/gateway.conf @@ -318,9 +318,9 @@ service { system { domain-name lab.gilman.io host-name gateway - /* SSH keys managed separately via Ansible or Packer + /* SSH keys managed separately via Ansible or vyos-build * Do not commit real keys to this file - * Packer: provision.sh accepts SSH_KEY parameter + * vyos-build: baked into image via build-flavors/gateway.toml * Ansible: deploy.yml -e ssh_public_key_file=~/.ssh/id_rsa.pub */ name-server 1.1.1.1 diff --git a/infrastructure/network/vyos/packer/scripts/provision.sh b/infrastructure/network/vyos/packer/scripts/provision.sh deleted file mode 100644 index beedf4b..0000000 --- a/infrastructure/network/vyos/packer/scripts/provision.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/vbash -# VyOS Provisioning Script -# Loads gateway.conf and configures SSH key -# -# Arguments: -# $1 - SSH key type (e.g., ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256) -# $2 - SSH public key (base64 encoded key body) - -set -e - -SSH_KEY_TYPE="$1" -SSH_KEY="$2" - -# Config file location (copied by Packer) -CONFIG_FILE="/tmp/gateway.conf" - -# Source VyOS environment -source /opt/vyatta/etc/functions/script-template - -echo "=== VyOS Lab Gateway Provisioning ===" - -# ============================================================================= -# Validate Required Arguments -# ============================================================================= -if [ -z "${SSH_KEY_TYPE}" ] || [ -z "${SSH_KEY}" ]; then - echo "ERROR: SSH key type and key are required" - echo "Usage: provision.sh " - echo "Example: provision.sh ssh-ed25519 AAAAC3Nz..." - exit 1 -fi - -if [ ! -f "${CONFIG_FILE}" ]; then - echo "ERROR: Configuration file not found: ${CONFIG_FILE}" - exit 1 -fi - -echo "SSH Key Type: ${SSH_KEY_TYPE}" - -# ============================================================================= -# Load Configuration -# ============================================================================= -echo "Loading configuration from gateway.conf..." - -configure - -# Load the base configuration file (source of truth) -load "${CONFIG_FILE}" - -# ============================================================================= -# Configure SSH Key -# ============================================================================= -echo "Configuring SSH authentication..." - -set system login user vyos authentication public-keys admin type "${SSH_KEY_TYPE}" -set system login user vyos authentication public-keys admin key "${SSH_KEY}" - -# ============================================================================= -# Commit and Save -# ============================================================================= -echo "Committing configuration..." -commit - -echo "Saving configuration..." -save - -exit - -echo "" -echo "=== VyOS Lab Gateway Provisioning Complete ===" diff --git a/infrastructure/network/vyos/packer/source.auto.pkrvars.hcl b/infrastructure/network/vyos/packer/source.auto.pkrvars.hcl deleted file mode 100644 index 3e17c03..0000000 --- a/infrastructure/network/vyos/packer/source.auto.pkrvars.hcl +++ /dev/null @@ -1,2 +0,0 @@ -vyos_iso_url = "https://github.com/vyos/vyos-nightly-build/releases/download/2025.12.20-0020-rolling/vyos-2025.12.20-0020-rolling-generic-amd64.iso" -vyos_iso_checksum = "sha256:7f9eb1d6d9aacbd8fb684bb384cf2251d987097993fe7dbead8653ffbde31d04" diff --git a/infrastructure/network/vyos/packer/variables.pkr.hcl b/infrastructure/network/vyos/packer/variables.pkr.hcl deleted file mode 100644 index ccd6299..0000000 --- a/infrastructure/network/vyos/packer/variables.pkr.hcl +++ /dev/null @@ -1,69 +0,0 @@ -# VyOS Packer Variables -# Infrastructure: VP6630 Gateway Router -# -# Network configuration (interfaces, IPs, VLANs) is defined in: -# ../configs/gateway.conf -# -# If interface names need to change, update gateway.conf directly. - -variable "vyos_iso_url" { - type = string - description = "URL to VyOS ISO image" - default = "https://github.com/vyos/vyos-rolling-nightly-builds/releases/download/1.5-rolling-202412190007/vyos-1.5-rolling-202412190007-amd64.iso" -} - -variable "vyos_iso_checksum" { - type = string - description = "SHA256 checksum of VyOS ISO (format: sha256:HASH)" - # No default - must be provided via build script or command line - # Build script calculates: sha256:$(sha256sum vyos.iso | awk '{print $1}') -} - -variable "output_directory" { - type = string - description = "Directory for output image" - default = "output" -} - -variable "disk_size" { - type = string - description = "Disk size for VyOS image" - default = "8G" -} - -variable "memory" { - type = number - description = "Memory for build VM (MB)" - default = 2048 -} - -variable "cpus" { - type = number - description = "CPUs for build VM" - default = 2 -} - -# SSH Configuration (required) -variable "ssh_key_type" { - type = string - description = "SSH key type (e.g., ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256)" - # No default - must be provided via build script - - validation { - condition = length(var.ssh_key_type) > 0 - error_message = "SSH key type is required." - } -} - -variable "ssh_public_key" { - type = string - description = "SSH public key body (base64 encoded) for vyos user" - sensitive = true - # No default - must be provided via build script or PKR_VAR_ssh_public_key - # Build script extracts from: ~/.ssh/id_rsa.pub (or --ssh-key flag) - - validation { - condition = length(var.ssh_public_key) > 0 - error_message = "SSH public key is required. Set PKR_VAR_ssh_public_key or use build script with --ssh-key flag." - } -} diff --git a/infrastructure/network/vyos/packer/vyos.pkr.hcl b/infrastructure/network/vyos/packer/vyos.pkr.hcl deleted file mode 100644 index f30b508..0000000 --- a/infrastructure/network/vyos/packer/vyos.pkr.hcl +++ /dev/null @@ -1,136 +0,0 @@ -# VyOS Gateway Image Build -# Builds a raw disk image with lab configuration baked in -# Target: VP6630 (Minisforum) - Lab Gateway Router - -packer { - required_plugins { - qemu = { - version = ">= 1.1.0" - source = "github.com/hashicorp/qemu" - } - } -} - -source "qemu" "vyos" { - iso_url = var.vyos_iso_url - iso_checksum = var.vyos_iso_checksum - output_directory = var.output_directory - shutdown_command = "sudo poweroff" - disk_size = var.disk_size - format = "raw" - accelerator = "kvm" - memory = var.memory - cpus = var.cpus - net_device = "virtio-net" - disk_interface = "virtio" - - # VyOS boot configuration - boot_wait = "5s" - boot_command = [ - # Wait for live system to boot - "", - # Login as vyos user (default password: vyos) - "vyos", - "vyos", - # Run automated installation - "install image", - # Confirm disk selection - "", - # Confirm partition deletion - "Yes", - # Accept default root partition size - "", - # Image name - "", - # Copy running config - "", - # Set password for vyos user - "vyos", - "vyos", - # Installation completes - "", - # Reboot into installed system - "reboot", - # Login to installed system - "vyos", - "vyos", - # Enable SSH for provisioner - "configure", - "set service ssh port 22", - "set system login user vyos authentication plaintext-password vyos", - "commit", - "save", - "exit" - ] - - # SSH connection for provisioner - ssh_username = "vyos" - ssh_password = "vyos" - ssh_timeout = "30m" - ssh_port = 22 - - # VM configuration - vm_name = "vyos-lab" - headless = true - - # QEMU settings - qemuargs = [ - ["-m", "${var.memory}"], - ["-smp", "${var.cpus}"] - ] -} - -build { - name = "vyos-lab-gateway" - sources = ["source.qemu.vyos"] - - # Copy gateway configuration - provisioner "file" { - source = "../configs/gateway.conf" - destination = "/tmp/gateway.conf" - } - - # Copy provisioning script - provisioner "file" { - source = "scripts/provision.sh" - destination = "/tmp/provision.sh" - } - - # Run provisioning script (SSH key is required) - provisioner "shell" { - inline = [ - "chmod +x /tmp/provision.sh", - "sudo /tmp/provision.sh '${var.ssh_key_type}' '${var.ssh_public_key}'" - ] - } - - # Final cleanup - provisioner "shell" { - inline = [ - # Remove SSH password auth (key-only after provisioning) - "source /opt/vyatta/etc/functions/script-template", - "configure", - "delete system login user vyos authentication plaintext-password", - "commit", - "save", - "exit", - # Clean up temp files - "rm -f /tmp/gateway.conf /tmp/provision.sh", - # Clear command history - "history -c" - ] - } - - # Rename output file to ensure .raw extension - post-processor "shell-local" { - inline = [ - "cd ${var.output_directory}", - "if [ -f 'vyos-lab' ] && [ ! -f 'vyos-lab.raw' ]; then mv vyos-lab vyos-lab.raw; fi", - "echo 'VyOS image built successfully!'", - "echo 'Output: ${var.output_directory}/vyos-lab.raw'", - "echo ''", - "echo 'To use with Tinkerbell, copy to NAS:'", - "echo ' scp ${var.output_directory}/vyos-lab.raw nas:/volume1/images/vyos-lab.raw'" - ] - } -} diff --git a/infrastructure/network/vyos/vyos-build/README.md b/infrastructure/network/vyos/vyos-build/README.md index 94b33e7..36478b0 100644 --- a/infrastructure/network/vyos/vyos-build/README.md +++ b/infrastructure/network/vyos/vyos-build/README.md @@ -4,7 +4,7 @@ This directory contains the configuration and scripts for building custom VyOS g ## Overview -Instead of using Packer with QEMU keystroke automation (brittle and slow), this approach: +This approach: 1. Uses the official `vyos/vyos-build` Docker container 2. Bakes the gateway configuration directly into the image via build flavors @@ -72,15 +72,3 @@ The configuration matches `infrastructure/network/vyos/configs/gateway.conf` wit |------|---------| | `configs/gateway.conf` | Source of truth for VyOS config (Ansible applies updates) | | `vyos-build/build-flavors/gateway.toml` | Build-time config with SSH credentials | -| `packer/` | Legacy build (deprecated) | - -## Migration from Packer - -The Packer-based build (`infrastructure/network/vyos/packer/`) is deprecated. Key differences: - -| Aspect | Packer | vyos-build | -|--------|--------|------------| -| Build time | ~10 minutes | ~5 minutes | -| Dependencies | QEMU + KVM | Docker only | -| Config injection | SSH provisioner | Baked in image | -| Reliability | Keystroke-dependent | Deterministic | diff --git a/tools/labctl/cmd/images/upload.go b/tools/labctl/cmd/images/upload.go index 16a9808..481bf97 100644 --- a/tools/labctl/cmd/images/upload.go +++ b/tools/labctl/cmd/images/upload.go @@ -21,7 +21,7 @@ var uploadCmd = &cobra.Command{ Short: "Upload a local file to e2", Long: `Upload a local file to e2 storage. -The upload command is used by Packer workflows to upload built images. +The upload command is used by build workflows to upload built images. It computes the SHA256 checksum and writes metadata JSON in the same format as the sync command.`, RunE: runUpload, diff --git a/tools/labctl/internal/config/manifest_test.go b/tools/labctl/internal/config/manifest_test.go index 247226d..aa1fbcb 100644 --- a/tools/labctl/internal/config/manifest_test.go +++ b/tools/labctl/internal/config/manifest_test.go @@ -63,7 +63,7 @@ spec: checksum: sha256:abc123 destination: vyos/vyos-1.5.iso updateFile: - path: infrastructure/network/vyos/packer/source.auto.pkrvars.hcl + path: infrastructure/example/vars.hcl replacements: - pattern: 'vyos_iso_url\s*=\s*"[^"]*"' value: 'vyos_iso_url = "{{ .Source.URL }}"' diff --git a/tools/labctl/internal/store/s3_test.go b/tools/labctl/internal/store/s3_test.go index 23070bf..c101b07 100644 --- a/tools/labctl/internal/store/s3_test.go +++ b/tools/labctl/internal/store/s3_test.go @@ -526,7 +526,7 @@ func TestImageMetadata_JSON(t *testing.T) { UploadedAt: time.Date(2024, 12, 20, 12, 0, 0, 0, time.UTC), Source: SourceMetadata{ Type: "local", - Path: "infrastructure/network/vyos/packer/output/vyos-lab.raw", + Path: "/tmp/vyos-gateway.raw", }, } diff --git a/tools/labctl/internal/updater/file_test.go b/tools/labctl/internal/updater/file_test.go index 5913eea..ad8d8ef 100644 --- a/tools/labctl/internal/updater/file_test.go +++ b/tools/labctl/internal/updater/file_test.go @@ -116,7 +116,7 @@ vyos_iso_checksum = "sha256:newchecksum"`, wantModified: false, }, { - name: "HCL packer vars format", + name: "HCL vars format", replacements: []Replacement{ {Pattern: `vyos_iso_url\s*=\s*"[^"]*"`, Value: `vyos_iso_url = "{{ .Source.URL }}"`}, }, From 553ff4f893ac3cf68e8985aac3068e495f504af5 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 12:19:46 -0800 Subject: [PATCH 10/20] Add VyOS containerlab test harness --- .github/workflows/vyos-build.yml | 207 +++++++++++++++++- .gitignore | 7 +- .../09_design_decisions/003_vyos_gitops.md | 52 +++++ .../network/vyos/Dockerfile.containerlab | 35 +++ infrastructure/network/vyos/justfile | 36 +++ infrastructure/network/vyos/tests/README.md | 61 ++++++ infrastructure/network/vyos/tests/conftest.py | 205 +++++++++++++++++ .../network/vyos/tests/render-config-boot.sh | 53 +++++ .../network/vyos/tests/requirements.txt | 3 + infrastructure/network/vyos/tests/test_bgp.py | 46 ++++ .../network/vyos/tests/test_firewall.py | 95 ++++++++ .../network/vyos/tests/test_interfaces.py | 16 ++ infrastructure/network/vyos/tests/test_nat.py | 30 +++ .../network/vyos/tests/test_routing.py | 14 ++ .../network/vyos/tests/test_services.py | 43 ++++ .../network/vyos/tests/test_system.py | 34 +++ .../network/vyos/tests/topology.clab.yml | 112 ++++++++++ 17 files changed, 1044 insertions(+), 5 deletions(-) create mode 100644 infrastructure/network/vyos/Dockerfile.containerlab create mode 100644 infrastructure/network/vyos/justfile create mode 100644 infrastructure/network/vyos/tests/README.md create mode 100644 infrastructure/network/vyos/tests/conftest.py create mode 100755 infrastructure/network/vyos/tests/render-config-boot.sh create mode 100644 infrastructure/network/vyos/tests/requirements.txt create mode 100644 infrastructure/network/vyos/tests/test_bgp.py create mode 100644 infrastructure/network/vyos/tests/test_firewall.py create mode 100644 infrastructure/network/vyos/tests/test_interfaces.py create mode 100644 infrastructure/network/vyos/tests/test_nat.py create mode 100644 infrastructure/network/vyos/tests/test_routing.py create mode 100644 infrastructure/network/vyos/tests/test_services.py create mode 100644 infrastructure/network/vyos/tests/test_system.py create mode 100644 infrastructure/network/vyos/tests/topology.clab.yml diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index c7242a5..1d2100f 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -4,12 +4,11 @@ on: push: branches: [master] paths: - - 'infrastructure/network/vyos/vyos-build/**' - - 'infrastructure/network/vyos/configs/gateway.conf' + - 'infrastructure/network/vyos/**' pull_request: paths: - - 'infrastructure/network/vyos/vyos-build/**' - - 'infrastructure/network/vyos/configs/gateway.conf' + - '.github/workflows/vyos-build.yml' + - 'infrastructure/network/vyos/**' workflow_dispatch: inputs: upload: @@ -162,3 +161,203 @@ jobs: path: /tmp/vyos-gateway.raw retention-days: 7 if-no-files-found: warn + + # Build container image for integration testing + build-container: + if: github.event_name == 'pull_request' + runs-on: warp-ubuntu-latest-x64-8x + needs: validate + steps: + - uses: actions/checkout@v4 + + - name: Install SOPS + run: | + curl -LO https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 + chmod +x sops-v3.9.2.linux.amd64 + sudo mv sops-v3.9.2.linux.amd64 /usr/local/bin/sops + + - name: Write SOPS age key + run: | + echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/age-key.txt + chmod 600 /tmp/age-key.txt + + - name: Extract SSH public key + env: + SOPS_AGE_KEY_FILE: /tmp/age-key.txt + run: | + sops --decrypt \ + --extract '["ssh_public_key"]' images/packer-ssh.sops.yaml > /tmp/ssh_key.pub + echo "SSH key extracted" + + - name: Clone vyos-build + run: | + git clone -b current --single-branch --depth 1 \ + https://github.com/vyos/vyos-build.git /tmp/vyos-build + + - name: Generate build flavor + run: | + ./infrastructure/network/vyos/vyos-build/scripts/generate-flavor.sh \ + "$(cat /tmp/ssh_key.pub)" \ + /tmp/vyos-build/data/build-flavors/gateway.toml + + - name: Build VyOS ISO + run: | + # Generate version string + VERSION="test-$(date +%Y%m%d%H%M%S)" + + # Build ISO (produces squashfs we need for container) + docker run --rm --privileged \ + -v /tmp/vyos-build:/vyos \ + -e VYOS_BUILD_BY="ci@lab.gilman.io" \ + -w /vyos \ + vyos/vyos-build:current \ + bash -c "sudo ./build-vyos-image iso --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} --build-flavor gateway" + + echo "Build complete, checking for squashfs..." + find /tmp/vyos-build -name "*.squashfs" -type f 2>/dev/null || true + + - name: Install squashfs tools + run: sudo apt-get update && sudo apt-get install -y squashfs-tools-ng + + - name: Build container image + run: | + cd /tmp/vyos-build + + # Find the squashfs filesystem + SQUASHFS=$(find . -name "filesystem.squashfs" -type f 2>/dev/null | head -1) + + if [[ -z "${SQUASHFS}" ]]; then + # Try alternative location + SQUASHFS=$(find . -name "*.squashfs" -type f 2>/dev/null | head -1) + fi + + if [[ -z "${SQUASHFS}" || ! -f "${SQUASHFS}" ]]; then + echo "ERROR: squashfs not found" + find . -type f -name "*.squashfs" 2>/dev/null || true + ls -la build/ 2>/dev/null || true + exit 1 + fi + + echo "Found squashfs: ${SQUASHFS}" + + # Extract squashfs to tarball + sqfs2tar "${SQUASHFS}" > /tmp/rootfs.tar + echo "Extracted rootfs.tar: $(ls -lah /tmp/rootfs.tar)" + + # Build container image + cd $GITHUB_WORKSPACE + cp /tmp/rootfs.tar . + docker build -t vyos-gateway:test -f infrastructure/network/vyos/Dockerfile.containerlab . + rm rootfs.tar + + echo "Container image built successfully" + docker images vyos-gateway:test + + - name: Save container image + run: | + docker save vyos-gateway:test -o /tmp/vyos-gateway-container.tar + ls -lah /tmp/vyos-gateway-container.tar + + - name: Upload container image artifact + uses: actions/upload-artifact@v4 + with: + name: vyos-container-image + path: /tmp/vyos-gateway-container.tar + retention-days: 1 + + # Run integration tests using Containerlab + integration-test: + needs: build-container + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download container image artifact + uses: actions/download-artifact@v4 + with: + name: vyos-container-image + path: /tmp + + - name: Load container image + run: | + docker load -i /tmp/vyos-gateway-container.tar + docker images vyos-gateway:test + + - name: Install Containerlab + run: | + bash -c "$(curl -sL https://get.containerlab.dev)" + containerlab version + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install test dependencies + run: | + pip install -r infrastructure/network/vyos/tests/requirements.txt + + - name: Generate test config.boot + run: | + ssh-keygen -t ed25519 -f /tmp/vyos-test-key -N "" -C "vyos-ci" + chmod +x infrastructure/network/vyos/tests/render-config-boot.sh + infrastructure/network/vyos/tests/render-config-boot.sh "$(cat /tmp/vyos-test-key.pub)" + + - name: Deploy Containerlab topology + run: | + cd infrastructure/network/vyos/tests + sudo containerlab deploy -t topology.clab.yml --reconfigure + + - name: Wait for VyOS boot + run: | + echo "Waiting for VyOS to boot..." + CONTAINER="clab-vyos-gateway-test-gateway" + + # Wait for container to be running + for i in {1..30}; do + if docker ps --filter "name=${CONTAINER}" --filter "status=running" | grep -q "${CONTAINER}"; then + echo "Container is running" + break + fi + echo "Waiting for container... ($i/30)" + sleep 3 + done + + # Wait for systemd to be ready + for i in {1..60}; do + if docker exec "${CONTAINER}" systemctl is-system-running --quiet 2>/dev/null; then + echo "VyOS systemd is running" + break + fi + echo "Waiting for systemd... ($i/60)" + sleep 5 + done + + # Additional settle time for all services + echo "Waiting for services to stabilize..." + sleep 30 + + # Check VyOS status + docker exec "${CONTAINER}" /opt/vyatta/bin/vyatta-op-cmd-wrapper show version || true + + - name: Run integration tests + run: | + cd infrastructure/network/vyos/tests + pytest -v --tb=short -x + + - name: Collect logs on failure + if: failure() + run: | + CONTAINER="clab-vyos-gateway-test-gateway" + echo "=== Container logs ===" + docker logs "${CONTAINER}" 2>&1 | tail -100 || true + echo "=== VyOS configuration ===" + docker exec "${CONTAINER}" /opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration 2>&1 || true + echo "=== Interfaces ===" + docker exec "${CONTAINER}" /opt/vyatta/bin/vyatta-op-cmd-wrapper show interfaces 2>&1 || true + + - name: Cleanup + if: always() + run: | + cd infrastructure/network/vyos/tests + sudo containerlab destroy -t topology.clab.yml --cleanup || true diff --git a/.gitignore b/.gitignore index e003e5d..c17fcf6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,9 @@ .codex AGENTS.md CLAUDE.md -ref \ No newline at end of file +ref + +# VyOS containerlab test artifacts +infrastructure/network/vyos/tests/config.boot +infrastructure/network/vyos/tests/.vyos-test-key +infrastructure/network/vyos/tests/.vyos-test-key.pub diff --git a/docs/architecture/09_design_decisions/003_vyos_gitops.md b/docs/architecture/09_design_decisions/003_vyos_gitops.md index 1a2b8f5..914a63f 100644 --- a/docs/architecture/09_design_decisions/003_vyos_gitops.md +++ b/docs/architecture/09_design_decisions/003_vyos_gitops.md @@ -174,8 +174,60 @@ VyOS `commit-confirm` provides automatic rollback: 3. **Auditability**: Full Git history of all configuration changes 4. **Existing Infrastructure**: Leverages existing Tailscale network +## Integration Testing + +### Containerlab-Based Validation + +To validate configuration changes before they reach production, we use [Containerlab](https://containerlab.dev/) to run integration tests on pull requests. + +#### How It Works + +1. **Container Image Build**: The vyos-build pipeline produces a squashfs filesystem which is converted to a container image using `sqfs2tar` and a minimal Dockerfile. + +2. **Topology Simulation**: Containerlab deploys a test topology with: + - VyOS gateway container (same rootfs as production) + - Simulated network clients for WAN, MGMT, and Platform networks + +3. **Test Suite**: pytest with scrapli validates: + - Firewall groups and rules + - Interface configuration and addresses + - DHCP, DNS, and BGP configuration + - NAT/masquerade rules + - Static routes and system settings + +#### Test Files + +``` +infrastructure/network/vyos/ +├── Dockerfile.containerlab # Container build from squashfs +└── tests/ + ├── topology.clab.yml # Containerlab topology + ├── conftest.py # pytest fixtures + ├── test_gateway.py # Test suite + └── requirements.txt # Python dependencies +``` + +#### Interface Mapping + +The test environment uses simplified interface mapping: + +| Production | Test | Network | +|:-----------|:-----|:--------| +| eth4 | eth1 | WAN | +| eth5.10 | eth2 | MGMT (VLAN 10) | +| eth5.30 | eth3 | Platform (VLAN 30) | + +#### CI Integration + +Integration tests run automatically on PRs modifying `infrastructure/network/vyos/**`: + +1. `build-container` job builds VyOS container from squashfs +2. `integration-test` job deploys topology and runs pytest suite +3. Tests must pass before merge + ## Consequences - VyOS must run Tailscale client (or self-hosted runner needs lab network access) - Secrets (Tailscale OAuth, SSH keys) managed in GitHub Secrets - Initial effort to structure Ansible playbooks and test workflow +- Integration tests add build time (~5-8 minutes) but catch issues before production diff --git a/infrastructure/network/vyos/Dockerfile.containerlab b/infrastructure/network/vyos/Dockerfile.containerlab new file mode 100644 index 0000000..37d9a29 --- /dev/null +++ b/infrastructure/network/vyos/Dockerfile.containerlab @@ -0,0 +1,35 @@ +# VyOS Container Image for Containerlab Testing +# +# This Dockerfile builds a container image from the VyOS squashfs filesystem +# produced by vyos-build. The container uses the same rootfs as the production +# raw disk image, ensuring test fidelity. +# +# Usage: +# sqfs2tar build/live/filesystem.squashfs > rootfs.tar +# docker build -t vyos-gateway:test -f Dockerfile.containerlab . +# +# The resulting image can be used with Containerlab for integration testing. + +FROM scratch + +# Add the extracted squashfs filesystem +ADD rootfs.tar / + +# Mask services/targets that don't work well in containers +# - getty.target: No TTY in container +# - auditd.service: Audit subsystem not available +# - kea-dhcp-ddns-server.service: Not needed for testing +# - reboot/poweroff/halt/kexec: prevent container shutdown loops +RUN for service in getty.target auditd.service \ + reboot.target poweroff.target halt.target kexec.target \ + systemd-reboot.service systemd-poweroff.service systemd-halt.service systemd-kexec.service; do \ + systemctl mask $service 2>/dev/null || true; \ + done && \ + systemctl disable kea-dhcp-ddns-server.service 2>/dev/null || true + +# Healthcheck to verify systemd is running +HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 \ + CMD systemctl is-system-running --quiet || exit 1 + +# Start systemd as init +CMD ["/sbin/init"] diff --git a/infrastructure/network/vyos/justfile b/infrastructure/network/vyos/justfile new file mode 100644 index 0000000..5846e69 --- /dev/null +++ b/infrastructure/network/vyos/justfile @@ -0,0 +1,36 @@ +set shell := ["bash", "-euo", "pipefail", "-c"] + +SQUASHFS := "build/live/filesystem.squashfs" +ROOTFS := "rootfs.tar" +IMAGE := "vyos-gateway:test" +TOPO := "tests/topology.clab.yml" +KEY := "tests/.vyos-test-key" + +key: + test -f "{{KEY}}" || ssh-keygen -t ed25519 -f "{{KEY}}" -N "" -C "vyos-ci" + +config: key + tests/render-config-boot.sh "$(cat {{KEY}}.pub)" + +rootfs: + test -f "{{SQUASHFS}}" + sqfs2tar "{{SQUASHFS}}" > "{{ROOTFS}}" + +image: rootfs + docker build -t "{{IMAGE}}" -f Dockerfile.containerlab . + +deploy: config + sudo containerlab deploy -t "{{TOPO}}" + +destroy: + sudo containerlab destroy -t "{{TOPO}}" --cleanup + +pytest: + pytest -v tests + +test: + just deploy + just pytest + +clean: + rm -f "{{ROOTFS}}" diff --git a/infrastructure/network/vyos/tests/README.md b/infrastructure/network/vyos/tests/README.md new file mode 100644 index 0000000..c957ed9 --- /dev/null +++ b/infrastructure/network/vyos/tests/README.md @@ -0,0 +1,61 @@ +# VyOS Containerlab Tests + +This suite validates the VyOS gateway configuration using a Containerlab +topology and pytest. The topology keeps the production interface layout +(`eth4` WAN, `eth5` trunk) to exercise the real configuration. + +## Prerequisites + +- Docker (or compatible runtime) +- Containerlab +- `sqfs2tar` from `squashfs-tools-ng` +- Python with the dependencies in `requirements.txt` +- `just` (optional, for local workflow helpers) + +## Local Workflow + +From `infrastructure/network/vyos`: + +1) Build the VyOS container image (requires a `filesystem.squashfs` artifact) + +``` +just image SQUASHFS=build/live/filesystem.squashfs +``` + +2) Generate a test SSH key + config.boot + +``` +just config +``` + +3) Deploy the Containerlab topology + +``` +just deploy +``` + +4) Run tests + +``` +just pytest +``` + +5) Destroy the lab + +``` +just destroy +``` + +You can also run the full sequence with: + +``` +just test +``` + +## Environment Overrides + +- `VYOS_HOST` (default: `clab-vyos-gateway-test-gateway`) +- `VYOS_CONTAINER` (default: `VYOS_HOST`, used for `docker exec` config checks) +- `VYOS_USER` (default: `vyos`) +- `VYOS_PASS` (default: `vyos`) +- `VYOS_SSH_KEY` (path to private key for SSH auth) diff --git a/infrastructure/network/vyos/tests/conftest.py b/infrastructure/network/vyos/tests/conftest.py new file mode 100644 index 0000000..4831b58 --- /dev/null +++ b/infrastructure/network/vyos/tests/conftest.py @@ -0,0 +1,205 @@ +""" +Pytest fixtures for VyOS Gateway integration tests. + +This module provides fixtures for connecting to the VyOS gateway container +running in Containerlab. +""" + +import os +import subprocess +import time +from dataclasses import dataclass +from typing import Callable, Iterable + +import pytest +from scrapli import Scrapli + + +def wait_for_vyos_ready(host: str, timeout: int = 240, interval: int = 5) -> bool: + """Wait for VyOS to be ready for SSH connections.""" + import socket + + start_time = time.time() + while time.time() - start_time < timeout: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + result = sock.connect_ex((host, 22)) + sock.close() + if result == 0: + # SSH port is open, wait a bit more for VyOS to fully initialize + time.sleep(10) + return True + except socket.error: + pass + time.sleep(interval) + return False + + +@dataclass(frozen=True) +class TestTopology: + """Expected values for the Containerlab test topology.""" + + wan_iface: str = "eth4" + wan_ip: str = "192.168.0.2/24" + wan_gateway: str = "192.168.0.1" + trunk_iface: str = "eth5" + mgmt_vif: str = "10" + mgmt_ip: str = "10.10.10.1/24" + prov_vif: str = "20" + prov_ip: str = "10.10.20.1/24" + platform_vif: str = "30" + platform_ip: str = "10.10.30.1/24" + cluster_vif: str = "40" + cluster_ip: str = "10.10.40.1/24" + service_vif: str = "50" + service_ip: str = "10.10.50.1/24" + storage_vif: str = "60" + storage_ip: str = "10.10.60.1/24" + home_cidr: str = "192.168.0.0/24" + lab_cidr: str = "10.10.0.0/16" + dhcp_subnet: str = "10.10.10.0/24" + dhcp_range_start: str = "10.10.10.200" + dhcp_range_stop: str = "10.10.10.250" + dns_listen_addresses: tuple[str, ...] = ("10.10.10.1", "10.10.30.1") + bgp_neighbors: tuple[str, ...] = ("10.10.30.10", "10.10.30.11", "10.10.30.12") + bgp_remote_as: str = "64513" + bgp_local_as: str = "64512" + bgp_router_id: str = "10.10.50.1" + bgp_service_network: str = "10.10.50.0/24" + domain_name: str = "lab.gilman.io" + hostname: str = "gateway" + name_servers: tuple[str, ...] = ("1.1.1.1", "8.8.8.8") + time_zone: str = "America/Los_Angeles" + + +@pytest.fixture(scope="session") +def vyos_host() -> str: + """Get the VyOS gateway hostname from environment or use default.""" + return os.environ.get("VYOS_HOST", "clab-vyos-gateway-test-gateway") + + +@pytest.fixture(scope="session") +def vyos_container(vyos_host: str) -> str: + """Get the VyOS container name for docker exec.""" + return os.environ.get("VYOS_CONTAINER", vyos_host) + + +@pytest.fixture(scope="session") +def vyos_username() -> str: + """Get the VyOS username from environment or use default.""" + return os.environ.get("VYOS_USER", "vyos") + + +@pytest.fixture(scope="session") +def vyos_password() -> str: + """Get the VyOS password from environment or use default.""" + return os.environ.get("VYOS_PASS", "vyos") + + +@pytest.fixture(scope="session") +def vyos_private_key() -> str | None: + """Get the path to the VyOS SSH private key, if provided.""" + env_key = os.environ.get("VYOS_SSH_KEY") + if env_key: + return env_key + default_key = os.path.join(os.path.dirname(__file__), ".vyos-test-key") + return default_key if os.path.exists(default_key) else None + + +@pytest.fixture(scope="session") +def test_topology() -> TestTopology: + """Return the expected topology values for assertions.""" + return TestTopology() + + +@pytest.fixture(scope="session") +def vyos( + vyos_host: str, + vyos_username: str, + vyos_password: str, + vyos_private_key: str | None, +) -> Scrapli: + """ + Create a Scrapli connection to the VyOS gateway. + + This fixture uses session scope so the connection is reused across all tests. + """ + # Wait for VyOS to be ready + if not wait_for_vyos_ready(vyos_host): + pytest.fail(f"VyOS at {vyos_host} not ready after timeout") + + conn_args = { + "host": vyos_host, + "auth_username": vyos_username, + "auth_strict_key": False, + "transport": "system", + "platform": "vyos_vyos", + "transport_options": {"open_cmd": ["-tt"]}, + } + if vyos_private_key: + conn_args["auth_private_key"] = vyos_private_key + else: + conn_args["auth_password"] = vyos_password + + conn = Scrapli(**conn_args) + conn.open() + yield conn + conn.close() + + +@pytest.fixture(scope="session") +def vyos_show(vyos: Scrapli) -> Callable[[str], str]: + """Return a helper to run show commands and return output.""" + + def _show(command: str) -> str: + result = vyos.send_command(command) + if result.failed: + pytest.fail(f"Command failed: {command}") + return normalize_output(result.result) + + return _show + + +@pytest.fixture(scope="session") +def config_commands(vyos_container: str) -> str: + """Return the rendered config as VyOS set-style commands.""" + result = subprocess.run( + [ + "docker", + "exec", + vyos_container, + "vyos-config-to-commands", + "/opt/vyatta/etc/config/config.boot", + ], + check=False, + capture_output=True, + text=True, + ) + if result.returncode != 0: + stderr = result.stderr.strip() or result.stdout.strip() + pytest.fail(f"Failed to render config commands via docker exec: {stderr}") + return normalize_output(result.stdout) + + +@pytest.fixture(scope="session") +def assert_contains() -> Callable[[str, Iterable[str], str], None]: + """Return an assertion helper for checking output content.""" + + def _assert(output: str, items: Iterable[str], context: str = "") -> None: + missing = [item for item in items if item not in output] + if missing: + prefix = f"{context}: " if context else "" + raise AssertionError(f"{prefix}missing {', '.join(missing)}") + + return _assert + + +def normalize_output(output: str) -> str: + """Normalize VyOS CLI output for stable assertions.""" + warning_lines = { + "WARNING: terminal is not fully functional", + "Press RETURN to continue", + } + filtered = [line for line in output.splitlines() if line.strip() not in warning_lines] + return "\n".join(filtered).replace("'", "") diff --git a/infrastructure/network/vyos/tests/render-config-boot.sh b/infrastructure/network/vyos/tests/render-config-boot.sh new file mode 100755 index 0000000..3891660 --- /dev/null +++ b/infrastructure/network/vyos/tests/render-config-boot.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="${SCRIPT_DIR}/.." +TEMPLATE_FILE="${REPO_ROOT}/vyos-build/build-flavors/gateway.toml" +OUTPUT_FILE="${SCRIPT_DIR}/config.boot" + +usage() { + echo "Usage: $0 " + echo "" + echo "Example:" + echo " $0 \"ssh-ed25519 AAAA... comment\"" + exit 1 +} + +if [[ $# -ne 1 ]]; then + usage +fi + +SSH_PUBLIC_KEY="$1" +SSH_KEY_TYPE=$(echo "${SSH_PUBLIC_KEY}" | awk '{print $1}') +SSH_KEY_BODY=$(echo "${SSH_PUBLIC_KEY}" | awk '{print $2}') + +if [[ -z "${SSH_KEY_TYPE}" ]] || [[ -z "${SSH_KEY_BODY}" ]]; then + echo "ERROR: Could not parse SSH public key" + echo "Expected format: 'type key [comment]'" + exit 1 +fi + +if [[ ! -f "${TEMPLATE_FILE}" ]]; then + echo "ERROR: Template file not found: ${TEMPLATE_FILE}" + exit 1 +fi + +sed -n "/^default_config = '''$/,/^'''$/p" "${TEMPLATE_FILE}" \ + | sed '1d;$d' \ + | sed -e "s|%%SSH_KEY_TYPE%%|${SSH_KEY_TYPE}|g" \ + -e "s|%%SSH_PUBLIC_KEY%%|${SSH_KEY_BODY}|g" \ + > "${OUTPUT_FILE}" + +if command -v getenforce >/dev/null 2>&1 && command -v chcon >/dev/null 2>&1; then + if [[ "$(getenforce)" == "Enforcing" ]]; then + if [[ "${EUID}" -ne 0 ]] && command -v sudo >/dev/null 2>&1; then + sudo chcon -t container_file_t "${OUTPUT_FILE}" || true + else + chcon -t container_file_t "${OUTPUT_FILE}" || true + fi + fi +fi + +echo "Wrote ${OUTPUT_FILE}" diff --git a/infrastructure/network/vyos/tests/requirements.txt b/infrastructure/network/vyos/tests/requirements.txt new file mode 100644 index 0000000..3674c71 --- /dev/null +++ b/infrastructure/network/vyos/tests/requirements.txt @@ -0,0 +1,3 @@ +# VyOS Gateway Integration Test Dependencies +scrapli[ssh2,community]==2024.7.30 +pytest>=8.0.0 diff --git a/infrastructure/network/vyos/tests/test_bgp.py b/infrastructure/network/vyos/tests/test_bgp.py new file mode 100644 index 0000000..761c704 --- /dev/null +++ b/infrastructure/network/vyos/tests/test_bgp.py @@ -0,0 +1,46 @@ +""" +BGP configuration tests for the VyOS gateway. +""" + + +def test_bgp_local_as(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set protocols bgp system-as {test_topology.bgp_local_as}"], + context="BGP local AS", + ) + + +def test_bgp_router_id(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set protocols bgp parameters router-id {test_topology.bgp_router_id}"], + context="BGP router ID", + ) + + +def test_bgp_neighbors_configured(config_commands, test_topology, assert_contains): + expected = [ + f"set protocols bgp neighbor {neighbor} remote-as {test_topology.bgp_remote_as}" + for neighbor in test_topology.bgp_neighbors + ] + assert_contains(config_commands, expected, context="BGP neighbors") + + +def test_bgp_neighbors_shutdown(config_commands, test_topology, assert_contains): + expected = [ + f"set protocols bgp neighbor {neighbor} shutdown" + for neighbor in test_topology.bgp_neighbors + ] + assert_contains(config_commands, expected, context="BGP neighbor shutdown") + + +def test_bgp_network_advertisement(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [ + "set protocols bgp address-family ipv4-unicast", + f"set protocols bgp address-family ipv4-unicast network {test_topology.bgp_service_network}", + ], + context="BGP network advertisement", + ) diff --git a/infrastructure/network/vyos/tests/test_firewall.py b/infrastructure/network/vyos/tests/test_firewall.py new file mode 100644 index 0000000..7acb6d6 --- /dev/null +++ b/infrastructure/network/vyos/tests/test_firewall.py @@ -0,0 +1,95 @@ +""" +Firewall configuration tests for the VyOS gateway. +""" + + +def test_firewall_groups_exist(config_commands, assert_contains): + assert_contains( + config_commands, + [ + "set firewall group network-group HOME_NETWORK", + "set firewall group network-group LAB_NETWORKS", + "set firewall group network-group RFC1918", + ], + context="firewall groups", + ) + + +def test_home_network_group_content(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set firewall group network-group HOME_NETWORK network {test_topology.home_cidr}"], + context="HOME_NETWORK group", + ) + + +def test_lab_networks_group_content(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set firewall group network-group LAB_NETWORKS network {test_topology.lab_cidr}"], + context="LAB_NETWORKS group", + ) + + +def test_rfc1918_group_content(config_commands, assert_contains): + assert_contains( + config_commands, + [ + "set firewall group network-group RFC1918 network 10.0.0.0/8", + "set firewall group network-group RFC1918 network 172.16.0.0/12", + "set firewall group network-group RFC1918 network 192.168.0.0/16", + ], + context="RFC1918 group", + ) + + +def test_firewall_interface_binding(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [ + f"set firewall interface {test_topology.wan_iface} in name WAN_TO_LAB", + f"set firewall interface {test_topology.wan_iface} local name LOCAL", + f"set firewall interface {test_topology.wan_iface} out name LAB_TO_WAN", + ], + context="firewall interface binding", + ) + + +def test_wan_to_lab_rules(config_commands, assert_contains): + assert_contains( + config_commands, + [ + "set firewall ipv4 name WAN_TO_LAB default-action drop", + "set firewall ipv4 name WAN_TO_LAB rule 10 state established", + "set firewall ipv4 name WAN_TO_LAB rule 10 state related", + "set firewall ipv4 name WAN_TO_LAB rule 20 source group network-group HOME_NETWORK", + ], + context="WAN_TO_LAB rules", + ) + + +def test_lab_to_wan_rules(config_commands, assert_contains): + assert_contains( + config_commands, + [ + "set firewall ipv4 name LAB_TO_WAN default-action accept", + "set firewall ipv4 name LAB_TO_WAN rule 10 state established", + "set firewall ipv4 name LAB_TO_WAN rule 10 state related", + "set firewall ipv4 name LAB_TO_WAN rule 20 destination group network-group HOME_NETWORK", + ], + context="LAB_TO_WAN rules", + ) + + +def test_local_firewall_rules(config_commands, assert_contains): + assert_contains( + config_commands, + [ + "set firewall ipv4 name LOCAL default-action drop", + "set firewall ipv4 name LOCAL rule 30 destination port 22", + "set firewall ipv4 name LOCAL rule 40 destination port 53", + "set firewall ipv4 name LOCAL rule 50 destination port 67", + "set firewall ipv4 name LOCAL rule 60 destination port 179", + ], + context="LOCAL rules", + ) diff --git a/infrastructure/network/vyos/tests/test_interfaces.py b/infrastructure/network/vyos/tests/test_interfaces.py new file mode 100644 index 0000000..fb13df9 --- /dev/null +++ b/infrastructure/network/vyos/tests/test_interfaces.py @@ -0,0 +1,16 @@ +""" +Interface configuration tests for the VyOS gateway. +""" + + +def test_interface_addresses(config_commands, test_topology, assert_contains): + expected = [ + f"set interfaces ethernet {test_topology.wan_iface} address {test_topology.wan_ip}", + f"set interfaces ethernet {test_topology.trunk_iface} vif {test_topology.mgmt_vif} address {test_topology.mgmt_ip}", + f"set interfaces ethernet {test_topology.trunk_iface} vif {test_topology.prov_vif} address {test_topology.prov_ip}", + f"set interfaces ethernet {test_topology.trunk_iface} vif {test_topology.platform_vif} address {test_topology.platform_ip}", + f"set interfaces ethernet {test_topology.trunk_iface} vif {test_topology.cluster_vif} address {test_topology.cluster_ip}", + f"set interfaces ethernet {test_topology.trunk_iface} vif {test_topology.service_vif} address {test_topology.service_ip}", + f"set interfaces ethernet {test_topology.trunk_iface} vif {test_topology.storage_vif} address {test_topology.storage_ip}", + ] + assert_contains(config_commands, expected, context="interface addresses") diff --git a/infrastructure/network/vyos/tests/test_nat.py b/infrastructure/network/vyos/tests/test_nat.py new file mode 100644 index 0000000..a6c15e2 --- /dev/null +++ b/infrastructure/network/vyos/tests/test_nat.py @@ -0,0 +1,30 @@ +""" +NAT configuration tests for the VyOS gateway. +""" + + +def test_source_nat_rule_exists(config_commands, assert_contains): + assert_contains( + config_commands, + ["set nat source rule 100"], + context="NAT rule", + ) + + +def test_masquerade_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [ + "set nat source rule 100 translation address masquerade", + f"set nat source rule 100 source address {test_topology.lab_cidr}", + ], + context="NAT masquerade", + ) + + +def test_nat_outbound_interface(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set nat source rule 100 outbound-interface name {test_topology.wan_iface}"], + context="NAT outbound interface", + ) diff --git a/infrastructure/network/vyos/tests/test_routing.py b/infrastructure/network/vyos/tests/test_routing.py new file mode 100644 index 0000000..56efc01 --- /dev/null +++ b/infrastructure/network/vyos/tests/test_routing.py @@ -0,0 +1,14 @@ +""" +Routing configuration tests for the VyOS gateway. +""" + + +def test_default_route_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [ + "set protocols static route 0.0.0.0/0", + f"set protocols static route 0.0.0.0/0 next-hop {test_topology.wan_gateway}", + ], + context="default route", + ) diff --git a/infrastructure/network/vyos/tests/test_services.py b/infrastructure/network/vyos/tests/test_services.py new file mode 100644 index 0000000..09bcb66 --- /dev/null +++ b/infrastructure/network/vyos/tests/test_services.py @@ -0,0 +1,43 @@ +""" +Service configuration tests for the VyOS gateway. +""" + + +def test_dhcp_server_configured(config_commands, assert_contains): + assert_contains( + config_commands, + ["set service dhcp-server", "set service dhcp-server shared-network-name LAB_MGMT"], + context="DHCP server config", + ) + + +def test_dhcp_range_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [test_topology.dhcp_range_start, test_topology.dhcp_range_stop], + context="DHCP range", + ) + + +def test_dns_forwarding_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + ["set service dns forwarding", f"set service dns forwarding allow-from {test_topology.lab_cidr}"], + context="DNS forwarding", + ) + + +def test_dns_listen_addresses(config_commands, test_topology, assert_contains): + expected = [ + f"set service dns forwarding listen-address {address}" + for address in test_topology.dns_listen_addresses + ] + assert_contains(config_commands, expected, context="DNS listen addresses") + + +def test_ssh_service_enabled(config_commands, assert_contains): + assert_contains( + config_commands, + ["set service ssh", "set service ssh port 22"], + context="SSH service", + ) diff --git a/infrastructure/network/vyos/tests/test_system.py b/infrastructure/network/vyos/tests/test_system.py new file mode 100644 index 0000000..6aab44e --- /dev/null +++ b/infrastructure/network/vyos/tests/test_system.py @@ -0,0 +1,34 @@ +""" +System configuration tests for the VyOS gateway. +""" + + +def test_hostname_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set system host-name {test_topology.hostname}"], + context="hostname", + ) + + +def test_domain_name_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set system domain-name {test_topology.domain_name}"], + context="domain name", + ) + + +def test_name_servers_configured(config_commands, test_topology, assert_contains): + expected = [ + f"set system name-server {server}" for server in test_topology.name_servers + ] + assert_contains(config_commands, expected, context="name servers") + + +def test_timezone_configured(config_commands, test_topology, assert_contains): + assert_contains( + config_commands, + [f"set system time-zone {test_topology.time_zone}"], + context="time zone", + ) diff --git a/infrastructure/network/vyos/tests/topology.clab.yml b/infrastructure/network/vyos/tests/topology.clab.yml new file mode 100644 index 0000000..ef0d16d --- /dev/null +++ b/infrastructure/network/vyos/tests/topology.clab.yml @@ -0,0 +1,112 @@ +# Containerlab Topology for VyOS Gateway Integration Tests +# +# This topology creates a minimal lab environment to test the VyOS gateway +# configuration. It simulates key network segments with Linux clients. +# +# Interface Mapping: +# eth0 - Containerlab management (reserved) +# eth4 - WAN interface (production mapping) +# eth5 - Trunk interface (production mapping) +# +# This topology keeps the production interface layout and uses a VLAN-aware +# trunk so tests exercise the real gateway configuration. + +name: vyos-gateway-test + +topology: + nodes: + # VyOS Gateway under test + gateway: + kind: linux + image: vyos-gateway:test + # Run with systemd and extra capabilities for VyOS functionality + cmd: /sbin/init + binds: + - config.boot:/opt/vyatta/etc/config/config.boot:ro,Z + - /lib/modules:/lib/modules:ro + exec: + - sh -c "modprobe br_netfilter" + - sh -c "python3 /usr/libexec/vyos/vyos-boot-config-loader.py /opt/vyatta/etc/config/config.boot" + cap-add: + - NET_ADMIN + - SYS_ADMIN + - SYS_MODULE + + # Trunk switch for VLAN-tagged lab networks + trunk-switch: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link add br0 type bridge && ip link set br0 up" + - sh -c "for iface in eth1 eth2 eth3 eth4 eth5 eth6 eth7; do ip link set $iface up && ip link set $iface master br0; done" + + # WAN-side client (simulates home network / upstream) + wan-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip addr add 192.168.0.100/24 dev eth1 && ip link set eth1 up" + - ip route replace default via 192.168.0.2 + + # Management network client (VLAN 10 simulation) + mgmt-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link set eth1 up && ip link add link eth1 name eth1.10 type vlan id 10 && ip addr add 10.10.10.100/24 dev eth1.10 && ip link set eth1.10 up" + - ip route replace default via 10.10.10.1 + + # Provisioning network client (VLAN 20 simulation) + prov-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link set eth1 up && ip link add link eth1 name eth1.20 type vlan id 20 && ip addr add 10.10.20.100/24 dev eth1.20 && ip link set eth1.20 up" + - ip route replace default via 10.10.20.1 + + # Platform network client (VLAN 30 simulation) + platform-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link set eth1 up && ip link add link eth1 name eth1.30 type vlan id 30 && ip addr add 10.10.30.100/24 dev eth1.30 && ip link set eth1.30 up" + - ip route replace default via 10.10.30.1 + + # Tenant cluster network client (VLAN 40 simulation) + cluster-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link set eth1 up && ip link add link eth1 name eth1.40 type vlan id 40 && ip addr add 10.10.40.100/24 dev eth1.40 && ip link set eth1.40 up" + - ip route replace default via 10.10.40.1 + + # Service VIP network client (VLAN 50 simulation) + service-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link set eth1 up && ip link add link eth1 name eth1.50 type vlan id 50 && ip addr add 10.10.50.100/24 dev eth1.50 && ip link set eth1.50 up" + - ip route replace default via 10.10.50.1 + + # Storage network client (VLAN 60 simulation) + storage-client: + kind: linux + image: alpine:latest + exec: + - sh -c "ip link set eth1 up && ip link add link eth1 name eth1.60 type vlan id 60 && ip addr add 10.10.60.100/24 dev eth1.60 && ip link set eth1.60 up" + - ip route replace default via 10.10.60.1 + + links: + # WAN connection (gateway eth4 <-> wan-client eth1) + - endpoints: ["gateway:eth4", "wan-client:eth1"] + + # Trunk connection (gateway eth5 <-> trunk-switch eth1) + - endpoints: ["gateway:eth5", "trunk-switch:eth1"] + + # VLAN clients connected to trunk-switch + - endpoints: ["trunk-switch:eth2", "mgmt-client:eth1"] + - endpoints: ["trunk-switch:eth3", "prov-client:eth1"] + - endpoints: ["trunk-switch:eth4", "platform-client:eth1"] + - endpoints: ["trunk-switch:eth5", "cluster-client:eth1"] + - endpoints: ["trunk-switch:eth6", "service-client:eth1"] + - endpoints: ["trunk-switch:eth7", "storage-client:eth1"] From ddacd7ccd49c90b957893806adffa42e57cb07c5 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 12:23:36 -0800 Subject: [PATCH 11/20] Run VyOS build workflow on feature branch --- .github/workflows/vyos-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 1d2100f..0ef3445 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -2,7 +2,9 @@ name: Build VyOS Image on: push: - branches: [master] + branches: + - master + - joshuagilman/hom-24-introduce-containerlab-integration-tests-for-vyos-gateway paths: - 'infrastructure/network/vyos/**' pull_request: From 938a192fc02112df87f6c3853ad82a18e05ec367 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 12:27:39 -0800 Subject: [PATCH 12/20] Run build-container on push and dispatch --- .github/workflows/vyos-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 0ef3445..eb18c22 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -166,7 +166,7 @@ jobs: # Build container image for integration testing build-container: - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' runs-on: warp-ubuntu-latest-x64-8x needs: validate steps: From f019f8cd1111db354393dfa638bd717601411837 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 12:36:49 -0800 Subject: [PATCH 13/20] Fix vyos-build ISO flavor argument --- .github/workflows/vyos-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index eb18c22..9a62ca5 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -213,7 +213,7 @@ jobs: -e VYOS_BUILD_BY="ci@lab.gilman.io" \ -w /vyos \ vyos/vyos-build:current \ - bash -c "sudo ./build-vyos-image iso --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} --build-flavor gateway" + bash -c "sudo ./build-vyos-image iso --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" echo "Build complete, checking for squashfs..." find /tmp/vyos-build -name "*.squashfs" -type f 2>/dev/null || true From 72af07188aaaefcbf1ca59da3097cd328bc6d711 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 12:48:52 -0800 Subject: [PATCH 14/20] Skip full VyOS build on non-master runs --- .github/workflows/vyos-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 9a62ca5..d376709 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -61,7 +61,7 @@ jobs: done build: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') runs-on: warp-ubuntu-latest-x64-8x needs: validate steps: From ceac288ecc6e22bf473563956acb8b327c0c21fe Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 12:51:33 -0800 Subject: [PATCH 15/20] Fix vyos-build container ISO command --- .github/workflows/vyos-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index d376709..4c64149 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -213,7 +213,7 @@ jobs: -e VYOS_BUILD_BY="ci@lab.gilman.io" \ -w /vyos \ vyos/vyos-build:current \ - bash -c "sudo ./build-vyos-image iso --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" + bash -c "sudo ./build-vyos-image --architecture amd64 --build-by ci@lab.gilman.io --build-type release --version ${VERSION} gateway" echo "Build complete, checking for squashfs..." find /tmp/vyos-build -name "*.squashfs" -type f 2>/dev/null || true From d045dff4de796b17cca59ab588e3938b48062af2 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 13:15:42 -0800 Subject: [PATCH 16/20] Restore vyos-build workflow triggers --- .github/workflows/vyos-build.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 4c64149..fc866ec 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -2,14 +2,11 @@ name: Build VyOS Image on: push: - branches: - - master - - joshuagilman/hom-24-introduce-containerlab-integration-tests-for-vyos-gateway + branches: [master] paths: - 'infrastructure/network/vyos/**' pull_request: paths: - - '.github/workflows/vyos-build.yml' - 'infrastructure/network/vyos/**' workflow_dispatch: inputs: @@ -61,7 +58,7 @@ jobs: done build: - if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: warp-ubuntu-latest-x64-8x needs: validate steps: @@ -166,7 +163,7 @@ jobs: # Build container image for integration testing build-container: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' + if: github.event_name == 'pull_request' runs-on: warp-ubuntu-latest-x64-8x needs: validate steps: From 19116d73bd1eb8c49d1e598f313265d70d15e09b Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 13:39:14 -0800 Subject: [PATCH 17/20] Add CI caches for sops, containerlab, and pip --- .github/workflows/vyos-build.yml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index fc866ec..5601cfe 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -169,11 +169,23 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Cache SOPS + uses: actions/cache@v4 + with: + path: ~/.cache/sops + key: sops-v3.9.2 + - name: Install SOPS run: | - curl -LO https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 - chmod +x sops-v3.9.2.linux.amd64 - sudo mv sops-v3.9.2.linux.amd64 /usr/local/bin/sops + if [[ -x "${HOME}/.cache/sops/sops" ]]; then + sudo cp "${HOME}/.cache/sops/sops" /usr/local/bin/sops + exit 0 + fi + mkdir -p "${HOME}/.cache/sops" + curl -Lo "${HOME}/.cache/sops/sops" \ + https://github.com/getsops/sops/releases/download/v3.9.2/sops-v3.9.2.linux.amd64 + chmod +x "${HOME}/.cache/sops/sops" + sudo cp "${HOME}/.cache/sops/sops" /usr/local/bin/sops - name: Write SOPS age key run: | @@ -284,13 +296,21 @@ jobs: - name: Install Containerlab run: | - bash -c "$(curl -sL https://get.containerlab.dev)" + if [[ -x "${HOME}/.cache/containerlab/containerlab" ]]; then + sudo cp "${HOME}/.cache/containerlab/containerlab" /usr/local/bin/containerlab + else + mkdir -p "${HOME}/.cache/containerlab" + bash -c "$(curl -sL https://get.containerlab.dev)" + sudo cp /usr/local/bin/containerlab "${HOME}/.cache/containerlab/containerlab" + fi containerlab version - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' + cache: 'pip' + cache-dependency-path: infrastructure/network/vyos/tests/requirements.txt - name: Install test dependencies run: | From fb934fdf9f67bfa8d039e268235956252cab3d2b Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 14:55:08 -0800 Subject: [PATCH 18/20] Add timeout for containerlab deploy --- .github/workflows/vyos-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index 5601cfe..f3274cd 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -326,6 +326,7 @@ jobs: run: | cd infrastructure/network/vyos/tests sudo containerlab deploy -t topology.clab.yml --reconfigure + timeout-minutes: 10 - name: Wait for VyOS boot run: | From 670d63cef703b4d9985f6bb674ebcbfd81cb388b Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 15:05:02 -0800 Subject: [PATCH 19/20] Fix containerlab cache copy path --- .github/workflows/vyos-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index f3274cd..be17cc1 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -301,7 +301,8 @@ jobs: else mkdir -p "${HOME}/.cache/containerlab" bash -c "$(curl -sL https://get.containerlab.dev)" - sudo cp /usr/local/bin/containerlab "${HOME}/.cache/containerlab/containerlab" + BIN_PATH="$(command -v containerlab)" + sudo cp "${BIN_PATH}" "${HOME}/.cache/containerlab/containerlab" fi containerlab version From e09a0c722c571f7514714f9fc7cd16c5daf9105e Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 20 Dec 2025 15:17:08 -0800 Subject: [PATCH 20/20] Move VyOS boot init out of containerlab deploy --- .github/workflows/vyos-build.yml | 6 ++++++ infrastructure/network/vyos/tests/topology.clab.yml | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/vyos-build.yml b/.github/workflows/vyos-build.yml index be17cc1..8a972d6 100644 --- a/.github/workflows/vyos-build.yml +++ b/.github/workflows/vyos-build.yml @@ -329,6 +329,12 @@ jobs: sudo containerlab deploy -t topology.clab.yml --reconfigure timeout-minutes: 10 + - name: Initialize VyOS config + run: | + CONTAINER="clab-vyos-gateway-test-gateway" + sudo docker exec "${CONTAINER}" sh -c "modprobe br_netfilter || true" + sudo docker exec "${CONTAINER}" sh -c "timeout 60 python3 /usr/libexec/vyos/vyos-boot-config-loader.py /opt/vyatta/etc/config/config.boot || true" + - name: Wait for VyOS boot run: | echo "Waiting for VyOS to boot..." diff --git a/infrastructure/network/vyos/tests/topology.clab.yml b/infrastructure/network/vyos/tests/topology.clab.yml index ef0d16d..9bd2ec6 100644 --- a/infrastructure/network/vyos/tests/topology.clab.yml +++ b/infrastructure/network/vyos/tests/topology.clab.yml @@ -24,9 +24,6 @@ topology: binds: - config.boot:/opt/vyatta/etc/config/config.boot:ro,Z - /lib/modules:/lib/modules:ro - exec: - - sh -c "modprobe br_netfilter" - - sh -c "python3 /usr/libexec/vyos/vyos-boot-config-loader.py /opt/vyatta/etc/config/config.boot" cap-add: - NET_ADMIN - SYS_ADMIN