diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a7ac93e..61e527a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -25,10 +25,20 @@ We are migrating the tracker to a new infrastructure on Hetzner, involving: torrust-tracker-demo/ ├── .github/ │ ├── workflows/ # GitHub Actions CI/CD pipelines +│ ├── prompts/ # AI assistant prompts and templates │ └── copilot-instructions.md # This contributor guide ├── docs/ │ ├── adr/ # Architecture Decision Records │ │ └── 001-makefile-location.md # Makefile location decision +│ ├── guides/ # User and developer guides +│ │ ├── integration-testing-guide.md # Testing guide +│ │ ├── quick-start.md # Fast setup guide +│ │ └── smoke-testing-guide.md # End-to-end testing +│ ├── infrastructure/ # Infrastructure-specific documentation +│ ├── issues/ # Issue documentation and analysis +│ ├── plans/ # Project planning documentation +│ ├── refactoring/ # Refactoring documentation +│ ├── testing/ # Testing documentation │ └── README.md # Cross-cutting documentation index ├── infrastructure/ # Infrastructure as Code │ ├── terraform/ # OpenTofu/Terraform configurations @@ -39,34 +49,52 @@ torrust-tracker-demo/ │ │ ├── user-data-minimal.yaml.tpl # Debug configuration │ │ ├── meta-data.yaml # VM metadata │ │ └── network-config.yaml # Network setup -│ ├── scripts/ # Infrastructure automation scripts -│ ├── tests/ # Infrastructure validation tests -│ ├── docs/ # Infrastructure documentation +│ ├── config/ # Infrastructure configuration templates +│ │ ├── environments/ # Environment-specific configs +│ │ └── templates/ # Configuration templates +│ ├── scripts/ # Infrastructure automation scripts +│ │ ├── deploy-app.sh # Application deployment script +│ │ ├── provision-infrastructure.sh # Infrastructure provisioning +│ │ └── health-check.sh # Health validation script +│ ├── tests/ # Infrastructure validation tests +│ ├── docs/ # Infrastructure documentation │ │ ├── quick-start.md # Fast setup guide │ │ ├── local-testing-setup.md # Detailed setup │ │ ├── infrastructure-overview.md # Architecture overview +│ │ ├── refactoring/ # Refactoring documentation │ │ ├── testing/ # Testing documentation -│ │ └── third-party/ # Third-party setup guides -│ ├── .gitignore # Infrastructure-specific ignores -│ └── README.md # Infrastructure overview -├── application/ # Application deployment and services +│ │ ├── third-party/ # Third-party setup guides +│ │ └── bugs/ # Bug documentation +│ ├── .gitignore # Infrastructure-specific ignores +│ └── README.md # Infrastructure overview +├── application/ # Application deployment and services +│ ├── config/ # Application configuration +│ │ └── templates/ # Configuration templates │ ├── share/ -│ │ ├── bin/ # Deployment and utility scripts -│ │ ├── container/ # Docker service configurations -│ │ ├── dev/ # Development configs -│ │ └── grafana/ # Grafana dashboards -│ ├── docs/ # Application documentation +│ │ ├── bin/ # Deployment and utility scripts +│ │ ├── container/ # Docker service configurations +│ │ ├── dev/ # Development configs +│ │ └── grafana/ # Grafana dashboards +│ ├── storage/ # Persistent data storage +│ │ ├── certbot/ # SSL certificate storage +│ │ ├── dhparam/ # DH parameters +│ │ ├── prometheus/ # Prometheus data +│ │ ├── proxy/ # Nginx proxy configs +│ │ └── tracker/ # Tracker data +│ ├── docs/ # Application documentation │ │ ├── production-setup.md # Production deployment docs │ │ ├── deployment.md # Deployment procedures │ │ ├── firewall-requirements.md # Application firewall requirements │ │ ├── useful-commands.md # Operational commands -│ │ └── media/ # Screenshots and diagrams -│ ├── compose.yaml # Docker Compose for services -│ ├── .env.production # Production environment template -│ ├── .gitignore # Application-specific ignores -│ └── README.md # Application overview -├── Makefile # Main automation interface -└── *.md # Project root documentation +│ │ └── media/ # Screenshots and diagrams +│ ├── compose.yaml # Docker Compose for services +│ ├── .env # Local environment configuration +│ ├── .gitignore # Application-specific ignores +│ └── README.md # Application overview +├── scripts/ # Project-wide utility scripts +│ └── lint.sh # Linting script for all file types +├── Makefile # Main automation interface +└── *.md # Project root documentation ``` ### Key Components @@ -106,33 +134,120 @@ make install-deps # 3. Setup SSH key for VMs make setup-ssh-key -# 4. Test infrastructure locally -make apply # Deploy test VM -make ssh # Connect to VM -make destroy # Cleanup +# 4. Test twelve-factor deployment workflow locally +make infra-apply # Provision infrastructure (platform setup) +make app-deploy # Deploy application (Build + Release + Run stages) +make health-check # Validate deployment +make ssh # Connect to VM +make infra-destroy # Cleanup # 5. Run tests -make test # Full infrastructure test -make test-syntax # Syntax validation only +make test # Full infrastructure test +make test-syntax # Syntax validation only ``` ### Main Commands -| Command | Purpose | -| ------------------------- | ------------------------------------------- | -| `make help` | Show all available commands | -| `make install-deps` | Install OpenTofu, libvirt, KVM, virt-viewer | -| `make test` | Run complete infrastructure tests | -| `make apply` | Deploy VM with full configuration | -| `make apply-minimal` | Deploy VM with minimal config | -| `make ssh` | Connect to deployed VM | -| `make console` | Access VM console (text-based) | -| `make vm-console` | Access VM graphical console (GUI) | -| `make destroy` | Remove deployed VM | -| `make monitor-cloud-init` | Watch VM provisioning progress | +#### Twelve-Factor Workflow (Recommended) + +| Command | Purpose | +| ------------------- | ------------------------------------------------- | +| `make infra-apply` | Provision infrastructure (platform setup) | +| `make app-deploy` | Deploy application (Build + Release + Run stages) | +| `make app-redeploy` | Redeploy application (Release + Run stages only) | +| `make health-check` | Validate deployment health | + +#### Infrastructure Management + +| Command | Purpose | +| -------------------------- | -------------------------------------------- | +| `make help` | Show all available commands | +| `make install-deps` | Install OpenTofu, libvirt, KVM, virt-viewer | +| `make infra-init` | Initialize infrastructure (Terraform init) | +| `make infra-plan` | Plan infrastructure changes | +| `make infra-destroy` | Destroy infrastructure | +| `make infra-status` | Show infrastructure status | +| `make infra-refresh-state` | Refresh Terraform state to detect IP changes | + +#### VM Access and Debugging + +| Command | Purpose | +| ----------------- | --------------------------------- | +| `make ssh` | Connect to deployed VM | +| `make console` | Access VM console (text-based) | +| `make vm-console` | Access VM graphical console (GUI) | + +#### Testing and Validation + +| Command | Purpose | +| ------------------ | --------------------------------------- | +| `make test` | Run complete infrastructure tests | +| `make test-syntax` | Run syntax validation only | +| `make lint` | Run all linting (alias for test-syntax) | + +#### Legacy Commands (Deprecated) + +| Command | New Equivalent | +| -------------- | -------------------------------------- | +| `make apply` | `make infra-apply` + `make app-deploy` | +| `make destroy` | `make infra-destroy` | +| `make status` | `make infra-status` | ## 📋 Conventions and Standards +### Twelve-Factor App Principles + +This project implements [twelve-factor app](https://12factor.net/) methodology for application deployment, with a clear separation between infrastructure provisioning and application deployment: + +#### Infrastructure vs Application Deployment + +**Important Distinction**: The twelve-factor methodology applies specifically to **application deployment**, not infrastructure provisioning. + +- **Infrastructure Provisioning** (`make infra-apply`): Separate step that provisions the platform/environment + - Creates VMs, networks, firewall rules using Infrastructure as Code + - Applies cloud-init configuration + - Sets up the foundation where the application will run + - **This is NOT part of the twelve-factor Build stage** + +#### Twelve-Factor Application Deployment Stages + +The twelve-factor **Build, Release, Run** stages apply to the application deployment process (`make app-deploy`): + +- **Build Stage**: Transform application code into executable artifacts + + - Compile source code for production + - Create container images (Docker) + - Package application dependencies + - Generate static assets + +- **Release Stage**: Combine built application with environment-specific configuration + + - Apply environment variables and configuration files + - Combine application artifacts with runtime configuration + - Prepare deployment-ready releases + +- **Run Stage**: Execute the application in the runtime environment + - Start application processes (tracker binary, background jobs) + - Start supporting services (MySQL, Nginx, Prometheus, Grafana) + - Enable health checks and monitoring + - Make the application accessible to clients + +#### Benefits of This Approach + +- **Separation of Concerns**: Infrastructure changes don't require application redeployment +- **Faster Iteration**: Use `make app-redeploy` to update only the application (Release + Run stages) +- **Environment Consistency**: Same application deployment workflow for local testing and production +- **Rollback Capability**: Infrastructure and application can be rolled back independently +- **Testing Isolation**: Test infrastructure provisioning separately from application deployment + +#### Typical Development Workflow + +1. **Initial Setup**: `make infra-apply` → `make app-deploy` +2. **Code Changes**: `make app-redeploy` (skips infrastructure) +3. **Infrastructure Changes**: `make infra-apply` → `make app-redeploy` +4. **Validation**: `make health-check` +5. **Cleanup**: `make infra-destroy` + ### Git Workflow #### Branch Naming @@ -185,6 +300,8 @@ make test-syntax # Syntax validation only - **Structure**: Use consistent heading hierarchy - **Links**: Prefer relative links for internal documentation - **Code blocks**: Always specify language for syntax highlighting +- **Tables**: Tables automatically ignore line length limits (configured globally in + `.markdownlint.json`). No special formatting required for table line lengths. #### Automated Linting @@ -237,7 +354,29 @@ The project includes a comprehensive linting script that validates all file type For verifying the functionality of the tracker from an end-user's perspective (e.g., simulating announce/scrape requests), refer to the **Smoke Testing Guide**. This guide explains how to use the official `torrust-tracker-client` tools to perform black-box testing against a running tracker instance without needing a full BitTorrent client. - **Guide**: [Smoke Testing Guide](../docs/guides/smoke-testing-guide.md) -- **When to use**: After a deployment (`make apply`) or to validate that all services are working together correctly. +- **When to use**: After a deployment (`make infra-apply` + `make app-deploy`) or to validate that all services are working together correctly. + +#### Sudo Cache Management + +The project implements intelligent sudo cache management to improve the user experience during infrastructure provisioning: + +- **Automatic prompting**: Scripts will warn users before operations requiring sudo +- **Cache preparation**: Sudo credentials are cached upfront to prevent interruptions +- **Clean output**: Password prompts occur before main operations, not mixed with output +- **Safe commands**: Uses `sudo -v` to cache credentials without executing privileged operations + +**Implementation details:** + +- Functions in `scripts/shell-utils.sh`: `ensure_sudo_cached()`, `is_sudo_cached()`, `run_with_sudo()` +- Used in: `infrastructure/scripts/fix-volume-permissions.sh`, `infrastructure/scripts/provision-infrastructure.sh`, `tests/test-e2e.sh` +- Cache duration: ~15 minutes (system default) + +**Testing the sudo cache:** + +```bash +# Test sudo cache management functions +./test-sudo-cache.sh +``` ### Security Guidelines @@ -298,9 +437,10 @@ For verifying the functionality of the tracker from an end-user's perspective (e 6. **Test a simple change**: ```bash - make apply # Deploy test VM + make infra-apply # Deploy test VM + make app-deploy # Deploy application make ssh # Verify access - make destroy # Clean up + make infra-destroy # Clean up ``` 7. **Review existing issues**: Check [GitHub Issues](https://github.com/torrust/torrust-tracker-demo/issues) for good first contributions @@ -310,7 +450,12 @@ For verifying the functionality of the tracker from an end-user's perspective (e 1. **Local testing first**: Always test infrastructure changes locally 2. **Validate syntax**: Run `make test-syntax` before committing 3. **Document changes**: Update relevant documentation -4. **Test end-to-end**: Ensure the full deployment pipeline works +4. **Test twelve-factor workflow**: Ensure both infrastructure provisioning and application deployment work + ```bash + make infra-apply # Test infrastructure provisioning + make app-deploy # Test application deployment + make health-check # Validate services + ``` ### For AI Assistants @@ -327,7 +472,7 @@ When providing assistance: Be mindful of the execution context for different types of commands. The project uses several command-line tools that must be run from specific directories: -- **`make` commands**: (e.g., `make help`, `make status`) must be run from the project root directory. +- **`make` commands**: (e.g., `make help`, `make infra-status`) must be run from the project root directory. - **OpenTofu commands**: (e.g., `tofu init`, `tofu plan`, `tofu apply`) must be run from the `infrastructure/terraform/` directory. - **Docker Compose commands**: (e.g., `docker compose up -d`, `docker compose ps`) are intended to be run _inside the deployed virtual machine_, typically from the `/home/torrust/github/torrust/torrust-tracker-demo/application` directory. @@ -421,19 +566,22 @@ This ensures that the command is executed and its output is returned to the prim **Commit Signing Requirement**: All commits MUST be signed with GPG. When performing git commits, always use the default git commit behavior (which will trigger GPG signing) rather than `--no-gpg-sign`. -**Pre-commit Linting Requirement**: ALWAYS run the linting script before committing any changes: +**Pre-commit Testing Requirement**: ALWAYS run the CI test suite before committing any changes: ```bash -./scripts/lint.sh +make test-ci ``` -This script validates: +This command runs all unit tests that don't require a virtual machine, including: + +- **Linting validation**: YAML files (yamllint), shell scripts (ShellCheck), markdown files (markdownlint) +- **Infrastructure tests**: Terraform/OpenTofu syntax, cloud-init templates, infrastructure scripts +- **Application tests**: Docker Compose syntax, application configuration, deployment scripts +- **Project tests**: Makefile syntax, project structure, tool requirements, documentation structure -- YAML files with yamllint -- Shell scripts with ShellCheck -- Markdown files with markdownlint +Only commit if all CI tests pass. If any tests fail, fix the issues before committing. -Only commit if all linting checks pass. If linting fails, fix the issues before committing. +**Note**: End-to-end tests (`make test`) are excluded from pre-commit requirements due to their longer execution time (~5-8 minutes), but running them before pushing is strongly recommended for comprehensive validation. **Best Practice**: Always ask "Would you like me to commit these changes?" before performing any git state-changing operations. diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8940886..f185cb7 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,19 +7,24 @@ on: branches: [main, develop] jobs: - lint: + ci-tests: runs-on: ubuntu-latest + name: CI-Compatible Tests steps: - name: Checkout code uses: actions/checkout@v4 - - name: Install linting tools + - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y yamllint shellcheck + sudo apt-get install -y yamllint shellcheck docker-compose sudo npm install -g markdownlint-cli - - name: Run linting script - run: | - ./scripts/lint.sh + # Install OpenTofu + curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh + chmod +x install-opentofu.sh + sudo ./install-opentofu.sh --install-method deb + + - name: Run CI test suite + run: make test-ci diff --git a/.markdownlint.json b/.markdownlint.json index d3e2b98..90739b7 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,7 +1,8 @@ { "default": true, "MD013": { - "line_length": 100 + "line_length": 100, + "tables": false }, "MD031": true, "MD032": true, diff --git a/.markdownlint.md b/.markdownlint.md new file mode 100644 index 0000000..7c6852b --- /dev/null +++ b/.markdownlint.md @@ -0,0 +1,38 @@ +# Markdownlint Configuration + +This file documents the markdownlint configuration for the project. + +## Line Length Handling + +The project enforces a 100-character line limit for markdown files (`MD013` rule). +Tables are automatically excluded from this limit to maintain readability. + +### Table Line Length Configuration + +Tables are configured to ignore line length limits globally via the `.markdownlint.json` configuration: + +```json +"MD013": { + "line_length": 100, + "tables": false +} +``` + +This means: + +- **Regular text**: Must stay within 100 characters per line +- **Tables**: Can exceed line length limits without linting errors +- **Code blocks**: Follow normal line length rules + +### Alternative Approach + +If you need to disable line length for specific non-table content, you can still use +markdownlint ignore blocks: + +```markdown + + +Very long line content that needs to exceed the normal limit + + +``` diff --git a/Makefile b/Makefile index 66251d4..21c3164 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,41 @@ -# Makefile for Torrust Tracker Local Testing Infrastructure -.PHONY: help init plan apply destroy test clean status refresh-state ssh install-deps console vm-console lint lint-yaml lint-shell lint-markdown configure-local configure-production validate-config validate-config-production deploy-local deploy-production start-services stop-services +# Makefile for Torrust Tracker Demo - Twelve-Factor App Deployment +.PHONY: help install-deps lint test clean +.PHONY: infra-init infra-plan infra-apply infra-destroy infra-status infra-refresh-state +.PHONY: app-deploy app-redeploy health-check +.PHONY: ssh console vm-console +.PHONY: configure-local configure-production validate-config # Default variables VM_NAME ?= torrust-tracker-demo +ENVIRONMENT ?= local TERRAFORM_DIR = infrastructure/terraform -TESTS_DIR = infrastructure/tests +INFRA_TESTS_DIR = infrastructure/tests +TESTS_DIR = tests +SCRIPTS_DIR = infrastructure/scripts # Help target help: ## Show this help message - @echo "Torrust Tracker Local Testing Infrastructure" + @echo "Torrust Tracker Demo - Twelve-Factor App Deployment" + @echo "" + @echo "=== TWELVE-FACTOR DEPLOYMENT WORKFLOW ===" + @echo " 1. infra-apply - Provision infrastructure (platform setup)" + @echo " 2. app-deploy - Deploy application (Build + Release + Run stages)" + @echo " 3. health-check - Validate deployment" + @echo "" + @echo "=== TESTING WORKFLOW ===" + @echo " 1. test-syntax - Fast syntax validation (30s)" + @echo " 2. test-unit - Unit tests without deployment (1-2min)" + @echo " 3. test-ci - CI-compatible tests (syntax + config + scripts)" + @echo " 4. test-local - Local-only tests (requires virtualization)" + @echo " 5. test - Full E2E test with deployment (5-8min)" @echo "" @echo "Available targets:" @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "Examples:" + @echo " make infra-apply ENVIRONMENT=local" + @echo " make app-deploy ENVIRONMENT=local" + @echo " make health-check ENVIRONMENT=local" install-deps: ## Install required dependencies (Ubuntu/Debian) @echo "Installing dependencies..." @@ -19,400 +43,171 @@ install-deps: ## Install required dependencies (Ubuntu/Debian) sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virt-viewer genisoimage sudo usermod -aG libvirt $$USER sudo usermod -aG kvm $$USER - sudo systemctl enable libvirtd - sudo systemctl start libvirtd - @echo "Setting up libvirt storage and permissions..." - @sudo virsh pool-define-as default dir --target /var/lib/libvirt/images || true - @sudo virsh pool-autostart default || true - @sudo virsh pool-start default || true - @sudo chown -R libvirt-qemu:libvirt /var/lib/libvirt/images/ || true - @sudo chmod -R 755 /var/lib/libvirt/images/ || true - @echo "Installing OpenTofu..." - curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh - chmod +x install-opentofu.sh - sudo ./install-opentofu.sh --install-method deb - rm install-opentofu.sh @echo "Dependencies installed. Please log out and log back in for group changes to take effect." -init: ## Initialize OpenTofu - @echo "Initializing OpenTofu..." - cd $(TERRAFORM_DIR) && tofu init +# ============================================================================= +# INFRASTRUCTURE PROVISIONING TARGETS (PLATFORM SETUP) +# ============================================================================= -plan: ## Show what OpenTofu will do - @echo "Planning infrastructure changes..." - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - cd $(TERRAFORM_DIR) && tofu plan -var-file="local.tfvars"; \ - else \ - echo "WARNING: No local.tfvars found. Please create it first with 'make setup-ssh-key'"; \ - exit 1; \ - fi +infra-init: ## Initialize infrastructure (Terraform init) + @echo "Initializing infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) init -apply-minimal: ## Deploy VM with minimal cloud-init configuration - @echo "Ensuring libvirt permissions are correct..." - @$(MAKE) fix-libvirt - @echo "Deploying VM with minimal configuration..." - cd $(TERRAFORM_DIR) && tofu apply -var-file="local.tfvars" -var="use_minimal_config=true" -parallelism=1 -auto-approve - @echo "Fixing permissions after deployment..." - @$(MAKE) fix-libvirt - -apply: ## Deploy the VM - @echo "Ensuring libvirt permissions are correct..." - @$(MAKE) fix-libvirt - @echo "Deploying VM..." - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - echo "Using local SSH key configuration..."; \ - cd $(TERRAFORM_DIR) && tofu apply -var-file="local.tfvars" -parallelism=1 -auto-approve; \ - else \ - echo "WARNING: No local.tfvars found. Creating with placeholder..."; \ - echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ - echo "Please edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key and run 'make apply' again"; \ - exit 1; \ - fi - @echo "Fixing permissions after deployment..." - @$(MAKE) fix-libvirt +infra-plan: ## Plan infrastructure changes + @echo "Planning infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) plan + +infra-apply: ## Provision infrastructure (platform setup) + @echo "Provisioning infrastructure for $(ENVIRONMENT)..." + @echo "⚠️ This command may prompt for your password for sudo operations" + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) apply -destroy: ## Destroy the VM - @echo "Destroying VM..." - cd $(TERRAFORM_DIR) && tofu destroy -auto-approve +infra-destroy: ## Destroy infrastructure + @echo "Destroying infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) destroy -status: ## Show current infrastructure status - @echo "Infrastructure status:" - cd $(TERRAFORM_DIR) && tofu show +infra-status: ## Show infrastructure status + @echo "Infrastructure status for $(ENVIRONMENT):" + @cd $(TERRAFORM_DIR) && tofu show -no-color | grep -E "(vm_ip|vm_status)" || echo "No infrastructure found" -refresh-state: ## Refresh Terraform state to detect IP changes +infra-refresh-state: ## Refresh Terraform state to detect IP changes @echo "Refreshing Terraform state..." - cd $(TERRAFORM_DIR) && tofu refresh - @echo "Updated outputs:" - cd $(TERRAFORM_DIR) && tofu output + @cd $(TERRAFORM_DIR) && tofu refresh + +# ============================================================================= +# TWELVE-FACTOR APPLICATION TARGETS (BUILD + RELEASE + RUN STAGES) +# ============================================================================= + +app-deploy: ## Deploy application (Twelve-Factor Build + Release + Run stages) + @echo "Deploying application for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) + +app-redeploy: ## Redeploy application without infrastructure changes + @echo "Redeploying application for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) + +health-check: ## Validate deployment health + @echo "Running health check for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/health-check.sh $(ENVIRONMENT) + +# ============================================================================= +# VM ACCESS AND DEBUGGING +# ============================================================================= ssh: ## SSH into the VM - @echo "Connecting to VM..." - @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Connecting to $$VM_IP..."; \ - ssh torrust@$$VM_IP; \ + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) && \ + if [ -n "$$VM_IP" ] && [ "$$VM_IP" != "No IP assigned yet" ]; then \ + echo "Connecting to VM: $$VM_IP"; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP; \ else \ - echo "Could not get VM IP. Is the VM deployed?"; \ + echo "Error: VM IP not available. Run 'make infra-status' to check infrastructure."; \ exit 1; \ fi -test: ## Run all tests - @echo "Running infrastructure tests..." - $(TESTS_DIR)/test-local-setup.sh full-test +ssh-clean: ## Clean SSH known_hosts for VM (fixes host key verification warnings) + @echo "Cleaning SSH known_hosts for VM..." + @$(SCRIPTS_DIR)/ssh-utils.sh clean -test-prereq: ## Test prerequisites only - @echo "Testing prerequisites..." - $(TESTS_DIR)/test-local-setup.sh prerequisites +ssh-prepare: ## Clean SSH known_hosts and test connectivity + @echo "Preparing SSH connection to VM..." + @$(SCRIPTS_DIR)/ssh-utils.sh prepare -check-libvirt: ## Check libvirt installation and permissions - @echo "Checking libvirt setup..." - @echo "1. Checking if libvirt service is running:" - @sudo systemctl status libvirtd --no-pager -l || echo "libvirtd not running" - @echo "" - @echo "2. Checking user groups:" - @groups | grep -q libvirt && echo "✓ User is in libvirt group" || echo "✗ User is NOT in libvirt group" - @groups | grep -q kvm && echo "✓ User is in kvm group" || echo "✗ User is NOT in kvm group" - @echo "" - @echo "3. Testing libvirt access:" - @virsh list --all >/dev/null 2>&1 && echo "✓ User can access libvirt" || echo "✗ User cannot access libvirt (try 'sudo virsh list')" - @echo "" - @echo "4. Checking default network:" - @virsh net-list --all 2>/dev/null | grep -q default && echo "✓ Default network exists" || echo "✗ Default network missing" - @echo "" - @echo "5. Checking KVM support:" - @test -r /dev/kvm && echo "✓ KVM device accessible" || echo "✗ KVM device not accessible" - @echo "" - @echo "If you see any ✗ marks, run 'make fix-libvirt' to attempt fixes" +console: ## Access VM console (text-based) + @echo "Accessing VM console..." + @virsh console $(VM_NAME) || echo "VM console not accessible. Try 'make vm-console' for graphical console." -fix-libvirt: ## Fix common libvirt permission issues - @echo "Setting up user-friendly libvirt configuration..." - @infrastructure/scripts/setup-user-libvirt.sh - @echo "Attempting to fix libvirt permissions..." - @echo "Adding user to required groups..." - sudo usermod -aG libvirt $$USER - sudo usermod -aG kvm $$USER - @echo "Starting libvirt service..." - sudo systemctl enable libvirtd - sudo systemctl start libvirtd - @echo "Checking if default network needs to be started..." - @sudo virsh net-list --all | grep -q "default.*inactive" && sudo virsh net-start default || true - @sudo virsh net-autostart default 2>/dev/null || true - @echo "" - @echo "✓ Fix attempt completed!" - @echo "IMPORTANT: You need to log out and log back in (or run 'newgrp libvirt') for group changes to take effect" - @echo "Then run 'make check-libvirt' to verify the fixes worked" +vm-console: ## Access VM graphical console (requires GUI) + @echo "Opening graphical VM console..." + @virt-viewer --connect qemu:///system $(VM_NAME) & -test-syntax: ## Test configuration syntax only - @echo "Testing configuration syntax..." - $(TESTS_DIR)/test-local-setup.sh syntax +# ============================================================================= +# CONFIGURATION MANAGEMENT +# ============================================================================= -lint: ## Run all linting checks (yamllint, shellcheck, markdownlint) - @echo "Running linting checks..." - ./scripts/lint.sh +configure-local: ## Generate local environment configuration + @echo "Configuring local environment..." + $(SCRIPTS_DIR)/configure-env.sh local -lint-yaml: ## Run only yamllint - @echo "Running yamllint..." - ./scripts/lint.sh --yaml +configure-production: ## Generate production environment configuration + @echo "Configuring production environment..." + $(SCRIPTS_DIR)/configure-env.sh production -lint-shell: ## Run only shellcheck - @echo "Running shellcheck..." - ./scripts/lint.sh --shell +validate-config: ## Validate configuration for all environments + @echo "Validating configuration..." + $(SCRIPTS_DIR)/validate-config.sh -lint-markdown: ## Run only markdownlint - @echo "Running markdownlint..." - ./scripts/lint.sh --markdown +# ============================================================================= +# TESTING AND QUALITY ASSURANCE +# ============================================================================= -test-integration: ## Run integration tests (requires deployed VM) - @echo "Running integration tests..." - $(TESTS_DIR)/test-integration.sh full-test +test-prereq: ## Test system prerequisites for development + @echo "Testing prerequisites..." + $(INFRA_TESTS_DIR)/test-unit-infrastructure.sh vm-prereq -deploy-test: ## Deploy VM for testing (without cleanup) - @echo "Deploying test VM..." - $(TESTS_DIR)/test-local-setup.sh deploy +test: ## Run comprehensive end-to-end test (follows integration guide) + @echo "Running comprehensive end-to-end test..." + $(TESTS_DIR)/test-e2e.sh $(ENVIRONMENT) + +test-unit: ## Run unit tests (configuration, scripts, syntax) + @echo "Running unit tests..." + @echo "1. Configuration and syntax validation..." + $(INFRA_TESTS_DIR)/test-unit-config.sh + @echo "2. Infrastructure scripts validation..." + $(INFRA_TESTS_DIR)/test-unit-scripts.sh + +test-syntax: ## Run syntax validation only + @echo "Running syntax validation..." + ./scripts/lint.sh -clean: ## Clean up temporary files +test-ci: ## Run CI-compatible tests (syntax + config + scripts) + @echo "Running CI-compatible tests..." + $(INFRA_TESTS_DIR)/test-ci.sh + +test-local: ## Run local-only tests (requires virtualization) + @echo "Running local-only tests..." + $(INFRA_TESTS_DIR)/test-local.sh + +test-legacy: ## [DEPRECATED] Legacy test scripts have been removed + @echo "⚠️ DEPRECATED: Legacy test scripts have been removed" + @echo "Use 'make test-unit' for unit tests or 'make test' for E2E tests" + @exit 1 + +lint: test-syntax ## Run all linting (alias for test-syntax) + +clean: ## Clean up temporary files and caches @echo "Cleaning up..." - rm -f $(TERRAFORM_DIR)/.terraform.lock.hcl - rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup - rm -f install-opentofu.sh - rm -f /tmp/torrust-infrastructure-test.log - -clean-and-fix: ## Clean up all VMs and fix libvirt permissions - @echo "Cleaning up VMs and fixing permissions..." - @echo "1. Stopping and undefining any existing VMs:" - @for vm in $$(virsh list --all --name 2>/dev/null | grep -v '^$$'); do \ - echo " Cleaning up VM: $$vm"; \ - virsh destroy $$vm 2>/dev/null || true; \ - virsh undefine $$vm 2>/dev/null || true; \ - done - @echo "2. Removing OpenTofu state:" - @cd $(TERRAFORM_DIR) && rm -f terraform.tfstate terraform.tfstate.backup .terraform.lock.hcl 2>/dev/null || true - @echo "3. Cleaning libvirt images:" - @sudo rm -f /var/lib/libvirt/images/torrust-tracker-demo* /var/lib/libvirt/images/ubuntu-24.04-base.qcow2 2>/dev/null || true - @echo "4. Cleaning application storage (generated configuration files):" - @if [ -d "application/storage" ]; then \ - echo " WARNING: This will delete all generated configuration files in application/storage/"; \ - echo " This includes nginx configs, tracker configs, and any cached data."; \ - echo " These files will be regenerated when you run 'make configure-local'."; \ - read -p " Do you want to delete application/storage? (y/N): " confirm; \ - if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \ - echo " Removing application/storage..."; \ - rm -rf application/storage; \ - echo " ✓ Application storage cleaned"; \ - else \ - echo " Skipping application/storage cleanup"; \ - fi; \ - else \ - echo " No application/storage directory found"; \ - fi - @echo "5. Fixing libvirt setup:" - @$(MAKE) fix-libvirt - @echo "✓ Clean up complete. You can now run 'make apply' safely." - -# New target for setting up SSH key -setup-ssh-key: ## Setup local SSH key configuration - @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ - echo "Local SSH configuration already exists at $(TERRAFORM_DIR)/local.tfvars"; \ - echo "Current configuration:"; \ - cat $(TERRAFORM_DIR)/local.tfvars; \ - else \ - echo "Creating local SSH key configuration..."; \ - echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ - echo ""; \ - echo "✓ Created $(TERRAFORM_DIR)/local.tfvars"; \ - echo ""; \ - echo "Next steps:"; \ - echo "1. Get your SSH public key:"; \ - echo " cat ~/.ssh/id_rsa.pub"; \ - echo " # or cat ~/.ssh/id_ed25519.pub"; \ - echo ""; \ - echo "2. Edit the file and replace the placeholder:"; \ - echo " vim $(TERRAFORM_DIR)/local.tfvars"; \ - echo ""; \ - echo "3. Deploy the VM:"; \ - echo " make apply"; \ - fi + @rm -rf $(TERRAFORM_DIR)/.terraform + @rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup + @echo "Clean completed" -restart-and-monitor: ## Destroy, deploy fresh, and monitor cloud-init - @echo "🔄 Complete restart: destroying existing VM..." - @$(MAKE) destroy || true - @echo "🚀 Deploying fresh VM..." - @$(MAKE) apply & - @echo "⏳ Waiting 10 seconds for VM to start..." - @sleep 10 - @echo "📡 Starting cloud-init monitoring..." - @$(MAKE) monitor-cloud-init - -fresh-start: restart-and-monitor ## Alias for restart-and-monitor - -# Development targets -dev-setup: install-deps init fix-libvirt setup-ssh-key ## Complete development setup - @echo "Development environment setup complete!" - @echo "Next steps:" - @echo "1. Log out and log back in for group changes" - @echo "2. Edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key" - @echo "3. Run 'make test-prereq' to verify setup" - @echo "4. Run 'make apply' to deploy a VM" - -quick-test: test-prereq test-syntax ## Quick test without VM deployment - @echo "Quick tests completed!" - -# Help for specific workflows -workflow-help: ## Show common workflows - @echo "Common workflows:" - @echo "" - @echo "1. First-time setup:" - @echo " make dev-setup" - @echo " # Log out and log back in" - @echo " # Edit infrastructure/cloud-init/user-data.yaml to add your SSH key" - @echo " make test-prereq" - @echo "" - @echo "2. Deploy and test:" - @echo " make apply" - @echo " make ssh" - @echo " make destroy" - @echo "" - @echo "3. Run full test suite:" - @echo " make test" - @echo "" - @echo "4. Run integration tests:" - @echo " make apply" - @echo " make test-integration" - @echo " make destroy" - @echo "" - @echo "5. Development cycle:" - @echo " make plan # Review changes" - @echo " make apply # Deploy" - @echo " make ssh # Test manually" - @echo " make destroy # Clean up" - -monitor-cloud-init: ## Monitor cloud-init progress in real-time - @echo "Monitoring cloud-init progress..." - @./infrastructure/scripts/monitor-cloud-init.sh - -vm-restart: ## Restart the VM - @echo "Restarting VM..." - virsh shutdown $(VM_NAME) - @echo "Waiting for shutdown..." - @sleep 5 - virsh start $(VM_NAME) - @echo "VM restarted" - -# CI/CD specific targets -ci-test-syntax: ## Test syntax for CI (with dummy values) - @echo "Testing syntax for CI environment..." - @echo "Creating temporary config with dummy values..." - @cd $(TERRAFORM_DIR) && \ - echo 'ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC dummy-key-for-ci-testing"' > ci-test.tfvars && \ - tofu init && \ - tofu validate && \ - rm ci-test.tfvars - @echo "Testing cloud-init templates..." - @CI=true $(TESTS_DIR)/test-local-setup.sh syntax - @echo "Testing cloud-init YAML syntax with yamllint..." - @if command -v yamllint >/dev/null 2>&1; then \ - yamllint -c .yamllint-ci.yml infrastructure/cloud-init/network-config.yaml && \ - yamllint -c .yamllint-ci.yml infrastructure/cloud-init/meta-data.yaml && \ - cd infrastructure/cloud-init && \ - sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data.yaml.tpl > /tmp/user-data-test.yaml && \ - sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data-minimal.yaml.tpl > /tmp/user-data-minimal-test.yaml && \ - yamllint -c ../../.yamllint-ci.yml /tmp/user-data-test.yaml && \ - yamllint -c ../../.yamllint-ci.yml /tmp/user-data-minimal-test.yaml && \ - rm -f /tmp/user-data-test.yaml /tmp/user-data-minimal-test.yaml; \ - else \ - echo "yamllint not available, skipping additional YAML validation"; \ - fi +# ============================================================================= +# LEGACY COMPATIBILITY (DEPRECATED) +# ============================================================================= -vm-ip: ## Show VM IP address - @echo "Getting VM IP address..." - @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "VM IP: $$VM_IP"; \ - else \ - echo "VM IP not assigned yet or VM not running"; \ - echo "VM status:"; \ - virsh list --all | grep $(VM_NAME) || echo "VM not found"; \ - fi +# These targets are maintained for backward compatibility but are deprecated +# Use the twelve-factor targets above instead -vm-info: ## Show detailed VM network information - @echo "VM Network Information:" - @echo "======================" - @virsh list --all | grep $(VM_NAME) | head -1 || echo "VM not found" - @echo "" - @echo "Network interfaces:" - @virsh domifaddr $(VM_NAME) 2>/dev/null || echo "No network information available" +init: infra-init ## [DEPRECATED] Use infra-init instead + @echo "⚠️ DEPRECATED: Use 'make infra-init' instead" + +plan: infra-plan ## [DEPRECATED] Use infra-plan instead + @echo "⚠️ DEPRECATED: Use 'make infra-plan' instead" + +apply: ## [DEPRECATED] Use infra-apply + app-deploy instead + @echo "⚠️ DEPRECATED: This target combines infrastructure and application deployment" + @echo " For twelve-factor compliance, use:" + @echo " 1. make infra-apply ENVIRONMENT=$(ENVIRONMENT)" + @echo " 2. make app-deploy ENVIRONMENT=$(ENVIRONMENT)" @echo "" - @echo "DHCP leases:" - @virsh net-dhcp-leases default 2>/dev/null | grep $(VM_NAME) || echo "No DHCP lease found" + @echo "Proceeding with legacy deployment..." + @make infra-apply ENVIRONMENT=$(ENVIRONMENT) + @make app-deploy ENVIRONMENT=$(ENVIRONMENT) -console: ## Access VM console (text-based) - @echo "Connecting to VM console..." - @echo "Use Ctrl+] to exit console" - @virsh console $(VM_NAME) - -vm-console: ## Access VM graphical console (GUI) - @echo "Opening VM graphical console..." - @if command -v virt-viewer >/dev/null 2>&1; then \ - virt-viewer $(VM_NAME) || virt-viewer spice://127.0.0.1:5900; \ - else \ - echo "virt-viewer not found. Please install it:"; \ - echo " sudo apt install virt-viewer"; \ - fi +destroy: infra-destroy ## [DEPRECATED] Use infra-destroy instead + @echo "⚠️ DEPRECATED: Use 'make infra-destroy' instead" -# Configuration Management Targets -configure-local: ## Generate local environment configuration - @echo "Generating local environment configuration..." - @infrastructure/scripts/configure-env.sh local - -configure-production: ## Generate production environment configuration (requires secrets) - @echo "Generating production environment configuration..." - @infrastructure/scripts/configure-env.sh production - -validate-config: ## Validate generated configuration files - @echo "Validating configuration files..." - @infrastructure/scripts/validate-config.sh local - -validate-config-production: ## Validate production configuration files - @echo "Validating production configuration files..." - @infrastructure/scripts/validate-config.sh production - -# Deployment workflow targets -deploy-local: configure-local ## Deploy VM and configure for local environment - @echo "Deploying local environment..." - @$(MAKE) apply - @echo "Waiting for VM to be ready..." - @sleep 30 - @echo "Starting application services..." - @$(MAKE) start-services - -deploy-production: configure-production ## Deploy and configure for production environment (requires secrets) - @echo "Deploying production environment..." - @$(MAKE) apply - @echo "Waiting for VM to be ready..." - @sleep 30 - @echo "Starting application services..." - @$(MAKE) start-services - -start-services: ## Start Docker Compose services in the VM - @echo "Starting Docker Compose services..." - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ - VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Starting services on $$VM_IP..."; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose up -d'; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi +status: infra-status ## [DEPRECATED] Use infra-status instead + @echo "⚠️ DEPRECATED: Use 'make infra-status' instead" -stop-services: ## Stop Docker Compose services in the VM - @echo "Stopping Docker Compose services..." - @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ - VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ - if [ -n "$$VM_IP" ]; then \ - echo "Stopping services on $$VM_IP..."; \ - ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose down'; \ - else \ - echo "Could not get VM IP. Is the VM deployed?"; \ - exit 1; \ - fi +refresh-state: infra-refresh-state ## [DEPRECATED] Use infra-refresh-state instead + @echo "⚠️ DEPRECATED: Use 'make infra-refresh-state' instead" diff --git a/Makefile.backup b/Makefile.backup new file mode 100644 index 0000000..33a3396 --- /dev/null +++ b/Makefile.backup @@ -0,0 +1,536 @@ +# Makefile for Torrust Tracker Demo - Twelve-Factor App Deployment +.PHONY: help install-deps lint test clean +.PHONY: infra-init infra-plan infra-apply infra-destroy infra-status infra-refresh-state +.PHONY: app-deploy app-redeploy health-check +.PHONY: ssh console vm-console +.PHONY: configure-local configure-production validate-config + +# Default variables +VM_NAME ?= torrust-tracker-demo +ENVIRONMENT ?= local +TERRAFORM_DIR = infrastructure/terraform +TESTS_DIR = infrastructure/tests +SCRIPTS_DIR = infrastructure/scripts + +# Help target +help: ## Show this help message + @echo "Torrust Tracker Demo - Twelve-Factor App Deployment" + @echo "" + @echo "=== TWELVE-FACTOR DEPLOYMENT WORKFLOW ===" + @echo " 1. infra-apply - Provision infrastructure (Build stage)" + @echo " 2. app-deploy - Deploy application (Release + Run stages)" + @echo " 3. health-check - Validate deployment" + @echo "" + @echo "Available targets:" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "Examples:" + @echo " make infra-apply ENVIRONMENT=local" + @echo " make app-deploy ENVIRONMENT=local" + @echo " make health-check ENVIRONMENT=local" + +install-deps: ## Install required dependencies (Ubuntu/Debian) + @echo "Installing dependencies..." + sudo apt update + sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virt-viewer genisoimage + sudo usermod -aG libvirt $$USER + sudo usermod -aG kvm $$USER + @echo "Dependencies installed. Please log out and log back in for group changes to take effect." + +# ============================================================================= +# TWELVE-FACTOR INFRASTRUCTURE TARGETS (BUILD STAGE) +# ============================================================================= + +infra-init: ## Initialize infrastructure (Terraform init) + @echo "Initializing infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) init + +infra-plan: ## Plan infrastructure changes + @echo "Planning infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) plan + +infra-apply: ## Provision infrastructure (Twelve-Factor Build stage) + @echo "Provisioning infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) apply + +infra-destroy: ## Destroy infrastructure + @echo "Destroying infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) destroy + +infra-status: ## Show infrastructure status + @echo "Infrastructure status for $(ENVIRONMENT):" + @cd $(TERRAFORM_DIR) && tofu show -no-color | grep -E "(vm_ip|vm_status)" || echo "No infrastructure found" + +infra-refresh-state: ## Refresh Terraform state to detect IP changes + @echo "Refreshing Terraform state..." + @cd $(TERRAFORM_DIR) && tofu refresh -auto-approve + +# ============================================================================= +# TWELVE-FACTOR APPLICATION TARGETS (RELEASE + RUN STAGES) +# ============================================================================= + +app-deploy: ## Deploy application (Twelve-Factor Release + Run stages) + @echo "Deploying application for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) + +app-redeploy: ## Redeploy application without infrastructure changes + @echo "Redeploying application for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) + +health-check: ## Validate deployment health + @echo "Running health check for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/health-check.sh $(ENVIRONMENT) + +# ============================================================================= +# VM ACCESS AND DEBUGGING +# ============================================================================= + +ssh: ## SSH into the VM + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) && \ + if [ -n "$$VM_IP" ] && [ "$$VM_IP" != "No IP assigned yet" ]; then \ + echo "Connecting to VM: $$VM_IP"; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP; \ + else \ + echo "Error: VM IP not available. Run 'make infra-status' to check infrastructure."; \ + exit 1; \ + fi + +console: ## Access VM console (text-based) + @echo "Accessing VM console..." + @virsh console $(VM_NAME) || echo "VM console not accessible. Try 'make vm-console' for graphical console." + +vm-console: ## Access VM graphical console (requires GUI) + @echo "Opening graphical VM console..." + @virt-viewer --connect qemu:///system $(VM_NAME) & + +# ============================================================================= +# CONFIGURATION MANAGEMENT +# ============================================================================= + +configure-local: ## Generate local environment configuration + @echo "Configuring local environment..." + $(SCRIPTS_DIR)/configure-env.sh local + +configure-production: ## Generate production environment configuration + @echo "Configuring production environment..." + $(SCRIPTS_DIR)/configure-env.sh production + +validate-config: ## Validate configuration for all environments + @echo "Validating configuration..." + $(SCRIPTS_DIR)/validate-config.sh + +# ============================================================================= +# TESTING AND QUALITY ASSURANCE +# ============================================================================= + +test: ## Run comprehensive test suite + @echo "Running comprehensive test suite..." + $(TESTS_DIR)/test-local-setup.sh + +test-syntax: ## Run syntax validation only + @echo "Running syntax validation..." + ./scripts/lint.sh + +lint: test-syntax ## Run all linting (alias for test-syntax) + +clean: ## Clean up temporary files and caches + @echo "Cleaning up..." + @rm -rf $(TERRAFORM_DIR)/.terraform + @rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup + @echo "Clean completed" + +# ============================================================================= +# LEGACY COMPATIBILITY (DEPRECATED) +# ============================================================================= + +# These targets are maintained for backward compatibility but are deprecated +# Use the twelve-factor targets above instead + +init: infra-init ## [DEPRECATED] Use infra-init instead + @echo "⚠️ DEPRECATED: Use 'make infra-init' instead" + +plan: infra-plan ## [DEPRECATED] Use infra-plan instead + @echo "⚠️ DEPRECATED: Use 'make infra-plan' instead" + +apply: ## [DEPRECATED] Use infra-apply + app-deploy instead + @echo "⚠️ DEPRECATED: This target combines infrastructure and application deployment" + @echo " For twelve-factor compliance, use:" + @echo " 1. make infra-apply ENVIRONMENT=$(ENVIRONMENT)" + @echo " 2. make app-deploy ENVIRONMENT=$(ENVIRONMENT)" + @echo "" + @echo "Proceeding with legacy deployment..." + @make infra-apply ENVIRONMENT=$(ENVIRONMENT) + @make app-deploy ENVIRONMENT=$(ENVIRONMENT) + +destroy: infra-destroy ## [DEPRECATED] Use infra-destroy instead + @echo "⚠️ DEPRECATED: Use 'make infra-destroy' instead" + +status: infra-status ## [DEPRECATED] Use infra-status instead + @echo "⚠️ DEPRECATED: Use 'make infra-status' instead" + +refresh-state: infra-refresh-state ## [DEPRECATED] Use infra-refresh-state instead + @echo "⚠️ DEPRECATED: Use 'make infra-refresh-state' instead" + @echo "Fixing permissions after deployment..." + @$(MAKE) fix-libvirt + +apply: ## Deploy the VM + @echo "Ensuring libvirt permissions are correct..." + @$(MAKE) fix-libvirt + @echo "Deploying VM..." + @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ + echo "Using local SSH key configuration..."; \ + cd $(TERRAFORM_DIR) && tofu apply -var-file="local.tfvars" -parallelism=1 -auto-approve; \ + else \ + echo "WARNING: No local.tfvars found. Creating with placeholder..."; \ + echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ + echo "Please edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key and run 'make apply' again"; \ + exit 1; \ + fi + @echo "Fixing permissions after deployment..." + @$(MAKE) fix-libvirt + +destroy: ## Destroy the VM + @echo "Destroying VM..." + cd $(TERRAFORM_DIR) && tofu destroy -auto-approve + +status: ## Show current infrastructure status + @echo "Infrastructure status:" + cd $(TERRAFORM_DIR) && tofu show + +refresh-state: ## Refresh Terraform state to detect IP changes + @echo "Refreshing Terraform state..." + cd $(TERRAFORM_DIR) && tofu refresh + @echo "Updated outputs:" + cd $(TERRAFORM_DIR) && tofu output + +ssh: ## SSH into the VM + @echo "Connecting to VM..." + @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "Connecting to $$VM_IP..."; \ + ssh torrust@$$VM_IP; \ + else \ + echo "Could not get VM IP. Is the VM deployed?"; \ + exit 1; \ + fi + +test: ## Run all tests + @echo "Running infrastructure tests..." + $(TESTS_DIR)/test-local-setup.sh full-test + +test-prereq: ## Test prerequisites only + @echo "Testing prerequisites..." + $(TESTS_DIR)/test-local-setup.sh prerequisites + +check-libvirt: ## Check libvirt installation and permissions + @echo "Checking libvirt setup..." + @echo "1. Checking if libvirt service is running:" + @sudo systemctl status libvirtd --no-pager -l || echo "libvirtd not running" + @echo "" + @echo "2. Checking user groups:" + @groups | grep -q libvirt && echo "✓ User is in libvirt group" || echo "✗ User is NOT in libvirt group" + @groups | grep -q kvm && echo "✓ User is in kvm group" || echo "✗ User is NOT in kvm group" + @echo "" + @echo "3. Testing libvirt access:" + @virsh list --all >/dev/null 2>&1 && echo "✓ User can access libvirt" || echo "✗ User cannot access libvirt (try 'sudo virsh list')" + @echo "" + @echo "4. Checking default network:" + @virsh net-list --all 2>/dev/null | grep -q default && echo "✓ Default network exists" || echo "✗ Default network missing" + @echo "" + @echo "5. Checking KVM support:" + @test -r /dev/kvm && echo "✓ KVM device accessible" || echo "✗ KVM device not accessible" + @echo "" + @echo "If you see any ✗ marks, run 'make fix-libvirt' to attempt fixes" + +fix-libvirt: ## Fix common libvirt permission issues + @echo "Setting up user-friendly libvirt configuration..." + @infrastructure/scripts/setup-user-libvirt.sh + @echo "Attempting to fix libvirt permissions..." + @echo "Adding user to required groups..." + sudo usermod -aG libvirt $$USER + sudo usermod -aG kvm $$USER + @echo "Starting libvirt service..." + sudo systemctl enable libvirtd + sudo systemctl start libvirtd + @echo "Checking if default network needs to be started..." + @sudo virsh net-list --all | grep -q "default.*inactive" && sudo virsh net-start default || true + @sudo virsh net-autostart default 2>/dev/null || true + @echo "" + @echo "✓ Fix attempt completed!" + @echo "IMPORTANT: You need to log out and log back in (or run 'newgrp libvirt') for group changes to take effect" + @echo "Then run 'make check-libvirt' to verify the fixes worked" + +test-syntax: ## Test configuration syntax only + @echo "Testing configuration syntax..." + $(TESTS_DIR)/test-local-setup.sh syntax + +lint: ## Run all linting checks (yamllint, shellcheck, markdownlint) + @echo "Running linting checks..." + ./scripts/lint.sh + +lint-yaml: ## Run only yamllint + @echo "Running yamllint..." + ./scripts/lint.sh --yaml + +lint-shell: ## Run only shellcheck + @echo "Running shellcheck..." + ./scripts/lint.sh --shell + +lint-markdown: ## Run only markdownlint + @echo "Running markdownlint..." + ./scripts/lint.sh --markdown + +test-integration: ## Run integration tests (requires deployed VM) + @echo "Running integration tests..." + $(TESTS_DIR)/test-integration.sh full-test + +deploy-test: ## Deploy VM for testing (without cleanup) + @echo "Deploying test VM..." + $(TESTS_DIR)/test-local-setup.sh deploy + +clean: ## Clean up temporary files + @echo "Cleaning up..." + rm -f $(TERRAFORM_DIR)/.terraform.lock.hcl + rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup + rm -f install-opentofu.sh + rm -f /tmp/torrust-infrastructure-test.log + +clean-and-fix: ## Clean up all VMs and fix libvirt permissions + @echo "Cleaning up VMs and fixing permissions..." + @echo "1. Stopping and undefining any existing VMs:" + @for vm in $$(virsh list --all --name 2>/dev/null | grep -v '^$$'); do \ + echo " Cleaning up VM: $$vm"; \ + virsh destroy $$vm 2>/dev/null || true; \ + virsh undefine $$vm 2>/dev/null || true; \ + done + @echo "2. Removing OpenTofu state:" + @cd $(TERRAFORM_DIR) && rm -f terraform.tfstate terraform.tfstate.backup .terraform.lock.hcl 2>/dev/null || true + @echo "3. Cleaning libvirt images:" + @sudo rm -f /var/lib/libvirt/images/torrust-tracker-demo* /var/lib/libvirt/images/ubuntu-24.04-base.qcow2 2>/dev/null || true + @echo "4. Cleaning application storage (generated configuration files):" + @if [ -d "application/storage" ]; then \ + echo " WARNING: This will delete all generated configuration files in application/storage/"; \ + echo " This includes nginx configs, tracker configs, and any cached data."; \ + echo " These files will be regenerated when you run 'make configure-local'."; \ + read -p " Do you want to delete application/storage? (y/N): " confirm; \ + if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \ + echo " Removing application/storage..."; \ + rm -rf application/storage; \ + echo " ✓ Application storage cleaned"; \ + else \ + echo " Skipping application/storage cleanup"; \ + fi; \ + else \ + echo " No application/storage directory found"; \ + fi + @echo "5. Fixing libvirt setup:" + @$(MAKE) fix-libvirt + @echo "✓ Clean up complete. You can now run 'make apply' safely." + +# New target for setting up SSH key +setup-ssh-key: ## Setup local SSH key configuration + @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ + echo "Local SSH configuration already exists at $(TERRAFORM_DIR)/local.tfvars"; \ + echo "Current configuration:"; \ + cat $(TERRAFORM_DIR)/local.tfvars; \ + else \ + echo "Creating local SSH key configuration..."; \ + echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ + echo ""; \ + echo "✓ Created $(TERRAFORM_DIR)/local.tfvars"; \ + echo ""; \ + echo "Next steps:"; \ + echo "1. Get your SSH public key:"; \ + echo " cat ~/.ssh/id_rsa.pub"; \ + echo " # or cat ~/.ssh/id_ed25519.pub"; \ + echo ""; \ + echo "2. Edit the file and replace the placeholder:"; \ + echo " vim $(TERRAFORM_DIR)/local.tfvars"; \ + echo ""; \ + echo "3. Deploy the VM:"; \ + echo " make apply"; \ + fi + +restart-and-monitor: ## Destroy, deploy fresh, and monitor cloud-init + @echo "🔄 Complete restart: destroying existing VM..." + @$(MAKE) destroy || true + @echo "🚀 Deploying fresh VM..." + @$(MAKE) apply & + @echo "⏳ Waiting 10 seconds for VM to start..." + @sleep 10 + @echo "📡 Starting cloud-init monitoring..." + @$(MAKE) monitor-cloud-init + +fresh-start: restart-and-monitor ## Alias for restart-and-monitor + +# Development targets +dev-setup: install-deps init fix-libvirt setup-ssh-key ## Complete development setup + @echo "Development environment setup complete!" + @echo "Next steps:" + @echo "1. Log out and log back in for group changes" + @echo "2. Edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key" + @echo "3. Run 'make test-prereq' to verify setup" + @echo "4. Run 'make apply' to deploy a VM" + +quick-test: test-prereq test-syntax ## Quick test without VM deployment + @echo "Quick tests completed!" + +# Help for specific workflows +workflow-help: ## Show common workflows + @echo "Common workflows:" + @echo "" + @echo "1. First-time setup:" + @echo " make dev-setup" + @echo " # Log out and log back in" + @echo " # Edit infrastructure/cloud-init/user-data.yaml to add your SSH key" + @echo " make test-prereq" + @echo "" + @echo "2. Deploy and test:" + @echo " make apply" + @echo " make ssh" + @echo " make destroy" + @echo "" + @echo "3. Run full test suite:" + @echo " make test" + @echo "" + @echo "4. Run integration tests:" + @echo " make apply" + @echo " make test-integration" + @echo " make destroy" + @echo "" + @echo "5. Development cycle:" + @echo " make plan # Review changes" + @echo " make apply # Deploy" + @echo " make ssh # Test manually" + @echo " make destroy # Clean up" + +monitor-cloud-init: ## Monitor cloud-init progress in real-time + @echo "Monitoring cloud-init progress..." + @./infrastructure/scripts/monitor-cloud-init.sh + +vm-restart: ## Restart the VM + @echo "Restarting VM..." + virsh shutdown $(VM_NAME) + @echo "Waiting for shutdown..." + @sleep 5 + virsh start $(VM_NAME) + @echo "VM restarted" + +# CI/CD specific targets +ci-test-syntax: ## Test syntax for CI (with dummy values) + @echo "Testing syntax for CI environment..." + @echo "Creating temporary config with dummy values..." + @cd $(TERRAFORM_DIR) && \ + echo 'ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC dummy-key-for-ci-testing"' > ci-test.tfvars && \ + tofu init && \ + tofu validate && \ + rm ci-test.tfvars + @echo "Testing cloud-init templates..." + @CI=true $(TESTS_DIR)/test-local-setup.sh syntax + @echo "Testing cloud-init YAML syntax with yamllint..." + @if command -v yamllint >/dev/null 2>&1; then \ + yamllint -c .yamllint-ci.yml infrastructure/cloud-init/network-config.yaml && \ + yamllint -c .yamllint-ci.yml infrastructure/cloud-init/meta-data.yaml && \ + cd infrastructure/cloud-init && \ + sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data.yaml.tpl > /tmp/user-data-test.yaml && \ + sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data-minimal.yaml.tpl > /tmp/user-data-minimal-test.yaml && \ + yamllint -c ../../.yamllint-ci.yml /tmp/user-data-test.yaml && \ + yamllint -c ../../.yamllint-ci.yml /tmp/user-data-minimal-test.yaml && \ + rm -f /tmp/user-data-test.yaml /tmp/user-data-minimal-test.yaml; \ + else \ + echo "yamllint not available, skipping additional YAML validation"; \ + fi + +vm-ip: ## Show VM IP address + @echo "Getting VM IP address..." + @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "VM IP: $$VM_IP"; \ + else \ + echo "VM IP not assigned yet or VM not running"; \ + echo "VM status:"; \ + virsh list --all | grep $(VM_NAME) || echo "VM not found"; \ + fi + +vm-info: ## Show detailed VM network information + @echo "VM Network Information:" + @echo "======================" + @virsh list --all | grep $(VM_NAME) | head -1 || echo "VM not found" + @echo "" + @echo "Network interfaces:" + @virsh domifaddr $(VM_NAME) 2>/dev/null || echo "No network information available" + @echo "" + @echo "DHCP leases:" + @virsh net-dhcp-leases default 2>/dev/null | grep $(VM_NAME) || echo "No DHCP lease found" + +console: ## Access VM console (text-based) + @echo "Connecting to VM console..." + @echo "Use Ctrl+] to exit console" + @virsh console $(VM_NAME) + +vm-console: ## Access VM graphical console (GUI) + @echo "Opening VM graphical console..." + @if command -v virt-viewer >/dev/null 2>&1; then \ + virt-viewer $(VM_NAME) || virt-viewer spice://127.0.0.1:5900; \ + else \ + echo "virt-viewer not found. Please install it:"; \ + echo " sudo apt install virt-viewer"; \ + fi + +# Configuration Management Targets +configure-local: ## Generate local environment configuration + @echo "Generating local environment configuration..." + @infrastructure/scripts/configure-env.sh local + +configure-production: ## Generate production environment configuration (requires secrets) + @echo "Generating production environment configuration..." + @infrastructure/scripts/configure-env.sh production + +validate-config: ## Validate generated configuration files + @echo "Validating configuration files..." + @infrastructure/scripts/validate-config.sh local + +validate-config-production: ## Validate production configuration files + @echo "Validating production configuration files..." + @infrastructure/scripts/validate-config.sh production + +# Deployment workflow targets +deploy-local: configure-local ## Deploy VM and configure for local environment + @echo "Deploying local environment..." + @$(MAKE) apply + @echo "Waiting for VM to be ready..." + @sleep 30 + @echo "Starting application services..." + @$(MAKE) start-services + +deploy-production: configure-production ## Deploy and configure for production environment (requires secrets) + @echo "Deploying production environment..." + @$(MAKE) apply + @echo "Waiting for VM to be ready..." + @sleep 30 + @echo "Starting application services..." + @$(MAKE) start-services + +start-services: ## Start Docker Compose services in the VM + @echo "Starting Docker Compose services..." + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ + VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "Starting services on $$VM_IP..."; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose up -d'; \ + else \ + echo "Could not get VM IP. Is the VM deployed?"; \ + exit 1; \ + fi + +stop-services: ## Stop Docker Compose services in the VM + @echo "Stopping Docker Compose services..." + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ + VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "Stopping services on $$VM_IP..."; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose down'; \ + else \ + echo "Could not get VM IP. Is the VM deployed?"; \ + exit 1; \ + fi diff --git a/Makefile.old b/Makefile.old new file mode 100644 index 0000000..33a3396 --- /dev/null +++ b/Makefile.old @@ -0,0 +1,536 @@ +# Makefile for Torrust Tracker Demo - Twelve-Factor App Deployment +.PHONY: help install-deps lint test clean +.PHONY: infra-init infra-plan infra-apply infra-destroy infra-status infra-refresh-state +.PHONY: app-deploy app-redeploy health-check +.PHONY: ssh console vm-console +.PHONY: configure-local configure-production validate-config + +# Default variables +VM_NAME ?= torrust-tracker-demo +ENVIRONMENT ?= local +TERRAFORM_DIR = infrastructure/terraform +TESTS_DIR = infrastructure/tests +SCRIPTS_DIR = infrastructure/scripts + +# Help target +help: ## Show this help message + @echo "Torrust Tracker Demo - Twelve-Factor App Deployment" + @echo "" + @echo "=== TWELVE-FACTOR DEPLOYMENT WORKFLOW ===" + @echo " 1. infra-apply - Provision infrastructure (Build stage)" + @echo " 2. app-deploy - Deploy application (Release + Run stages)" + @echo " 3. health-check - Validate deployment" + @echo "" + @echo "Available targets:" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "Examples:" + @echo " make infra-apply ENVIRONMENT=local" + @echo " make app-deploy ENVIRONMENT=local" + @echo " make health-check ENVIRONMENT=local" + +install-deps: ## Install required dependencies (Ubuntu/Debian) + @echo "Installing dependencies..." + sudo apt update + sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virt-viewer genisoimage + sudo usermod -aG libvirt $$USER + sudo usermod -aG kvm $$USER + @echo "Dependencies installed. Please log out and log back in for group changes to take effect." + +# ============================================================================= +# TWELVE-FACTOR INFRASTRUCTURE TARGETS (BUILD STAGE) +# ============================================================================= + +infra-init: ## Initialize infrastructure (Terraform init) + @echo "Initializing infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) init + +infra-plan: ## Plan infrastructure changes + @echo "Planning infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) plan + +infra-apply: ## Provision infrastructure (Twelve-Factor Build stage) + @echo "Provisioning infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) apply + +infra-destroy: ## Destroy infrastructure + @echo "Destroying infrastructure for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/provision-infrastructure.sh $(ENVIRONMENT) destroy + +infra-status: ## Show infrastructure status + @echo "Infrastructure status for $(ENVIRONMENT):" + @cd $(TERRAFORM_DIR) && tofu show -no-color | grep -E "(vm_ip|vm_status)" || echo "No infrastructure found" + +infra-refresh-state: ## Refresh Terraform state to detect IP changes + @echo "Refreshing Terraform state..." + @cd $(TERRAFORM_DIR) && tofu refresh -auto-approve + +# ============================================================================= +# TWELVE-FACTOR APPLICATION TARGETS (RELEASE + RUN STAGES) +# ============================================================================= + +app-deploy: ## Deploy application (Twelve-Factor Release + Run stages) + @echo "Deploying application for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) + +app-redeploy: ## Redeploy application without infrastructure changes + @echo "Redeploying application for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/deploy-app.sh $(ENVIRONMENT) + +health-check: ## Validate deployment health + @echo "Running health check for $(ENVIRONMENT)..." + $(SCRIPTS_DIR)/health-check.sh $(ENVIRONMENT) + +# ============================================================================= +# VM ACCESS AND DEBUGGING +# ============================================================================= + +ssh: ## SSH into the VM + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) && \ + if [ -n "$$VM_IP" ] && [ "$$VM_IP" != "No IP assigned yet" ]; then \ + echo "Connecting to VM: $$VM_IP"; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP; \ + else \ + echo "Error: VM IP not available. Run 'make infra-status' to check infrastructure."; \ + exit 1; \ + fi + +console: ## Access VM console (text-based) + @echo "Accessing VM console..." + @virsh console $(VM_NAME) || echo "VM console not accessible. Try 'make vm-console' for graphical console." + +vm-console: ## Access VM graphical console (requires GUI) + @echo "Opening graphical VM console..." + @virt-viewer --connect qemu:///system $(VM_NAME) & + +# ============================================================================= +# CONFIGURATION MANAGEMENT +# ============================================================================= + +configure-local: ## Generate local environment configuration + @echo "Configuring local environment..." + $(SCRIPTS_DIR)/configure-env.sh local + +configure-production: ## Generate production environment configuration + @echo "Configuring production environment..." + $(SCRIPTS_DIR)/configure-env.sh production + +validate-config: ## Validate configuration for all environments + @echo "Validating configuration..." + $(SCRIPTS_DIR)/validate-config.sh + +# ============================================================================= +# TESTING AND QUALITY ASSURANCE +# ============================================================================= + +test: ## Run comprehensive test suite + @echo "Running comprehensive test suite..." + $(TESTS_DIR)/test-local-setup.sh + +test-syntax: ## Run syntax validation only + @echo "Running syntax validation..." + ./scripts/lint.sh + +lint: test-syntax ## Run all linting (alias for test-syntax) + +clean: ## Clean up temporary files and caches + @echo "Cleaning up..." + @rm -rf $(TERRAFORM_DIR)/.terraform + @rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup + @echo "Clean completed" + +# ============================================================================= +# LEGACY COMPATIBILITY (DEPRECATED) +# ============================================================================= + +# These targets are maintained for backward compatibility but are deprecated +# Use the twelve-factor targets above instead + +init: infra-init ## [DEPRECATED] Use infra-init instead + @echo "⚠️ DEPRECATED: Use 'make infra-init' instead" + +plan: infra-plan ## [DEPRECATED] Use infra-plan instead + @echo "⚠️ DEPRECATED: Use 'make infra-plan' instead" + +apply: ## [DEPRECATED] Use infra-apply + app-deploy instead + @echo "⚠️ DEPRECATED: This target combines infrastructure and application deployment" + @echo " For twelve-factor compliance, use:" + @echo " 1. make infra-apply ENVIRONMENT=$(ENVIRONMENT)" + @echo " 2. make app-deploy ENVIRONMENT=$(ENVIRONMENT)" + @echo "" + @echo "Proceeding with legacy deployment..." + @make infra-apply ENVIRONMENT=$(ENVIRONMENT) + @make app-deploy ENVIRONMENT=$(ENVIRONMENT) + +destroy: infra-destroy ## [DEPRECATED] Use infra-destroy instead + @echo "⚠️ DEPRECATED: Use 'make infra-destroy' instead" + +status: infra-status ## [DEPRECATED] Use infra-status instead + @echo "⚠️ DEPRECATED: Use 'make infra-status' instead" + +refresh-state: infra-refresh-state ## [DEPRECATED] Use infra-refresh-state instead + @echo "⚠️ DEPRECATED: Use 'make infra-refresh-state' instead" + @echo "Fixing permissions after deployment..." + @$(MAKE) fix-libvirt + +apply: ## Deploy the VM + @echo "Ensuring libvirt permissions are correct..." + @$(MAKE) fix-libvirt + @echo "Deploying VM..." + @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ + echo "Using local SSH key configuration..."; \ + cd $(TERRAFORM_DIR) && tofu apply -var-file="local.tfvars" -parallelism=1 -auto-approve; \ + else \ + echo "WARNING: No local.tfvars found. Creating with placeholder..."; \ + echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ + echo "Please edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key and run 'make apply' again"; \ + exit 1; \ + fi + @echo "Fixing permissions after deployment..." + @$(MAKE) fix-libvirt + +destroy: ## Destroy the VM + @echo "Destroying VM..." + cd $(TERRAFORM_DIR) && tofu destroy -auto-approve + +status: ## Show current infrastructure status + @echo "Infrastructure status:" + cd $(TERRAFORM_DIR) && tofu show + +refresh-state: ## Refresh Terraform state to detect IP changes + @echo "Refreshing Terraform state..." + cd $(TERRAFORM_DIR) && tofu refresh + @echo "Updated outputs:" + cd $(TERRAFORM_DIR) && tofu output + +ssh: ## SSH into the VM + @echo "Connecting to VM..." + @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "Connecting to $$VM_IP..."; \ + ssh torrust@$$VM_IP; \ + else \ + echo "Could not get VM IP. Is the VM deployed?"; \ + exit 1; \ + fi + +test: ## Run all tests + @echo "Running infrastructure tests..." + $(TESTS_DIR)/test-local-setup.sh full-test + +test-prereq: ## Test prerequisites only + @echo "Testing prerequisites..." + $(TESTS_DIR)/test-local-setup.sh prerequisites + +check-libvirt: ## Check libvirt installation and permissions + @echo "Checking libvirt setup..." + @echo "1. Checking if libvirt service is running:" + @sudo systemctl status libvirtd --no-pager -l || echo "libvirtd not running" + @echo "" + @echo "2. Checking user groups:" + @groups | grep -q libvirt && echo "✓ User is in libvirt group" || echo "✗ User is NOT in libvirt group" + @groups | grep -q kvm && echo "✓ User is in kvm group" || echo "✗ User is NOT in kvm group" + @echo "" + @echo "3. Testing libvirt access:" + @virsh list --all >/dev/null 2>&1 && echo "✓ User can access libvirt" || echo "✗ User cannot access libvirt (try 'sudo virsh list')" + @echo "" + @echo "4. Checking default network:" + @virsh net-list --all 2>/dev/null | grep -q default && echo "✓ Default network exists" || echo "✗ Default network missing" + @echo "" + @echo "5. Checking KVM support:" + @test -r /dev/kvm && echo "✓ KVM device accessible" || echo "✗ KVM device not accessible" + @echo "" + @echo "If you see any ✗ marks, run 'make fix-libvirt' to attempt fixes" + +fix-libvirt: ## Fix common libvirt permission issues + @echo "Setting up user-friendly libvirt configuration..." + @infrastructure/scripts/setup-user-libvirt.sh + @echo "Attempting to fix libvirt permissions..." + @echo "Adding user to required groups..." + sudo usermod -aG libvirt $$USER + sudo usermod -aG kvm $$USER + @echo "Starting libvirt service..." + sudo systemctl enable libvirtd + sudo systemctl start libvirtd + @echo "Checking if default network needs to be started..." + @sudo virsh net-list --all | grep -q "default.*inactive" && sudo virsh net-start default || true + @sudo virsh net-autostart default 2>/dev/null || true + @echo "" + @echo "✓ Fix attempt completed!" + @echo "IMPORTANT: You need to log out and log back in (or run 'newgrp libvirt') for group changes to take effect" + @echo "Then run 'make check-libvirt' to verify the fixes worked" + +test-syntax: ## Test configuration syntax only + @echo "Testing configuration syntax..." + $(TESTS_DIR)/test-local-setup.sh syntax + +lint: ## Run all linting checks (yamllint, shellcheck, markdownlint) + @echo "Running linting checks..." + ./scripts/lint.sh + +lint-yaml: ## Run only yamllint + @echo "Running yamllint..." + ./scripts/lint.sh --yaml + +lint-shell: ## Run only shellcheck + @echo "Running shellcheck..." + ./scripts/lint.sh --shell + +lint-markdown: ## Run only markdownlint + @echo "Running markdownlint..." + ./scripts/lint.sh --markdown + +test-integration: ## Run integration tests (requires deployed VM) + @echo "Running integration tests..." + $(TESTS_DIR)/test-integration.sh full-test + +deploy-test: ## Deploy VM for testing (without cleanup) + @echo "Deploying test VM..." + $(TESTS_DIR)/test-local-setup.sh deploy + +clean: ## Clean up temporary files + @echo "Cleaning up..." + rm -f $(TERRAFORM_DIR)/.terraform.lock.hcl + rm -f $(TERRAFORM_DIR)/terraform.tfstate.backup + rm -f install-opentofu.sh + rm -f /tmp/torrust-infrastructure-test.log + +clean-and-fix: ## Clean up all VMs and fix libvirt permissions + @echo "Cleaning up VMs and fixing permissions..." + @echo "1. Stopping and undefining any existing VMs:" + @for vm in $$(virsh list --all --name 2>/dev/null | grep -v '^$$'); do \ + echo " Cleaning up VM: $$vm"; \ + virsh destroy $$vm 2>/dev/null || true; \ + virsh undefine $$vm 2>/dev/null || true; \ + done + @echo "2. Removing OpenTofu state:" + @cd $(TERRAFORM_DIR) && rm -f terraform.tfstate terraform.tfstate.backup .terraform.lock.hcl 2>/dev/null || true + @echo "3. Cleaning libvirt images:" + @sudo rm -f /var/lib/libvirt/images/torrust-tracker-demo* /var/lib/libvirt/images/ubuntu-24.04-base.qcow2 2>/dev/null || true + @echo "4. Cleaning application storage (generated configuration files):" + @if [ -d "application/storage" ]; then \ + echo " WARNING: This will delete all generated configuration files in application/storage/"; \ + echo " This includes nginx configs, tracker configs, and any cached data."; \ + echo " These files will be regenerated when you run 'make configure-local'."; \ + read -p " Do you want to delete application/storage? (y/N): " confirm; \ + if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \ + echo " Removing application/storage..."; \ + rm -rf application/storage; \ + echo " ✓ Application storage cleaned"; \ + else \ + echo " Skipping application/storage cleanup"; \ + fi; \ + else \ + echo " No application/storage directory found"; \ + fi + @echo "5. Fixing libvirt setup:" + @$(MAKE) fix-libvirt + @echo "✓ Clean up complete. You can now run 'make apply' safely." + +# New target for setting up SSH key +setup-ssh-key: ## Setup local SSH key configuration + @if [ -f $(TERRAFORM_DIR)/local.tfvars ]; then \ + echo "Local SSH configuration already exists at $(TERRAFORM_DIR)/local.tfvars"; \ + echo "Current configuration:"; \ + cat $(TERRAFORM_DIR)/local.tfvars; \ + else \ + echo "Creating local SSH key configuration..."; \ + echo 'ssh_public_key = "REPLACE_WITH_YOUR_SSH_PUBLIC_KEY"' > $(TERRAFORM_DIR)/local.tfvars; \ + echo ""; \ + echo "✓ Created $(TERRAFORM_DIR)/local.tfvars"; \ + echo ""; \ + echo "Next steps:"; \ + echo "1. Get your SSH public key:"; \ + echo " cat ~/.ssh/id_rsa.pub"; \ + echo " # or cat ~/.ssh/id_ed25519.pub"; \ + echo ""; \ + echo "2. Edit the file and replace the placeholder:"; \ + echo " vim $(TERRAFORM_DIR)/local.tfvars"; \ + echo ""; \ + echo "3. Deploy the VM:"; \ + echo " make apply"; \ + fi + +restart-and-monitor: ## Destroy, deploy fresh, and monitor cloud-init + @echo "🔄 Complete restart: destroying existing VM..." + @$(MAKE) destroy || true + @echo "🚀 Deploying fresh VM..." + @$(MAKE) apply & + @echo "⏳ Waiting 10 seconds for VM to start..." + @sleep 10 + @echo "📡 Starting cloud-init monitoring..." + @$(MAKE) monitor-cloud-init + +fresh-start: restart-and-monitor ## Alias for restart-and-monitor + +# Development targets +dev-setup: install-deps init fix-libvirt setup-ssh-key ## Complete development setup + @echo "Development environment setup complete!" + @echo "Next steps:" + @echo "1. Log out and log back in for group changes" + @echo "2. Edit $(TERRAFORM_DIR)/local.tfvars with your SSH public key" + @echo "3. Run 'make test-prereq' to verify setup" + @echo "4. Run 'make apply' to deploy a VM" + +quick-test: test-prereq test-syntax ## Quick test without VM deployment + @echo "Quick tests completed!" + +# Help for specific workflows +workflow-help: ## Show common workflows + @echo "Common workflows:" + @echo "" + @echo "1. First-time setup:" + @echo " make dev-setup" + @echo " # Log out and log back in" + @echo " # Edit infrastructure/cloud-init/user-data.yaml to add your SSH key" + @echo " make test-prereq" + @echo "" + @echo "2. Deploy and test:" + @echo " make apply" + @echo " make ssh" + @echo " make destroy" + @echo "" + @echo "3. Run full test suite:" + @echo " make test" + @echo "" + @echo "4. Run integration tests:" + @echo " make apply" + @echo " make test-integration" + @echo " make destroy" + @echo "" + @echo "5. Development cycle:" + @echo " make plan # Review changes" + @echo " make apply # Deploy" + @echo " make ssh # Test manually" + @echo " make destroy # Clean up" + +monitor-cloud-init: ## Monitor cloud-init progress in real-time + @echo "Monitoring cloud-init progress..." + @./infrastructure/scripts/monitor-cloud-init.sh + +vm-restart: ## Restart the VM + @echo "Restarting VM..." + virsh shutdown $(VM_NAME) + @echo "Waiting for shutdown..." + @sleep 5 + virsh start $(VM_NAME) + @echo "VM restarted" + +# CI/CD specific targets +ci-test-syntax: ## Test syntax for CI (with dummy values) + @echo "Testing syntax for CI environment..." + @echo "Creating temporary config with dummy values..." + @cd $(TERRAFORM_DIR) && \ + echo 'ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC dummy-key-for-ci-testing"' > ci-test.tfvars && \ + tofu init && \ + tofu validate && \ + rm ci-test.tfvars + @echo "Testing cloud-init templates..." + @CI=true $(TESTS_DIR)/test-local-setup.sh syntax + @echo "Testing cloud-init YAML syntax with yamllint..." + @if command -v yamllint >/dev/null 2>&1; then \ + yamllint -c .yamllint-ci.yml infrastructure/cloud-init/network-config.yaml && \ + yamllint -c .yamllint-ci.yml infrastructure/cloud-init/meta-data.yaml && \ + cd infrastructure/cloud-init && \ + sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data.yaml.tpl > /tmp/user-data-test.yaml && \ + sed 's/$${ssh_public_key}/ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/' user-data-minimal.yaml.tpl > /tmp/user-data-minimal-test.yaml && \ + yamllint -c ../../.yamllint-ci.yml /tmp/user-data-test.yaml && \ + yamllint -c ../../.yamllint-ci.yml /tmp/user-data-minimal-test.yaml && \ + rm -f /tmp/user-data-test.yaml /tmp/user-data-minimal-test.yaml; \ + else \ + echo "yamllint not available, skipping additional YAML validation"; \ + fi + +vm-ip: ## Show VM IP address + @echo "Getting VM IP address..." + @VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "VM IP: $$VM_IP"; \ + else \ + echo "VM IP not assigned yet or VM not running"; \ + echo "VM status:"; \ + virsh list --all | grep $(VM_NAME) || echo "VM not found"; \ + fi + +vm-info: ## Show detailed VM network information + @echo "VM Network Information:" + @echo "======================" + @virsh list --all | grep $(VM_NAME) | head -1 || echo "VM not found" + @echo "" + @echo "Network interfaces:" + @virsh domifaddr $(VM_NAME) 2>/dev/null || echo "No network information available" + @echo "" + @echo "DHCP leases:" + @virsh net-dhcp-leases default 2>/dev/null | grep $(VM_NAME) || echo "No DHCP lease found" + +console: ## Access VM console (text-based) + @echo "Connecting to VM console..." + @echo "Use Ctrl+] to exit console" + @virsh console $(VM_NAME) + +vm-console: ## Access VM graphical console (GUI) + @echo "Opening VM graphical console..." + @if command -v virt-viewer >/dev/null 2>&1; then \ + virt-viewer $(VM_NAME) || virt-viewer spice://127.0.0.1:5900; \ + else \ + echo "virt-viewer not found. Please install it:"; \ + echo " sudo apt install virt-viewer"; \ + fi + +# Configuration Management Targets +configure-local: ## Generate local environment configuration + @echo "Generating local environment configuration..." + @infrastructure/scripts/configure-env.sh local + +configure-production: ## Generate production environment configuration (requires secrets) + @echo "Generating production environment configuration..." + @infrastructure/scripts/configure-env.sh production + +validate-config: ## Validate generated configuration files + @echo "Validating configuration files..." + @infrastructure/scripts/validate-config.sh local + +validate-config-production: ## Validate production configuration files + @echo "Validating production configuration files..." + @infrastructure/scripts/validate-config.sh production + +# Deployment workflow targets +deploy-local: configure-local ## Deploy VM and configure for local environment + @echo "Deploying local environment..." + @$(MAKE) apply + @echo "Waiting for VM to be ready..." + @sleep 30 + @echo "Starting application services..." + @$(MAKE) start-services + +deploy-production: configure-production ## Deploy and configure for production environment (requires secrets) + @echo "Deploying production environment..." + @$(MAKE) apply + @echo "Waiting for VM to be ready..." + @sleep 30 + @echo "Starting application services..." + @$(MAKE) start-services + +start-services: ## Start Docker Compose services in the VM + @echo "Starting Docker Compose services..." + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ + VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "Starting services on $$VM_IP..."; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose up -d'; \ + else \ + echo "Could not get VM IP. Is the VM deployed?"; \ + exit 1; \ + fi + +stop-services: ## Stop Docker Compose services in the VM + @echo "Stopping Docker Compose services..." + @VM_IP=$$(cd $(TERRAFORM_DIR) && tofu output -raw vm_ip 2>/dev/null) || \ + VM_IP=$$(virsh domifaddr $(VM_NAME) | grep ipv4 | awk '{print $$4}' | cut -d'/' -f1); \ + if [ -n "$$VM_IP" ]; then \ + echo "Stopping services on $$VM_IP..."; \ + ssh -o StrictHostKeyChecking=no torrust@$$VM_IP 'cd /home/torrust/github/torrust/torrust-tracker-demo/application && docker compose down'; \ + else \ + echo "Could not get VM IP. Is the VM deployed?"; \ + exit 1; \ + fi diff --git a/README.md b/README.md index a5e3066..15cf19d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Testing](https://github.com/torrust/torrust-tracker-demo/actions/workflows/testing.yml/badge.svg)](https://github.com/torrust/torrust-tracker-demo/actions/workflows/testing.yml) + # Torrust Tracker Demo This repo contains all the configuration needed to run the live Torrust Tracker demo. @@ -12,7 +14,7 @@ It's also used to track issues in production. ## 🏗️ Repository Structure -This repository is organized into two main concerns: +This repository is organized into distinct concerns: ### 📦 [`infrastructure/`](infrastructure/) @@ -21,7 +23,7 @@ This repository is organized into two main concerns: - OpenTofu/Terraform for VM provisioning - cloud-init templates for system setup - libvirt/KVM for local testing -- Infrastructure testing and validation +- Infrastructure unit tests and validation ### 🚀 [`application/`](application/) @@ -32,6 +34,14 @@ This repository is organized into two main concerns: - Nginx, Prometheus, Grafana setup - Application scripts and utilities +### 🧪 [`tests/`](tests/) + +**End-to-end testing** - Complete system validation + +- E2E deployment workflow tests +- Integration testing automation +- System-wide validation + ### 📚 [`docs/`](docs/) **Project documentation** - Guides, security, and reference materials diff --git a/application/compose.yaml b/application/compose.yaml index 1b3afad..688ab57 100644 --- a/application/compose.yaml +++ b/application/compose.yaml @@ -20,7 +20,6 @@ services: container_name: proxy restart: unless-stopped networks: - - frontend_network - backend_network ports: - "80:80" @@ -133,7 +132,6 @@ services: - mysql networks: - frontend_network: {} backend_network: {} volumes: diff --git a/application/tests/README.md b/application/tests/README.md new file mode 100644 index 0000000..64c163b --- /dev/null +++ b/application/tests/README.md @@ -0,0 +1,121 @@ +# Application Tests + +This directory contains tests specific to application deployment and configuration validation. + +## Purpose + +Tests in this directory focus on: + +- **Application configuration validation** (Docker Compose, environment files) +- **Application directory structure verification** +- **Deployment script validation** +- **Service configuration testing** (Grafana, monitoring configs) + +## Test Scope + +These tests validate application components **without performing actual deployment**. +They are static validation tests that ensure: + +- Configuration files are syntactically correct +- Required files and directories exist +- Scripts have proper permissions +- Service configurations are valid + +## Test Organization + +### Current Tests + +- `test-unit-application.sh` - Main application validation test suite + +### Test Categories + +1. **Docker Compose Validation** - Ensures `compose.yaml` is valid +2. **Configuration Validation** - Checks `.env` templates and config files +3. **Structure Validation** - Verifies application directory structure +4. **Script Validation** - Checks deployment scripts exist and are executable +5. **Service Configuration** - Validates Grafana dashboards and other service configs + +## Usage + +```bash +# Run all application tests +./test-unit-application.sh + +# Run specific test categories +./test-unit-application.sh docker # Docker Compose only +./test-unit-application.sh config # Configuration only +./test-unit-application.sh structure # Structure only +./test-unit-application.sh scripts # Scripts only +./test-unit-application.sh grafana # Grafana config only +``` + +## Test Organization Guidelines + +### What Belongs Here + +✅ **Application layer tests**: + +- Docker Compose file validation +- Application configuration files (`.env`, service configs) +- Application deployment scripts +- Service-specific configurations (Grafana, Prometheus configs) +- Application directory structure + +### What Does NOT Belong Here + +❌ **Infrastructure tests** (belong in `infrastructure/tests/`): + +- Terraform/OpenTofu configurations +- Cloud-init templates +- Infrastructure provisioning scripts +- VM-level configurations + +❌ **Project-wide tests** (belong in `tests/` at project root): + +- Root-level Makefile +- Project structure spanning multiple layers +- Tool availability checks +- Cross-cutting documentation + +## Integration with Other Test Layers + +This test suite is part of a three-layer testing architecture: + +1. **Infrastructure Tests** (`infrastructure/tests/`) - Infrastructure provisioning +2. **Application Tests** (`application/tests/`) - Application deployment (this directory) +3. **Project Tests** (`tests/`) - Cross-cutting project validation + +Each layer focuses on its specific concerns and can be run independently. + +## Adding New Tests + +When adding new application tests: + +1. **Categorize correctly** - Ensure the test belongs to the application layer +2. **Follow naming conventions** - Use `test_function_name()` format +3. **Add to main suite** - Include in `run_application_tests()` function +4. **Update help** - Add command options if needed +5. **Document purpose** - Explain what the test validates + +### Example Test Function + +```bash +test_new_application_feature() { + log_info "Testing new application feature..." + + local failed=0 + # Test implementation here + + if [[ ${failed} -eq 0 ]]; then + log_success "New application feature validation passed" + fi + + return ${failed} +} +``` + +## Related Documentation + +- [Infrastructure Tests](../infrastructure/tests/README.md) +- [Project Tests](../tests/README.md) +- [Testing Strategy](../docs/testing/test-strategy.md) diff --git a/application/tests/test-unit-application.sh b/application/tests/test-unit-application.sh new file mode 100755 index 0000000..609c2d6 --- /dev/null +++ b/application/tests/test-unit-application.sh @@ -0,0 +1,269 @@ +#!/bin/bash +# Unit tests for application deployment validation +# Focus: Validate application configuration, Docker Compose, and deployment-related files +# Scope: No actual deployment, only static validation of application components + +set -euo pipefail + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APPLICATION_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +TEST_LOG_FILE="/tmp/torrust-unit-application-test.log" + +# Source shared shell utilities +# shellcheck source=../../scripts/shell-utils.sh +source "${PROJECT_ROOT}/scripts/shell-utils.sh" + +# Set log file for tee output +export SHELL_UTILS_LOG_FILE="${TEST_LOG_FILE}" + +# Initialize test log +init_test_log() { + init_log_file "${TEST_LOG_FILE}" "Unit Tests - Application Deployment Validation" + log_info "Application Root: ${APPLICATION_ROOT}" +} + +# Test Docker Compose syntax validation +test_docker_compose_syntax() { + log_info "Testing Docker Compose syntax validation..." + + local compose_file="${APPLICATION_ROOT}/compose.yaml" + local failed=0 + + if [[ ! -f "${compose_file}" ]]; then + log_error "Docker Compose file not found: ${compose_file}" + return 1 + fi + + cd "${APPLICATION_ROOT}" + + # Test Docker Compose syntax + if command -v docker >/dev/null 2>&1; then + if docker compose config >/dev/null 2>&1; then + log_success "Docker Compose configuration is valid" + else + log_error "Docker Compose validation failed" + failed=1 + fi + else + log_warning "Docker not found - skipping Docker Compose validation" + fi + + return ${failed} +} + +# Test application configuration files +test_application_config() { + log_info "Testing application configuration files..." + + local failed=0 + local config_files=( + ".env.production" + "compose.yaml" + ) + + cd "${APPLICATION_ROOT}" + + for config_file in "${config_files[@]}"; do + if [[ ! -f "${config_file}" ]]; then + log_error "Required configuration file missing: ${config_file}" + failed=1 + else + log_info "Found configuration file: ${config_file}" + fi + done + + # Test that configuration templates exist + local template_dir="${APPLICATION_ROOT}/config/templates" + if [[ -d "${template_dir}" ]]; then + log_info "Configuration templates directory found: ${template_dir}" + else + log_warning "Configuration templates directory not found: ${template_dir}" + fi + + if [[ ${failed} -eq 0 ]]; then + log_success "Application configuration files are present" + fi + + return ${failed} +} + +# Test application directory structure +test_application_structure() { + log_info "Testing application directory structure..." + + local failed=0 + local required_paths=( + "compose.yaml" + "config" + "share" + "storage" + "docs" + ) + + cd "${APPLICATION_ROOT}" + + for path in "${required_paths[@]}"; do + if [[ ! -e "${path}" ]]; then + log_error "Required application path missing: ${path}" + failed=1 + fi + done + + if [[ ${failed} -eq 0 ]]; then + log_success "Application directory structure is valid" + fi + + return ${failed} +} + +# Test deployment scripts +test_deployment_scripts() { + log_info "Testing deployment scripts..." + + local failed=0 + local scripts_dir="${APPLICATION_ROOT}/share/bin" + + if [[ ! -d "${scripts_dir}" ]]; then + log_warning "Scripts directory not found: ${scripts_dir}" + return 0 + fi + + # Check for key deployment scripts + local key_scripts=( + "deploy-torrust-tracker-demo.com.sh" + ) + + for script in "${key_scripts[@]}"; do + local script_path="${scripts_dir}/${script}" + if [[ -f "${script_path}" ]]; then + if [[ -x "${script_path}" ]]; then + log_info "Found executable deployment script: ${script}" + else + log_warning "Deployment script exists but is not executable: ${script}" + fi + else + log_warning "Deployment script not found: ${script}" + fi + done + + log_success "Deployment scripts validation completed" + return ${failed} +} + +# Test Grafana configuration +test_grafana_config() { + log_info "Testing Grafana configuration..." + + local failed=0 + local grafana_dir="${APPLICATION_ROOT}/share/grafana" + + if [[ ! -d "${grafana_dir}" ]]; then + log_warning "Grafana directory not found: ${grafana_dir}" + return 0 + fi + + # Check for dashboard files + if find "${grafana_dir}" -name "*.json" -type f | grep -q .; then + log_success "Grafana dashboard files found" + else + log_warning "No Grafana dashboard files found in ${grafana_dir}" + fi + + return ${failed} +} + +# Run all application unit tests +run_application_tests() { + local failed=0 + + init_test_log + + log_info "Running application deployment unit tests..." + log_info "Application directory: ${APPLICATION_ROOT}" + + # Run all application tests + test_application_structure || failed=1 + test_application_config || failed=1 + test_docker_compose_syntax || failed=1 + test_deployment_scripts || failed=1 + test_grafana_config || failed=1 + + # Final result + if [[ ${failed} -eq 0 ]]; then + log_success "All application unit tests passed!" + log_info "Test log: ${TEST_LOG_FILE}" + return 0 + else + log_error "Some application unit tests failed!" + log_error "Check test log for details: ${TEST_LOG_FILE}" + return 1 + fi +} + +# Help function +show_help() { + cat <