mirror of
https://github.com/trailofbits/algo.git
synced 2026-08-17 21:25:50 +02:00
* feat: add destroy subcommand to tear down deployed servers Add `./algo destroy <server-ip>` to programmatically remove cloud resources and clean up local configs. Reads provider and server name from configs/<ip>/.config.yml, gathers credentials via existing prompts.yml, confirms with user, then dispatches to provider-specific destroy tasks. Supports all 11 cloud providers: - DigitalOcean, EC2, Lightsail (CloudFormation), Azure (resource group), GCE (instance + subsidiary resources), Hetzner, Vultr, Scaleway, OpenStack, CloudStack, Linode - Local provider: config cleanup only Also stores algo_region in .config.yml during deployment so destroy can auto-detect region. Fixes Scaleway module to allow state=absent without image/commercial_type/organization params. Adds Vultr to region-required providers and stores algo_region in Vultr prompts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add list-servers script and tests Add scripts/list_servers.py to scan configs/ for deployed server metadata and output JSON. Referenced by `./algo list-servers`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Tests
Running Tests
# Run all linters (same as CI)
ansible-lint . && yamllint . && ruff check . && shellcheck scripts/*.sh
# Run Python unit tests
pytest tests/unit/ -q
# Run E2E connectivity tests (requires deployed Algo on localhost)
sudo tests/e2e/test-vpn-connectivity.sh both
Directory Structure
tests/
├── unit/ # Python unit tests (pytest)
│ ├── test_basic_sanity.py
│ ├── test_config_validation.py
│ ├── test_template_rendering.py
│ └── ...
├── e2e/ # End-to-end connectivity tests
│ └── test-vpn-connectivity.sh
├── integration/ # Integration test helpers
│ └── mock_modules/
├── fixtures/ # Shared test data
│ └── test_variables.yml
└── conftest.py # Pytest configuration
Test Coverage
| Category | Tests | What's Verified |
|---|---|---|
| Sanity | test_basic_sanity.py |
Python version, config syntax, playbook validity |
| Config | test_config_validation.py |
WireGuard/IPsec config formats, key validation |
| Templates | test_template_rendering.py |
Jinja2 template syntax, filter compatibility |
| Certificates | test_certificate_validation.py |
OpenSSL compatibility, PKCS#12 export |
| Cloud Providers | test_cloud_provider_configs.py |
Region formats, instance types, OS images |
| E2E | test-vpn-connectivity.sh |
WireGuard handshake, IPsec connection, DNS through VPN |
CI Workflows
| Workflow | Trigger | What It Does |
|---|---|---|
lint.yml |
All PRs | ansible-lint, yamllint, ruff, shellcheck |
main.yml |
Push to master | Syntax check, unit tests, Docker build |
integration-tests.yml |
PRs to roles/ | Full localhost deployment + E2E tests |
smart-tests.yml |
All PRs | Runs subset based on changed files |
Writing Tests
Python Unit Tests
Place in tests/unit/. Use fixtures from conftest.py:
def test_something(mock_ansible_module, jinja_env):
# mock_ansible_module - mocked AnsibleModule
# jinja_env - Jinja2 environment with Ansible filters
pass
Shell Scripts
Use bash strict mode and pass shellcheck:
#!/bin/bash
set -euo pipefail
Troubleshooting
E2E tests fail with "namespace already exists"
sudo ip netns del algo-client
Template tests fail with "filter not found"
Add the filter to the mock in conftest.py.
CI fails but local passes Check Python/Ansible versions match CI (Python 3.11, Ansible 12+).