bastion.toml manifest parser with variable validation and dependency declarations. Declarative compliance policy schema with per-platform check implementations. Template loader with variable substitution (Bastion-owned files only — never touches Ansible/Terraform). PolicyRegistry and AccordRegistry with builtin fallbacks. BOUNDARY: loader never touches automation framework files. Signed-off-by: Tyler King <tking@guildhouse.dev>
98 lines
3 KiB
Python
98 lines
3 KiB
Python
# Copyright 2026 Guildhouse Dev
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Bastion template manifest schema.
|
|
|
|
A template is a Git repository with a bastion.toml at the root.
|
|
It contains policies, accords, harnesses, scripts, HBOM profiles,
|
|
and optionally framework-specific automation (Ansible, Terraform, etc).
|
|
|
|
The manifest declares:
|
|
- What the template provides (contents)
|
|
- What it depends on (dependencies)
|
|
- What the deployer must customize (variables)
|
|
- What Bastion version and connectors it requires (compatibility)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tomllib
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class TemplateMetadata(BaseModel):
|
|
"""Template identification and categorization."""
|
|
name: str
|
|
version: str
|
|
description: str = ""
|
|
authors: list[str] = []
|
|
license: str = "Apache-2.0"
|
|
repository: Optional[str] = None
|
|
|
|
vertical: Optional[str] = None
|
|
sub_vertical: Optional[str] = None
|
|
compliance_frameworks: list[str] = []
|
|
target_fleet_size: Optional[str] = None
|
|
requires_vdi: bool = False
|
|
requires_tpm: bool = False
|
|
|
|
|
|
class TemplateCompatibility(BaseModel):
|
|
"""What Bastion version and connectors this template needs."""
|
|
bastion_min: str = "0.5.0"
|
|
connectors_required: list[str] = []
|
|
connectors_optional: list[str] = []
|
|
|
|
|
|
class TemplateDependency(BaseModel):
|
|
"""A dependency on another template repository."""
|
|
git: str
|
|
tag: Optional[str] = None
|
|
branch: Optional[str] = None
|
|
|
|
|
|
class TemplateVariable(BaseModel):
|
|
"""A variable the deployer must or may customize."""
|
|
type: str = "string"
|
|
required: bool = False
|
|
default: Optional[str] = None
|
|
description: str = ""
|
|
sensitive: bool = False
|
|
|
|
|
|
class TemplateContents(BaseModel):
|
|
"""Directory mapping for template content."""
|
|
policies: str = "policies/"
|
|
accords: str = "accords/"
|
|
harnesses: str = "harnesses/"
|
|
scripts: str = "scripts/"
|
|
hbom_profiles: str = "hbom/"
|
|
dashboards: str = "dashboards/"
|
|
|
|
|
|
class TemplateManifest(BaseModel):
|
|
"""Root schema for bastion.toml."""
|
|
template: TemplateMetadata
|
|
compatibility: TemplateCompatibility = TemplateCompatibility()
|
|
dependencies: dict[str, TemplateDependency] = {}
|
|
variables: dict[str, TemplateVariable] = {}
|
|
contents: TemplateContents = TemplateContents()
|
|
|
|
@classmethod
|
|
def from_toml(cls, path: str | Path) -> TemplateManifest:
|
|
"""Parse a bastion.toml file."""
|
|
with open(path, "rb") as f:
|
|
data = tomllib.load(f)
|
|
return cls.model_validate(data)
|
|
|
|
def validate_variables(self, provided: dict[str, Any]) -> list[str]:
|
|
"""Check that all required variables are provided.
|
|
Returns list of error messages (empty = valid)."""
|
|
errors = []
|
|
for name, var in self.variables.items():
|
|
if var.required and name not in provided and var.default is None:
|
|
errors.append(f"Required variable '{name}' not provided")
|
|
return errors
|