From 4709a05ad4fa5178f761991eddb0c7918a876bdb Mon Sep 17 00:00:00 2001 From: Danilo Reyes Date: Sun, 28 Dec 2025 22:53:25 -0600 Subject: [PATCH] Refactor CI/CD workflow to utilize NixOS VM for testing - Changed the CI workflow to run tests inside a NixOS virtual machine instead of directly on the runner. - Updated the NixOS VM configuration to include necessary dependencies and services for testing. - Added a script to handle test execution within the VM, including setup for Python and Node.js environments. - Implemented SSH access to the VM for remote operations and streamlined the process of starting and stopping the VM during tests. - Enhanced the workflow to build the VM and copy the codebase for testing, ensuring a more isolated and consistent testing environment. --- .gitea/workflows/test.yml | 136 ++++++++++++++++++------------------- nix/test-vm.nix | 116 +++++++++++++++---------------- nix/vm-config.nix | 9 +++ scripts/run-tests-in-vm.sh | 64 +++++++++++++++++ 4 files changed, 191 insertions(+), 134 deletions(-) create mode 100644 nix/vm-config.nix create mode 100755 scripts/run-tests-in-vm.sh diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 83bb28c..fae0144 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -8,90 +8,84 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: nixos steps: - name: Checkout code uses: actions/checkout@v3 - - name: Setup Nix - uses: cachix/install-nix-action@v20 - with: - nix: 2.18.1 - extra_nix_config: | - experimental-features = nix-command flakes - - - name: Install Nix dependencies + - name: Build NixOS VM run: | - nix profile install nixpkgs#postgresql - nix profile install nixpkgs#python3 - nix profile install nixpkgs#nodejs_20 - nix profile install nixpkgs#npm + # Build the VM configuration using nixos-rebuild + # This creates a VM that can be run with QEMU + nixos-rebuild build-vm \ + -I nixos-config=./nix/test-vm.nix \ + -I nixpkgs= \ + -o vm-result - - name: Setup Python + - name: Start VM run: | - python3 -m pip install --upgrade pip - pip install -r backend/requirements.txt + # Find the VM run script (name may vary) + VM_SCRIPT=$(find vm-result/bin -name "run-*-vm" -type f | head -1) + if [ -z "$VM_SCRIPT" ]; then + echo "VM script not found in vm-result/bin" + ls -la vm-result/bin/ + exit 1 + fi + echo "Starting VM with script: $VM_SCRIPT" + + # Start VM in background + # The VM will expose SSH on port 2222 + $VM_SCRIPT > vm.log 2>&1 & + VM_PID=$! + echo "VM_PID=$VM_PID" >> $GITHUB_ENV + + # Wait for VM to boot and SSH to be available + echo "Waiting for VM to boot..." + for i in {1..120}; do + if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=2 \ + -p 2222 root@127.0.0.1 "echo 'VM is ready'" 2>/dev/null; then + echo "VM is ready!" + break + fi + if [ $i -eq 120 ]; then + echo "VM failed to start after 4 minutes" + echo "VM log:" + tail -100 vm.log + exit 1 + fi + sleep 2 + done - - name: Setup Node.js + - name: Copy code to VM run: | - cd frontend - npm ci + # Copy the entire repository to the VM + SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222" + ssh $SSH_OPTS root@127.0.0.1 "mkdir -p /tmp/moviemap" + + # Use tar to copy files (more reliable than rsync in CI) + tar --exclude='.git' \ + --exclude='node_modules' \ + --exclude='__pycache__' \ + --exclude='*.pyc' \ + --exclude='.pytest_cache' \ + --exclude='dist' \ + --exclude='vm-result' \ + -czf - . | ssh $SSH_OPTS root@127.0.0.1 "cd /tmp/moviemap && tar -xzf -" - - name: Start PostgreSQL + - name: Run all tests in VM run: | - sudo systemctl start postgresql || sudo service postgresql start - sudo -u postgres psql -c "CREATE DATABASE moviemap_test;" - sudo -u postgres psql -c "CREATE USER moviemap WITH PASSWORD 'test';" - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE moviemap_test TO moviemap;" - sudo -u postgres psql -c "ALTER USER moviemap CREATEDB;" - env: - POSTGRES_HOST: localhost - POSTGRES_PORT: 5432 + # Execute the test script inside the VM + SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222" + ssh $SSH_OPTS root@127.0.0.1 "bash /tmp/moviemap/scripts/run-tests-in-vm.sh" - - name: Run database migrations - run: | - cd backend - export POSTGRES_SOCKET_PATH=/var/run/postgresql - export POSTGRES_DB=moviemap_test - export POSTGRES_USER=moviemap - alembic upgrade head - env: - TEST_POSTGRES_DB: moviemap_test - TEST_POSTGRES_USER: moviemap - TEST_POSTGRES_SOCKET_PATH: /var/run/postgresql - - - name: Run backend tests - run: | - cd backend - export POSTGRES_SOCKET_PATH=/var/run/postgresql - export POSTGRES_DB=moviemap_test - export POSTGRES_USER=moviemap - export TEST_POSTGRES_DB=moviemap_test - export TEST_POSTGRES_USER=moviemap - export TEST_POSTGRES_SOCKET_PATH=/var/run/postgresql - pytest tests/ -v --cov=app --cov-report=term-missing - env: - # Mock *arr URLs for testing (tests will mock the actual API calls) - RADARR_URL: http://localhost:7878 - SONARR_URL: http://localhost:8989 - LIDARR_URL: http://localhost:8686 - RADARR_API_KEY: test-key - SONARR_API_KEY: test-key - LIDARR_API_KEY: test-key - - - name: Build frontend - run: | - cd frontend - npm run build - - - name: Run frontend tests - run: | - cd frontend - npm test -- --run - - - name: Cleanup + - name: Stop VM if: always() run: | - sudo systemctl stop postgresql || sudo service postgresql stop || true + if [ ! -z "$VM_PID" ]; then + kill $VM_PID || true + wait $VM_PID 2>/dev/null || true + fi + # Also try to kill any remaining QEMU processes + pkill -f "moviemap-test-vm" || true diff --git a/nix/test-vm.nix b/nix/test-vm.nix index 23d01e4..0832485 100644 --- a/nix/test-vm.nix +++ b/nix/test-vm.nix @@ -1,12 +1,20 @@ -# NixOS VM configuration for testing Movie Map -# This VM includes PostgreSQL, Radarr, Sonarr, and Lidarr with test data +# NixOS VM configuration for testing Movie Map in CI/CD +# This VM includes all dependencies needed for testing +# Usage: nixos-rebuild build-vm -I nixos-config=./nix/test-vm.nix { config, pkgs, lib, ... }: { - # Enable QEMU guest agent for better VM management - services.qemuGuest.enable = true; + imports = [ + + ]; + + # VM-specific configuration + virtualisation = { + memorySize = 2048; # 2GB RAM + cores = 2; + graphics = false; # Headless + }; - # Networking - allow external access networking = { hostName = "moviemap-test-vm"; firewall = { @@ -14,13 +22,23 @@ allowedTCPPorts = [ 8080 # Movie Map backend 5432 # PostgreSQL - 7878 # Radarr - 8989 # Sonarr - 8686 # Lidarr + 22 # SSH ]; }; }; + # Enable SSH for remote access + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "yes"; + PasswordAuthentication = true; + }; + }; + + # Set root password for SSH access + users.users.root.password = "test"; + # PostgreSQL configuration services.postgresql = { enable = true; @@ -40,63 +58,35 @@ }; }; - # Radarr configuration - services.radarr = { - enable = true; - openFirewall = true; - user = "radarr"; - group = "radarr"; - }; - - # Sonarr configuration - services.sonarr = { - enable = true; - openFirewall = true; - user = "sonarr"; - group = "sonarr"; - }; - - # Lidarr configuration - services.lidarr = { - enable = true; - openFirewall = true; - user = "lidarr"; - group = "lidarr"; - }; - - # Create test API keys for *arr services - # These will be set via environment variables in the CI/CD - # For now, we'll create a script that generates them - systemd.services.setup-arr-services = { - description = "Setup *arr services with test API keys"; - wantedBy = [ "multi-user.target" ]; - after = [ "radarr.service" "sonarr.service" "lidarr.service" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = '' - # Wait for services to be ready - sleep 10 - - # Note: In a real setup, you would configure API keys via the *arr APIs - # For testing, we'll use environment variables set by CI/CD - echo "Test VM setup complete" - ''; - }; - - # Environment variables for test configuration - environment.variables = { - TEST_RADARR_URL = "http://localhost:7878"; - TEST_SONARR_URL = "http://localhost:8989"; - TEST_LIDARR_URL = "http://localhost:8686"; - }; - - # System packages + # Python and Node.js for testing environment.systemPackages = with pkgs; [ + python3 + python3Packages.pip + nodejs_20 + nodePackages.npm + postgresql curl jq - postgresql + git + vim + # Testing tools + python3Packages.pytest + python3Packages.pytest-asyncio + python3Packages.pytest-cov + python3Packages.httpx ]; -} + # Create a test user + users.users.test = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + password = "test"; + openssh.authorizedKeys.keys = []; + }; + + # Allow passwordless sudo for test user + security.sudo.wheelNeedsPassword = false; + + # System configuration + system.stateVersion = "23.11"; +} diff --git a/nix/vm-config.nix b/nix/vm-config.nix new file mode 100644 index 0000000..41ea792 --- /dev/null +++ b/nix/vm-config.nix @@ -0,0 +1,9 @@ +# VM configuration entry point for CI/CD +{ pkgs, lib, ... }: + +{ + imports = [ + ./test-vm.nix + ]; +} + diff --git a/scripts/run-tests-in-vm.sh b/scripts/run-tests-in-vm.sh new file mode 100755 index 0000000..52ff1c6 --- /dev/null +++ b/scripts/run-tests-in-vm.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Helper script to run tests inside the VM +# This script is executed inside the VM + +set -e + +cd /tmp/moviemap + +echo "=== Setting up test environment ===" + +# Setup Python +cd backend +export PATH="/root/.local/bin:$PATH" +python3 -m pip install --user --upgrade pip +python3 -m pip install --user -r requirements.txt + +# Setup Node.js +cd ../frontend +npm ci + +echo "=== Waiting for PostgreSQL ===" +# Wait for PostgreSQL to be ready +for i in {1..30}; do + if sudo -u postgres psql -c "SELECT 1;" > /dev/null 2>&1; then + echo "PostgreSQL is ready" + break + fi + if [ $i -eq 30 ]; then + echo "PostgreSQL failed to start" + exit 1 + fi + sleep 1 +done + +echo "=== Running database migrations ===" +cd ../backend +export POSTGRES_SOCKET_PATH=/var/run/postgresql +export POSTGRES_DB=moviemap_test +export POSTGRES_USER=moviemap +export PATH="/root/.local/bin:$PATH" +alembic upgrade head + +echo "=== Running backend tests ===" +export TEST_POSTGRES_DB=moviemap_test +export TEST_POSTGRES_USER=moviemap +export TEST_POSTGRES_SOCKET_PATH=/var/run/postgresql +export RADARR_URL=http://localhost:7878 +export SONARR_URL=http://localhost:8989 +export LIDARR_URL=http://localhost:8686 +export RADARR_API_KEY=test-key +export SONARR_API_KEY=test-key +export LIDARR_API_KEY=test-key +export PATH="/root/.local/bin:$PATH" +pytest tests/ -v --cov=app --cov-report=term-missing + +echo "=== Building frontend ===" +cd ../frontend +npm run build + +echo "=== Running frontend tests ===" +npm test -- --run + +echo "=== All tests completed successfully ===" +