Files
devx/src/devx/tools/tofu_ops.py
T
emil ff80745eea
Post-merge / detect-type (push) Successful in 15s
Post-merge / validate-commit-msg (push) Successful in 14s
Build Images / detect-type (push) Successful in 56s
Post-merge / release (push) Successful in 1m2s
Post-merge / configure-repo (push) Successful in 28s
Post-merge / sync-wiki (push) Successful in 1m11s
Post-merge / badges (push) Successful in 1m19s
Post-merge / vikunja (push) Successful in 1m15s
Post-merge / publish (push) Successful in 33s
Build Images / build-and-push (push) Successful in 4m22s
Build Images / cleanup (push) Successful in 2m50s
DEVX-110: fix: add missing i18n translations for new tools
2026-07-01 23:35:56 +00:00

120 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""OpenTofu operations: init and validate across directories.
Handles initialization and validation of OpenTofu configurations across
multiple directories (modules + environments). Supports CI mode with
``-backend=false`` to avoid state backend access.
Usage::
python3 -m devx.tools.tofu_ops init --env staging
python3 -m devx.tools.tofu_ops validate
python3 -m devx.tools.tofu_ops validate --ci
"""
from __future__ import annotations
import subprocess # nosec B404
from pathlib import Path
import click
from devx.i18n import _
DEFAULT_ENV_DIRS = ["tofu/environments/{env}", "tofu/environments/dns"]
DEFAULT_VALIDATE_DIRS = [
"tofu/modules/hetzner-vm",
"tofu/modules/hetzner-network",
"tofu/environments/staging",
"tofu/environments/production",
"tofu/environments/dns",
]
def _run_tofu(cmd: list[str], cwd: Path) -> None:
"""Run a tofu command in the given directory, raising on failure."""
click.echo(_(" -> {dir}", dir=cwd))
result = subprocess.run( # nosec B603, B607
cmd,
cwd=str(cwd),
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
raise click.ClickException(
_("tofu command failed in {dir}: {error}", dir=cwd, error=result.stderr.strip()),
)
def tofu_init(env: str, root: str = ".", extra_dirs: list[str] | None = None) -> None:
"""Run ``tofu init`` in the environment directory and DNS directory.
Args:
env: Environment name (e.g. staging, production).
root: Repository root directory.
extra_dirs: Additional directory patterns to initialize.
"""
root_path = Path(root)
dirs = [d.format(env=env) for d in (extra_dirs or DEFAULT_ENV_DIRS)]
for dir_pattern in dirs:
dir_path = root_path / dir_pattern
if dir_path.is_dir():
click.echo(_("[tofu-init] Initializing {dir}...", dir=dir_path))
_run_tofu(["tofu", "init"], dir_path)
click.echo(_("[tofu-init] Done."))
def tofu_validate(
root: str = ".",
dirs: list[str] | None = None,
ci: bool = False,
) -> None:
"""Run ``tofu validate`` in all OpenTofu directories.
In CI mode, runs ``tofu init -backend=false`` before validate to avoid
state backend access.
Args:
root: Repository root directory.
dirs: List of directory paths to validate (relative to root).
ci: If True, use CI mode with -backend=false.
"""
root_path = Path(root)
target_dirs = dirs or DEFAULT_VALIDATE_DIRS
mode = "ci" if ci else "validate"
click.echo(_("[tofu-{mode}] Validating OpenTofu configurations...", mode=mode))
for dir_rel in target_dirs:
dir_path = root_path / dir_rel
if not dir_path.is_dir():
continue
if ci:
_run_tofu(["tofu", "init", "-backend=false", "-input=false"], dir_path)
_run_tofu(["tofu", "validate"], dir_path)
click.echo(_("[tofu-{mode}] All configurations valid.", mode=mode))
@click.group()
def cli() -> None:
"""OpenTofu operations."""
@cli.command()
@click.option("--env", required=True, help="Environment name (staging, production).")
@click.option("--root", default=".", help="Repository root directory.")
def init(env: str, root: str) -> None:
"""Initialize OpenTofu in an environment."""
tofu_init(env, root)
@cli.command()
@click.option("--root", default=".", help="Repository root directory.")
@click.option("--ci", is_flag=True, default=False, help="CI mode: use -backend=false.")
def validate(root: str, ci: bool) -> None:
"""Validate OpenTofu configurations."""
tofu_validate(root, ci=ci)
if __name__ == "__main__": # pragma: no cover
cli() # pragma: no cover