Skip to content

refactor: [#212] parameterize TofuTemplateRenderer by provider#213

Merged
josecelano merged 14 commits intomainfrom
212-parameterize-tofu-template-renderer-by-provider
Dec 3, 2025
Merged

refactor: [#212] parameterize TofuTemplateRenderer by provider#213
josecelano merged 14 commits intomainfrom
212-parameterize-tofu-template-renderer-by-provider

Conversation

@josecelano
Copy link
Copy Markdown
Member

Summary

This PR fixes the hardcoded LXD provider path in tofu_build_dir() which was causing Hetzner provisioning to fail because it was looking for templates in build/{env}/tofu/lxd instead of build/{env}/tofu/hetzner.

Changes

Error Handling Improvements

  • Added WorkingDirectoryNotFound error variant to CommandError for clearer error messages when the working directory doesn't exist
  • Previously, the error was a generic "No such file or directory (os error 2)" which could be confused with the command binary not being found

Provider-Parameterized Tofu Build Directory

  • Changed tofu_build_dir_for_provider to accept Provider enum instead of &str for type safety
  • Removed the hardcoded LXD-specific tofu_build_dir() method from InternalConfig
  • EnvironmentContext::tofu_build_dir() now uses the environment's actual provider to determine the correct build path

Testing

  • ✅ All pre-commit checks pass
  • ✅ Full Hetzner lifecycle tested manually: create → provision → configure → destroy
  • ✅ Build directory correctly uses provider name: build/hetzner-test/tofu/hetzner/

Related Issue

Closes #212

- Add Hetzner provider support with OpenTofu templates (main.tf, variables.tfvars.tera)
- Move cloud-init template to common folder shared by all providers
- Add image field to HetznerConfig for OS image selection
- Create Hetzner template wrappers (cloud_init, variables contexts)
- Update TofuTemplateRenderer to accept ProviderConfig parameter
- Ensure instance_info output format matches JSON parser expectations
- All E2E tests pass including full deployment workflow
- Add HetznerVariablesRenderingFailed error variant for proper error propagation
- Remove profile_name() from Environment - provider-specific data accessed via provider_config()
- Update TofuTemplateRenderer to extract profile_name from ProviderConfig when needed
- Add tera_error_chain() helper to display full Tera error chains including root cause
- Update TemplateEngineError to include source error messages in Display

The error messages now show the actual root cause:
Before: 'Provider ... is not yet supported for template rendering'
After: 'Failed to render Hetzner variables template: ... Variable `x` not found'
Replace LXD-specific troubleshooting commands with generic provider-neutral
instructions across all error help messages.

Files updated:
- src/application/command_handlers/provision/errors.rs
- src/application/command_handlers/destroy/errors.rs
- src/application/command_handlers/configure/errors.rs
- src/presentation/controllers/provision/errors.rs
- src/presentation/controllers/destroy/errors.rs
- src/presentation/controllers/configure/errors.rs
- src/adapters/ssh/error.rs
- src/bootstrap/help.rs

Changes:
- Remove 'lxc list', 'lxc exec', 'lxc version' commands from generic help
- Replace with 'use your provider tools' or 'using provider tools'
- Update paths like 'build/<env>' to 'build/<env>/tofu/<provider>'
- Keep provider-specific guidance only where it applies to that provider
- Update tests to check for 'provider' instead of 'lxc list'
- Simplify docs/user-guide/providers/README.md to be just an index
- Refactor LXD provider guide to focus only on LXD-specific config
- Refactor Hetzner provider guide to focus only on Hetzner-specific config
- Create new docs/tech-stack/ssh-keys.md for SSH key documentation
- Link to existing tech guides instead of duplicating content
- Remove duplicated command steps (already in quick-start.md)
- Remove made-up command outputs
- Create providers/lxd and providers/hetzner modules
- Move wrappers from template/wrappers to providers/{lxd,hetzner}/wrappers
- Remove empty template/wrappers directory
- Update all import paths to use new providers structure

New structure:
  template/
  ├── common/renderer/     (shared template rendering)
  └── providers/
      ├── lxd/wrappers/    (LXD-specific templates)
      └── hetzner/wrappers/ (Hetzner-specific templates)
Since we use the same cloud-init template (templates/tofu/common/cloud-init.yml.tera)
for all providers, there's no need for duplicate wrappers.

- Move cloud_init wrapper to common/wrappers/cloud_init
- Remove duplicate wrappers from providers/lxd and providers/hetzner
- Update imports in cloud_init renderer to use common wrapper
- Add notes in provider wrappers about shared cloud-init location

This follows the one-to-one relationship between Tera templates and wrappers:
- Common template → common wrapper
- Provider-specific template → provider-specific wrapper
The VariablesTemplateError type was duplicated in both LXD and Hetzner
provider variable wrappers. This extracts it to a shared location.

- Create common/wrappers/errors.rs with shared VariablesTemplateError
- Re-export VariablesTemplateError from both provider variable modules
- Update common/wrappers/mod.rs to export the errors module

This reduces code duplication while maintaining backward compatibility
with existing imports from provider modules.
Extract TofuTemplateRenderer and rename ProvisionTemplateError to
TofuTemplateRendererError for better naming alignment.

Changes:
- Create tofu_template_renderer.rs with TofuTemplateRenderer struct
  and TofuTemplateRendererError enum (moved from mod.rs)
- Rename ProvisionTemplateError to TofuTemplateRendererError
- Add deprecated type alias for backward compatibility
- Update all imports to use the new error type name
- Move all unit tests to the new module file

The rename follows Rust conventions where error types are named after
the type that produces them (TofuTemplateRenderer -> TofuTemplateRendererError).
Extract CloudInitTemplate and VariablesTemplate types to their own
module files for better code organization and separation of concerns.

Changes:
- Extract CloudInitTemplate from common/wrappers/cloud_init/mod.rs
  to cloud_init_template.rs
- Extract VariablesTemplate from providers/hetzner/wrappers/variables/mod.rs
  to variables_template.rs
- Extract VariablesTemplate from providers/lxd/wrappers/variables/mod.rs
  to variables_template.rs
- Move all associated unit tests with each type

Each mod.rs now contains only module declarations and re-exports,
following the pattern established in previous commits.
Remove the deprecated ProvisionTemplateError type alias and all its
re-exports throughout the module hierarchy. The alias was introduced
for backward compatibility during the rename to TofuTemplateRendererError.

Changes:
- Remove deprecated type alias from common/renderer/mod.rs
- Remove re-exports from template/mod.rs and common/mod.rs
- Remove re-export from tofu/mod.rs
- Update docs/codebase-architecture.md to use TofuTemplateRendererError

All code now uses TofuTemplateRendererError directly.
The OPENTOFU_SUBFOLDER constant was hardcoded to 'tofu/lxd' in
production code, but was only used by LXD-specific E2E tests.
This violates the principle that production code should not
contain provider-specific constants.

Changes:
- Moved constant from src/infrastructure/external_tools/tofu/mod.rs
  to src/testing/e2e/mod.rs
- Renamed from OPENTOFU_SUBFOLDER to LXD_OPENTOFU_SUBFOLDER
  for clarity about its provider-specific purpose
- Updated all 3 test files that use it:
  - container.rs - Services dependency injection
  - context.rs - TestContext drop cleanup
  - preflight_cleanup.rs - cleanup_opentofu_infrastructure
The build_template_path function in CloudInitTemplateRenderer
used a hardcoded 'tofu/common' string. This commit extracts it
to a named constant COMMON_TEMPLATES_PATH for better clarity
and maintainability.

This follows the pattern of extracting magic strings to named
constants for improved code readability and self-documentation.
Previously, when a command's working directory didn't exist, the error
was 'No such file or directory (os error 2)' which could be confused
with the command binary not being found.

Now we check if the working directory exists before running the command
and return a clear WorkingDirectoryNotFound error with the path, making
it obvious what's missing.
- tofu_build_dir_for_provider now takes Provider enum instead of &str
- Removed hardcoded LXD-specific tofu_build_dir method from InternalConfig
- EnvironmentContext::tofu_build_dir() now uses the environment's
  actual provider to determine the correct build directory path

This ensures the correct tofu build directory is used for each provider
(e.g., build/{env}/tofu/lxd vs build/{env}/tofu/hetzner).
@josecelano josecelano self-assigned this Dec 2, 2025
@josecelano
Copy link
Copy Markdown
Member Author

ACK 24c8d33

@josecelano josecelano merged commit 672ffa2 into main Dec 3, 2025
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parameterize TofuTemplateRenderer by Provider

1 participant