"""Utilities for the Docker integration tests under tests/.""" from __future__ import annotations import os import pathlib from typing import Annotated import jinja2 import typer app = typer.Typer(help="Utilities for the tests/ Docker integration tests") TESTS_DIR = pathlib.Path(__file__).resolve().parent.parent / "tests" def _env(name: str) -> str: """Jinja2 filter: `'NAME' | env` -> the environment variable NAME.""" return os.environ[name] def _file(relative: str) -> str: """Jinja2 filter: `'relative/path' | file` -> content of tests/relative/path.""" path = (TESTS_DIR / relative).resolve() if path != TESTS_DIR and TESTS_DIR not in path.parents: raise ValueError(f"refusing to read a file outside of {TESTS_DIR}: {relative}") return path.read_text(encoding="utf-8") def render_template(text: str) -> str: """Render a tests/configs/*.yaml.j2 fixture as a Jinja2 template. Custom filters: `| env` reads an environment variable, `| file` reads a file relative to tests/. Combine with Jinja2's built-in `indent` filter to fit multi-line content (e.g. a PEM certificate) into a YAML block-literal field, e.g.: content: | {{ 'certs/test-ca.crt.pem' | file | indent(6, first=true) }} """ # Autoescaping is for HTML output; these templates render YAML, so escaping # would corrupt them (e.g. turning "&" into "&" inside a PEM/URL). environment = jinja2.Environment(undefined=jinja2.StrictUndefined) # nosec B701 environment.filters["env"] = _env environment.filters["file"] = _file return environment.from_string(text).render() @app.command( name="render-config", help="Render a tests/configs/*.yaml.j2 fixture into a ready-to-use configuration.", ) def render_config( template: Annotated[pathlib.Path, typer.Argument(exists=True, dir_okay=False)], output: pathlib.Path, ) -> None: """Renders `template` as a Jinja2 template (see `render_template`) and writes the result to `output`, creating its parent directories as needed.""" content = render_template(template.read_text(encoding="utf-8")) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(content, encoding="utf-8") if __name__ == "__main__": app()