Files
ansible-testing/scripts/parse-assets.py
T

114 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Validate assets.yml and expose its metadata to CI jobs."""
import argparse
import json
from pathlib import Path
from typing import Any
import yaml
REQUIRED_PROJECT_FIELDS = ("id", "name", "environment")
def load_inventory(path: Path) -> dict[str, Any]:
with path.open(encoding="utf-8") as inventory_file:
inventory = yaml.safe_load(inventory_file)
if not isinstance(inventory, dict) or not isinstance(inventory.get("all"), dict):
raise ValueError("inventory must contain an 'all' mapping")
project = inventory["all"].get("vars", {}).get("test_project")
if not isinstance(project, dict):
raise ValueError("all.vars.test_project must be a mapping")
missing = [field for field in REQUIRED_PROJECT_FIELDS if not project.get(field)]
if missing:
raise ValueError(f"test_project is missing required values: {', '.join(missing)}")
children = inventory["all"].get("children", {})
if not isinstance(children, dict):
raise ValueError("all.children must be a mapping")
return inventory
def asset_matrix(inventory: dict[str, Any]) -> list[dict[str, Any]]:
assets = []
seen = set()
for group_name, group in inventory["all"].get("children", {}).items():
if not isinstance(group, dict):
raise ValueError(f"group '{group_name}' must be a mapping")
hosts = group.get("hosts", {})
if not isinstance(hosts, dict):
raise ValueError(f"group '{group_name}'.hosts must be a mapping")
for asset_id, host_vars in hosts.items():
if asset_id in seen:
raise ValueError(f"asset '{asset_id}' is declared more than once")
seen.add(asset_id)
variables = host_vars or {}
if not isinstance(variables, dict):
raise ValueError(f"asset '{asset_id}' variables must be a mapping")
assets.append(
{
"id": asset_id,
"group": group_name,
"address": variables.get("ansible_host", variables.get("target_url", asset_id)),
"asset_type": variables.get("asset_type", group_name.rstrip("s")),
"test_profiles": variables.get("test_profiles", []),
}
)
return assets
def dotenv_value(value: Any) -> str:
return str(value).replace("\n", " ").replace("\r", " ")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("inventory", type=Path)
parser.add_argument("--expected-environment")
parser.add_argument("--dotenv", type=Path, required=True)
parser.add_argument("--matrix", type=Path, required=True)
args = parser.parse_args()
try:
inventory = load_inventory(args.inventory)
assets = asset_matrix(inventory)
except (OSError, ValueError, yaml.YAMLError) as error:
parser.error(str(error))
project = inventory["all"]["vars"]["test_project"]
if args.expected_environment and project["environment"] != args.expected_environment:
parser.error(
"assets.yml environment "
f"'{project['environment']}' does not match TARGET_ENVIRONMENT "
f"'{args.expected_environment}'"
)
groups = sorted({asset["group"] for asset in assets})
dotenv = {
"TEST_PROJECT_ID": project["id"],
"TEST_PROJECT_NAME": project["name"],
"TEST_ENVIRONMENT": project["environment"],
"TEST_CUSTOMER": project.get("customer", ""),
"TEST_LOCATION": project.get("location", ""),
"ASSET_COUNT": len(assets),
"ASSET_GROUPS": ",".join(groups),
}
args.dotenv.parent.mkdir(parents=True, exist_ok=True)
args.matrix.parent.mkdir(parents=True, exist_ok=True)
args.dotenv.write_text(
"".join(f"{key}={dotenv_value(value)}\n" for key, value in dotenv.items()),
encoding="utf-8",
)
args.matrix.write_text(json.dumps({"assets": assets}, indent=2) + "\n", encoding="utf-8")
print(f"Validated {len(assets)} assets for project '{project['name']}'")
return 0
if __name__ == "__main__":
raise SystemExit(main())