From c3d43235783d2b0897e147fd4f584540fa53c618 Mon Sep 17 00:00:00 2001 From: Kris Turner Date: Tue, 17 Mar 2026 09:54:35 -0400 Subject: [PATCH 1/2] docs: consolidate variable docs to single reference Merge naming rules and compatibility section from standards/variables.md into reference/variables.md. Replace standards page with redirect. deployment/variables.md was already a redirect - unchanged. Part of AzureLocal/azurelocal.github.io#15 --- docs/reference/variables.md | 24 ++++++++++ docs/standards/variables.md | 93 ++++--------------------------------- 2 files changed, 32 insertions(+), 85 deletions(-) diff --git a/docs/reference/variables.md b/docs/reference/variables.md index b409341..7759b86 100644 --- a/docs/reference/variables.md +++ b/docs/reference/variables.md @@ -11,6 +11,30 @@ All deployment tools read from a single central configuration file: `config/vari --- +## Naming Rules + +| Scope | Convention | Example | +|-------|-----------|---------| +| Top-level sections | `snake_case` | `azure_local`, `data_disks` | +| Keys within sections | `snake_case` | `subscription_id`, `volume_size_gb` | +| Per-VM maps | Zero-padded string keys | `"01"`, `"02"`, `"03"` | +| Booleans | Descriptive name | `role_enabled: true` | +| Secrets | `keyvault://` URI | `keyvault://kv-name/secret-name` | +| Example values | IIC fictional identity | `iic.local`, `rg-iic-sofs-01`, `kv-iic-platform` | + +--- + +## Compatibility + +The PowerShell scripts include a **compatibility shim** that maps the new sectioned config into the legacy `compute_wsfc` / `wsfc_sofs_*` flat key format. This means: + +- New `config/variables.yml` → works with all scripts +- Legacy `solution-sofs.yml` → also works (auto-detected) + +The shim is transparent — downstream script logic is unchanged. + +--- + ## Azure ```yaml diff --git a/docs/standards/variables.md b/docs/standards/variables.md index e376b2e..ff78f7d 100644 --- a/docs/standards/variables.md +++ b/docs/standards/variables.md @@ -1,89 +1,12 @@ # Variable Standards -Conventions for the central configuration file and all variable naming across the repository. +!!! info "This page has moved" + Variable standards, naming conventions, and the complete configuration reference are now consolidated at **[Variable Reference](../reference/variables.md)**. ---- +See [Variable Reference](../reference/variables.md) for: -## Central Config - -The single source of truth is `config/variables.yml`. Copy from `config/variables.example.yml`. - -### Format - -- **YAML** with sectioned structure -- Clean, human-readable keys (no prefixes like `wsfc_sofs_`) -- 2-space indentation - -### Sections - -```yaml -azure: # Azure subscription, resource group, location -keyvault: # Key Vault name for secret resolution -azure_local: # Azure Local cluster, custom location, networks, images, storage paths -vm: # VM prefix, count, specs, admin creds, IPs -data_disks: # Disk count and size for S2D pool -domain: # AD domain config, join credentials, OU paths -dns_servers: # DNS server list -sofs: # SOFS role name, cluster name/IP, share name -s2d: # Storage Spaces Direct volume config -cloud_witness: # Cloud witness storage account -guest_config_engine: # How to configure guests (ansible_create, ansible_existing, manual) -ansible_controller: # Ansible controller VM details -tags: # Resource tags -``` - -### Naming Rules - -| Scope | Convention | Example | -|-------|-----------|---------| -| Top-level sections | `snake_case` | `azure_local`, `data_disks` | -| Keys within sections | `snake_case` | `subscription_id`, `volume_size_gb` | -| Per-VM maps | Zero-padded string keys | `"01"`, `"02"`, `"03"` | -| Booleans | Descriptive name | `role_enabled: true` | -| Secrets | `keyvault://` URI | `keyvault://kv-name/secret-name` | -| Example values | IIC fictional identity | `iic.local`, `rg-iic-sofs-01`, `kv-iic-platform` | - ---- - -## Key Vault References - -Secrets are **never** stored as plain text in config files. Use the `keyvault://` URI format: - -```yaml -vm: - admin_password: "keyvault://kv-platform-prod/sofs-vm-admin-password" -domain: - join_password: "keyvault://kv-platform-prod/domain-join-password" -``` - -At runtime, scripts resolve these via `Resolve-KeyVaultRef`: - -1. Try `Az.KeyVault` PowerShell module (preferred) -2. Fallback to `az keyvault secret show` CLI -3. Hard fail if neither works (no interactive prompts) - ---- - -## Compatibility - -The PowerShell scripts include a **compatibility shim** that maps the new sectioned config into the legacy `compute_wsfc` / `wsfc_sofs_*` flat key format. This means: - -- New `config/variables.yml` → works with all scripts -- Legacy `solution-sofs.yml` → also works (auto-detected) - -The shim is transparent — downstream script logic is unchanged. - ---- - -## Tool-Specific Parameter Files - -| Tool | File | Location | -|------|------|----------| -| PowerShell | `variables.yml` | `config/` | -| Bicep | `main.bicepparam` | `src/bicep/` | -| Terraform | `terraform.tfvars` | `src/terraform/` | -| ARM | `azuredeploy.parameters.json` | `src/arm/` | -| Ansible | `inventory.yml` | `src/ansible/inventory/` | -| Azure CLI | `.env` | `scripts/` | - -All tool-specific parameter files should derive their values from the central `config/variables.yml`. +- Naming rules and conventions +- Compatibility notes +- Every configuration section with types, defaults, and deployment phase mapping +- Key Vault secret resolution +- Tool-specific parameter mapping From 902ec4c9643e48d4df1329cd151f813dda9b0c18 Mon Sep 17 00:00:00 2001 From: Kris Turner Date: Tue, 17 Mar 2026 10:01:35 -0400 Subject: [PATCH 2/2] ci: add JSON Schema and validate-config workflow Add config/schema/variables.schema.json for validating variables.example.yml. Add CI workflow that validates example config against schema on PR. Part of AzureLocal/azurelocal.github.io#15 --- .github/workflows/validate-config.yml | 60 +++++++++++ config/schema/variables.schema.json | 149 ++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 .github/workflows/validate-config.yml create mode 100644 config/schema/variables.schema.json diff --git a/.github/workflows/validate-config.yml b/.github/workflows/validate-config.yml new file mode 100644 index 0000000..538f0ef --- /dev/null +++ b/.github/workflows/validate-config.yml @@ -0,0 +1,60 @@ +# ============================================================================= +# validate-config.yml — Validate config/variables.example.yml against schema +# ============================================================================= +# Triggered on PRs and pushes that touch config/ or this workflow. +# Validates YAML syntax and JSON Schema compliance. +# ============================================================================= + +name: Validate Configuration + +on: + push: + branches: [main] + paths: + - 'config/**' + - '.github/workflows/validate-config.yml' + pull_request: + branches: [main] + paths: + - 'config/**' + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install pyyaml jsonschema + + - name: Validate variables.example.yml against schema + run: | + python3 -c " + import yaml, json, sys + from jsonschema import validate, ValidationError + + with open('config/variables.example.yml') as f: + data = yaml.safe_load(f) + + with open('config/schema/variables.schema.json') as f: + schema = json.load(f) + + try: + validate(instance=data, schema=schema) + print('✅ config/variables.example.yml passes schema validation') + except ValidationError as e: + print(f'❌ Schema validation failed: {e.message}') + print(f' Path: {\" > \".join(str(p) for p in e.absolute_path)}') + sys.exit(1) + " diff --git a/config/schema/variables.schema.json b/config/schema/variables.schema.json new file mode 100644 index 0000000..4521695 --- /dev/null +++ b/config/schema/variables.schema.json @@ -0,0 +1,149 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/AzureLocal/azurelocal-sofs-fslogix/config/schema/variables.schema.json", + "title": "SOFS FSLogix Variables", + "description": "Schema for config/variables.example.yml — validates required sections and key structure.", + "type": "object", + "required": ["azure", "keyvault", "azure_local", "vm", "data_disks", "domain", "dns_servers", "sofs", "s2d", "cloud_witness", "guest_config_engine", "tags"], + "properties": { + "azure": { + "type": "object", + "required": ["subscription_id", "resource_group", "location"], + "properties": { + "subscription_id": { "type": "string" }, + "resource_group": { "type": "string" }, + "location": { "type": "string" } + } + }, + "keyvault": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" } + } + }, + "azure_local": { + "type": "object", + "required": ["cluster_name", "custom_location_id", "logical_network_id", "gallery_image_name"], + "properties": { + "cluster_name": { "type": "string" }, + "custom_location_id": { "type": "string" }, + "logical_network_id": { "type": "string" }, + "gallery_image_name": { "type": "string" }, + "storage_path_id": { "type": "string" }, + "storage_path_ids": { "type": "object", "additionalProperties": { "type": "string" } } + } + }, + "vm": { + "type": "object", + "required": ["prefix", "count", "processors", "memory_mb", "admin_username", "admin_password", "ips"], + "properties": { + "prefix": { "type": "string" }, + "count": { "type": "integer", "minimum": 1 }, + "processors": { "type": "integer", "minimum": 1 }, + "memory_mb": { "type": "integer", "minimum": 1024 }, + "admin_username": { "type": "string" }, + "admin_password": { "type": "string" }, + "ips": { "type": "object", "additionalProperties": { "type": "string" } } + } + }, + "data_disks": { + "type": "object", + "required": ["count", "size_gb"], + "properties": { + "count": { "type": "integer", "minimum": 1 }, + "size_gb": { "type": "integer", "minimum": 1 } + } + }, + "domain": { + "type": "object", + "required": ["fqdn", "netbios", "join_username", "join_password"], + "properties": { + "fqdn": { "type": "string" }, + "netbios": { "type": "string" }, + "join_username": { "type": "string" }, + "join_password": { "type": "string" }, + "cluster_ou_path": { "type": "string" }, + "nodes_ou_path": { "type": "string" } + } + }, + "dns_servers": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "sofs": { + "type": "object", + "required": ["name", "cluster_name", "cluster_ip"], + "properties": { + "name": { "type": "string" }, + "cluster_name": { "type": "string" }, + "cluster_ip": { "type": "string" }, + "role_enabled": { "type": "boolean" }, + "anti_affinity_rule_name": { "type": "string" }, + "share_name": { "type": "string" }, + "shares": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "volume"], + "properties": { + "name": { "type": "string" }, + "volume": { "type": "string" } + } + } + } + } + }, + "s2d": { + "type": "object", + "properties": { + "volume_name": { "type": "string" }, + "volume_size_gb": { "type": "integer" }, + "data_copies": { "type": "integer", "enum": [2, 3] }, + "volumes": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "size_gb", "data_copies"], + "properties": { + "name": { "type": "string" }, + "size_gb": { "type": "integer" }, + "data_copies": { "type": "integer", "enum": [2, 3] } + } + } + } + } + }, + "cloud_witness": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "key_uri": { "type": "string" }, + "key_secret": { "type": "string" } + } + }, + "guest_config_engine": { + "type": "string", + "enum": ["ansible_create", "ansible_existing", "manual"] + }, + "ansible_controller": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "size": { "type": "string" }, + "admin_username": { "type": "string" }, + "hub_subnet_id": { "type": "string" }, + "hub_rg": { "type": "string" }, + "existing_controller_ip": { "type": "string" }, + "existing_controller_user": { "type": "string" } + } + }, + "tags": { + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false +}