Skip to content

Parameterize TofuTemplateRenderer by Provider #212

@josecelano

Description

@josecelano

Parameterize TofuTemplateRenderer by Provider

Epic: #205 - Add Hetzner Provider Support
Phase: 1 - Make LXD Explicit
Dependencies: #206, #207, #208

Overview

This task makes the TofuTemplateRenderer provider-aware so it can render templates for different infrastructure providers (LXD, Hetzner). Currently, the renderer has hardcoded paths to tofu/lxd/ templates. After this change, it will dynamically select the template directory based on the provider specified in the environment configuration.

This is Task 4 in Phase 1 ("Make LXD Explicit") of the Hetzner Provider Support epic.

Specification

See detailed specification: docs/issues/212-parameterize-tofu-template-renderer-by-provider.md

🏗️ Architecture Requirements

DDD Layer: Infrastructure
Module Path: src/infrastructure/external_tools/tofu/template/
Pattern: Template Renderer with Strategy Pattern for provider-specific rendering

Key Design Decisions

  1. No template sharing between providers: Each provider has its own independent templates. This makes providers easier to add, maintain, and debug.
  2. Provider-specific code isolation: All Rust code specific to a provider must be in its own module (e.g., wrappers/hetzner/, wrappers/lxd/).
  3. Only variables.tfvars.tera needed: The application generates the final variables.tfvars from the Tera template at runtime.

Anti-Patterns to Avoid

  • ❌ Hardcoding provider-specific paths in generic code
  • ❌ Complex conditionals that grow with each new provider
  • ❌ Sharing templates between providers (each provider must be self-contained)
  • ❌ Placing provider-specific Rust code in shared modules (use wrappers/{provider}/ structure)

Implementation Plan

Phase 1: Create and Validate Hetzner OpenTofu Templates (estimated 2-3 hours)

Goal: Create static OpenTofu files and test them manually with real Hetzner infrastructure before any Rust code changes. This isolates infrastructure issues from code issues.

  • Task 1.1: Create static Hetzner OpenTofu configuration (versions.tf, main.tf, variables.tfvars, cloud-init.yml)
  • Task 1.2: Test OpenTofu configuration manually (tofu init, validate, plan, apply, output -json, SSH test, destroy)
  • Task 1.3: Document any issues and fixes

Phase 2: Integrate Hetzner Templates with Rust Code (estimated 3-4 hours)

Goal: Convert working static files to Tera templates and integrate with the Rust codebase.

  • Task 2.1: Create Hetzner Tera templates in templates/tofu/hetzner/ (independent from LXD, no sharing)
  • Task 2.2: Create Hetzner template wrappers in wrappers/hetzner/ module (HetznerVariablesTemplate, HetznerCloudInitTemplate)
  • Task 2.3: Refactor TofuTemplateRenderer to accept Provider parameter and select templates dynamically
  • Task 2.4: Update callers of TofuTemplateRenderer to pass provider
  • Task 2.5: Register static Hetzner templates per docs/contributing/templates.md

Hetzner Template Structure

templates/tofu/hetzner/
├── versions.tf           # Provider version requirements (hcloud ~> 1.47, time ~> 0.11)
├── main.tf               # Server resource, outputs, and variable definitions
├── variables.tfvars.tera # Tera template for variable values (generated at runtime)
└── cloud-init.yml.tera   # Cloud-init template (independent from LXD)

Note: Only variables.tfvars.tera is needed for the Tera rendering pipeline.

Acceptance Criteria

Quality Checks:

  • Pre-commit checks pass: ./scripts/pre-commit.sh

Phase 1 Criteria (Manual Testing):

  • Static Hetzner OpenTofu files created in test directory
  • tofu init, validate, plan, apply work correctly
  • tofu output -json produces instance_info structure matching Rust parser
  • SSH access and tofu destroy work

Phase 2 Criteria (Rust Integration):

  • Hetzner Tera templates created in templates/tofu/hetzner/ (no sharing with LXD)
  • HetznerVariablesTemplate and HetznerCloudInitTemplate wrappers in wrappers/hetzner/ module
  • TofuTemplateRenderer accepts Provider parameter
  • All existing LXD E2E tests still pass
  • Provider-specific code isolated in provider modules

Related

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions