Public Access
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
120 lines
3.7 KiB
Python
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
|