Files
devx/src/devx/ci/check_workflow_tofu_init.py
T
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 5cacbdec09 feat: sync missing features from v0.49.x line to master
The v0.49.x tag line diverged from origin/master, leaving many
features only accessible via tags but not on the master branch.

New modules:
- ci/cancel_superseded_runs.py — cancel superseded CI runs
- ci/check_workflow_artifact_deps.py — validate artifact deps
- ci/check_workflow_tofu_init.py — validate tofu init steps
- tools/check_alert_rules.py — validate Prometheus alert rules
- tools/check_ansible_set_fact_to_json.py — lint set_fact usage
- tools/check_docker_init.py — validate Docker init scripts
- utils/jinja.py — Jinja2 template utilities
- utils/ui.py — UI/console utilities

Modified modules:
- distribute_molecule.py: add --include-roles/--exclude-roles
- utils/api.py: add container.credentials for private registry auth
- install_tools.py: retry ansible-galaxy on transient timeouts
- setup_image.py: skip dep resolution with --no-deps
- cli.py: register new commands
- i18n.py: add new translation keys

Also removes accidentally committed .vale/styles/Google/ files.

Test results: 2195 passed, 100% coverage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-09 03:04:39 +02:00

146 lines
4.7 KiB
Python

"""Check that workflow jobs using tofu state have a tofu-init step.
This prevents the class of bug where a job runs ``tofu output`` or calls
a script that uses tofu state without first running ``tofu init``,
causing "Required plugins are not installed" errors.
The check scans all workflow YAML files for jobs that:
- Call scripts that use ``tofu output`` (configurable via --state-scripts)
- Call ``tofu output`` directly
- Call ``tofu plan`` or ``tofu apply`` directly
For each such job, it verifies the same job has a ``tofu-init`` step,
either:
- Directly via ``tofu init`` in a step's run command
- Via ``create_staging_deployment.py --phase tofu-init``
- Via ``create_production_deployment.py --phase tofu-init``
Usage::
python -m devx.ci.check_workflow_tofu_init
python -m devx.ci.check_workflow_tofu_init --workflow .gitea/workflows/deploy.yml
Exit code 0 if all jobs have tofu-init, 1 otherwise.
"""
from __future__ import annotations
import sys
from pathlib import Path
import click
import yaml
REPO_ROOT = Path.cwd()
WORKFLOWS_DIR = REPO_ROOT / ".gitea" / "workflows"
# Scripts that call `tofu output`, `tofu plan`, or `tofu apply` internally.
# If a job calls any of these, it must have a tofu-init step.
# NOTE: destroy_orphans.py reads terraform.tfstate directly from disk
# (does not invoke `tofu output`), so it does NOT need tofu-init.
DEFAULT_TOFU_STATE_SCRIPTS: set[str] = {
"preflight_deploy.py",
}
# Commands that directly use tofu state (must be preceded by tofu init).
TOFU_STATE_COMMANDS = ("tofu output", "tofu plan", "tofu apply", "tofu show")
# Commands that initialize tofu (counted as tofu-init steps).
TOFU_INIT_COMMANDS = (
"tofu init",
"--phase tofu-init",
"tofu-init",
)
def _check_workflow(filepath: Path, state_scripts: set[str]) -> list[str]:
"""Check a single workflow file for missing tofu-init steps.
Returns a list of error messages (empty if all OK).
"""
errors: list[str] = []
content = filepath.read_text(encoding="utf-8")
try:
workflow = yaml.safe_load(content)
except yaml.YAMLError as exc:
return [f"{filepath}: cannot parse YAML: {exc}"]
jobs = workflow.get("jobs", {})
for job_name, job_def in jobs.items():
steps = job_def.get("steps", [])
if not steps:
continue
uses_tofu_state = False
has_tofu_init = False
for step in steps:
run_cmd = step.get("run", "")
if not run_cmd:
continue
# Check if this step uses tofu state
for script in state_scripts:
if script in run_cmd:
uses_tofu_state = True
for cmd in TOFU_STATE_COMMANDS:
if cmd in run_cmd:
uses_tofu_state = True
# Check if this step initializes tofu
for cmd in TOFU_INIT_COMMANDS:
if cmd in run_cmd:
has_tofu_init = True
if uses_tofu_state and not has_tofu_init:
errors.append(
f"{filepath.name}::{job_name}: uses tofu state "
f"(tofu output/plan/apply or {state_scripts}) "
f"but has no tofu-init step. Add a step running "
f"'create_*_deployment.py --phase tofu-init' before "
f"the first tofu state access."
)
return errors
@click.command()
@click.option(
"--workflow",
type=click.Path(exists=True, path_type=Path),
help="Check a specific workflow file (default: all in .gitea/workflows/).",
)
@click.option(
"--workflows-dir",
type=click.Path(exists=True, path_type=Path),
default=None,
help="Override the workflows directory (default: .gitea/workflows/).",
)
@click.option(
"--state-script",
"state_scripts",
multiple=True,
default=None,
help="Add a script name that uses tofu state (can be repeated). Overrides the default list if any are specified.",
)
def main(workflow: Path | None, workflows_dir: Path | None, state_scripts: tuple[str, ...]) -> None:
"""Check that workflow jobs using tofu state have a tofu-init step."""
scripts = set(state_scripts) if state_scripts else DEFAULT_TOFU_STATE_SCRIPTS
wdir = workflows_dir or WORKFLOWS_DIR
files = [workflow] if workflow else sorted(wdir.glob("*.yml"))
all_errors: list[str] = []
for f in files:
errors = _check_workflow(f, scripts)
all_errors.extend(errors)
if all_errors:
click.echo("[check-workflow-tofu-init] FAIL: missing tofu-init steps found:")
for err in all_errors:
click.echo(f" - {err}")
sys.exit(1)
else:
click.echo("[check-workflow-tofu-init] OK: all tofu-state jobs have tofu-init.")
if __name__ == "__main__": # pragma: no cover
main()