diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3ed7ac3..dcbbd38 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,3 +26,11 @@ Closes # - [ ] I have added an entry to `CHANGELOG.md` under `[Unreleased]` - [ ] My code follows the project's style guidelines in `CONTRIBUTING.md` - [ ] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) + +## Standards Compliance + +- [ ] Follows [repo structure standard](https://azurelocal.cloud/standards/repo-structure) (required files present) +- [ ] Follows [naming conventions](https://azurelocal.cloud/standards/documentation/naming-conventions) (files, variables, resources) +- [ ] Uses [IIC fictional company](https://azurelocal.cloud/standards/fictional-company-policy) in all examples (never Contoso) +- [ ] Config changes validated against JSON Schema (if applicable) +- [ ] No hardcoded IPs, names, secrets, or environment-specific values in committed code 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/.github/workflows/validate-repo-structure.yml b/.github/workflows/validate-repo-structure.yml new file mode 100644 index 0000000..e50c5a7 --- /dev/null +++ b/.github/workflows/validate-repo-structure.yml @@ -0,0 +1,80 @@ +name: Validate Repo Structure +on: + pull_request: + branches: [main] + +jobs: + check-structure: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check required root files + run: | + missing=0 + for f in README.md CONTRIBUTING.md LICENSE CHANGELOG.md .gitignore; do + if [ ! -f "$f" ]; then + echo "::error::Missing required file: $f" + missing=$((missing + 1)) + fi + done + if [ $missing -gt 0 ]; then + echo "::error::$missing required root file(s) missing" + exit 1 + fi + echo "All required root files present" + + - name: Check required directories + run: | + missing=0 + for d in docs .github; do + if [ ! -d "$d" ]; then + echo "::error::Missing required directory: $d/" + missing=$((missing + 1)) + fi + done + if [ $missing -gt 0 ]; then + echo "::error::$missing required directory(s) missing" + exit 1 + fi + echo "All required directories present" + + - name: Check PR template + run: | + if [ ! -f ".github/PULL_REQUEST_TEMPLATE.md" ]; then + echo "::error::Missing .github/PULL_REQUEST_TEMPLATE.md" + exit 1 + fi + echo "PR template found" + + - name: Check config structure (if config dir exists) + run: | + if [ -d "config" ]; then + missing=0 + if [ ! -f "config/variables.example.yml" ]; then + echo "::error::Missing config/variables.example.yml" + missing=$((missing + 1)) + fi + if [ ! -f "config/schema/variables.schema.json" ]; then + echo "::error::Missing config/schema/variables.schema.json" + missing=$((missing + 1)) + fi + if [ $missing -gt 0 ]; then + exit 1 + fi + echo "Config structure valid" + else + echo "No config/ directory — skipping config checks" + fi + + - name: Check variable reference doc (if config dir exists) + run: | + if [ -d "config" ]; then + if [ ! -f "docs/reference/variables.md" ]; then + echo "::error::Missing docs/reference/variables.md (required when config/ exists)" + exit 1 + fi + echo "Variable reference doc found" + else + echo "No config/ directory — skipping variable reference check" + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b16ba19..5e1c25d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,6 +78,17 @@ Examples: - Test against a real Azure Local environment before submitting - Describe your test environment and results in the PR +## Standards + +This project follows the **org-wide AzureLocal standards** documented at [azurelocal.cloud/standards](https://azurelocal.cloud/standards/). Key references: + +- [Repository Structure](https://azurelocal.cloud/standards/repo-structure) — Required files, directories, labels, branch naming +- [Scripting Standards](https://azurelocal.cloud/standards/scripting/scripting-standards) — PowerShell conventions +- [Solution Development](https://azurelocal.cloud/standards/solutions/solution-development-standard) — IaC conventions +- [Documentation Standards](https://azurelocal.cloud/standards/documentation/documentation-standards) — Writing and formatting +- [Variable Management](https://azurelocal.cloud/docs/implementation/04-variable-management-standard) — Config file patterns +- [Fictional Company Policy](https://azurelocal.cloud/standards/fictional-company-policy) — Use IIC, never Contoso + ## Code of Conduct Be respectful and constructive. Keep discussions on-topic and collaborative. diff --git a/config/schema/variables.schema.json b/config/schema/variables.schema.json new file mode 100644 index 0000000..76b1cf1 --- /dev/null +++ b/config/schema/variables.schema.json @@ -0,0 +1,100 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://github.com/AzureLocal/aurelocal-avd/config/schema/variables.schema.json", + "title": "AVD on Azure Local Variables", + "description": "Schema for config/variables.example.yml — validates required sections and key structure.", + "type": "object", + "required": ["subscription", "security", "control_plane", "session_hosts", "domain", "tags"], + "properties": { + "subscription": { + "type": "object", + "required": ["avd_subscription_id", "azure_local_subscription_id", "tenant_id", "location"], + "properties": { + "avd_subscription_id": { "type": "string" }, + "azure_local_subscription_id": { "type": "string" }, + "tenant_id": { "type": "string" }, + "location": { "type": "string" } + } + }, + "security": { + "type": "object", + "required": ["key_vault_name", "key_vault_resource_group"], + "properties": { + "key_vault_name": { "type": "string" }, + "key_vault_resource_group": { "type": "string" } + } + }, + "control_plane": { + "type": "object", + "required": ["resource_group", "host_pool_name", "host_pool_type", "app_group_name", "workspace_name"], + "properties": { + "resource_group": { "type": "string" }, + "host_pool_name": { "type": "string" }, + "host_pool_type": { "type": "string", "enum": ["Pooled", "Personal"] }, + "load_balancer_type": { "type": "string", "enum": ["BreadthFirst", "DepthFirst"] }, + "max_session_limit": { "type": "integer", "minimum": 1 }, + "preferred_app_group_type": { "type": "string", "enum": ["Desktop", "RailApplications", "None"] }, + "personal_assignment_type": { "type": "string", "enum": ["Automatic", "Direct"] }, + "start_vm_on_connect": { "type": "boolean" }, + "validation_environment": { "type": "boolean" }, + "custom_rdp_properties": { "type": "string" }, + "host_pool_friendly_name": { "type": "string" }, + "host_pool_description": { "type": "string" }, + "app_group_name": { "type": "string" }, + "app_group_type": { "type": "string", "enum": ["Desktop", "RemoteApp"] }, + "app_group_friendly_name": { "type": "string" }, + "workspace_name": { "type": "string" }, + "workspace_friendly_name": { "type": "string" } + } + }, + "session_hosts": { + "type": "object", + "required": ["resource_group", "session_host_count", "vm_naming_prefix", "vm_processors", "vm_memory_mb", "vm_admin_username", "vm_admin_password", "custom_location_id", "logical_network_id", "gallery_image_id", "storage_path_id"], + "properties": { + "resource_group": { "type": "string" }, + "session_host_count": { "type": "integer", "minimum": 1 }, + "vm_naming_prefix": { "type": "string" }, + "vm_start_index": { "type": "integer", "minimum": 0 }, + "vm_processors": { "type": "integer", "minimum": 1 }, + "vm_memory_mb": { "type": "integer", "minimum": 1024 }, + "vm_admin_username": { "type": "string" }, + "vm_admin_password": { "type": "string" }, + "session_host_os": { "type": "string" }, + "custom_location_id": { "type": "string" }, + "logical_network_id": { "type": "string" }, + "gallery_image_id": { "type": "string" }, + "storage_path_id": { "type": "string" } + } + }, + "domain": { + "type": "object", + "required": ["domain_fqdn", "domain_join_username", "domain_join_password"], + "properties": { + "domain_fqdn": { "type": "string" }, + "domain_join_username": { "type": "string" }, + "domain_join_password": { "type": "string" }, + "domain_join_ou_path": { "type": "string" } + } + }, + "entra_id": { + "type": "object", + "properties": { + "enable_entra_id_auth": { "type": "boolean" }, + "enroll_in_intune": { "type": "boolean" }, + "entra_user_login_group_id": { "type": "string" }, + "entra_admin_login_group_id": { "type": "string" } + } + }, + "tags": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "ansible": { + "type": "object", + "properties": { + "ansible_connection": { "type": "string" } + } + } + }, + "additionalProperties": false +} diff --git a/docs/reference/variables.md b/docs/reference/variables.md index f82b015..e42e07a 100644 --- a/docs/reference/variables.md +++ b/docs/reference/variables.md @@ -1,66 +1,239 @@ # Variable Reference -All deployment parameters consolidated from every tool's parameter file. +All deployment tools read from a single central configuration file: `config/variables.yml`. This file is the **single source of truth** — your architecture decisions, sizing, identity settings, and infrastructure IDs are declared here and consumed by every automation tool. -See `config/variables.example.yml` for the full reference including types, defaults, and which phases consume each variable. +!!! tip "Getting started" + Copy the example and fill in your values: + ```powershell + cp config/variables.example.yml config/variables.yml + ``` + **Never commit** `variables.yml` — it is excluded by `.gitignore` because it contains environment-specific values and Key Vault references. -!!! warning "Secrets" - Never store passwords, tokens, or certificates in parameter files. Use Azure Key Vault references instead. +--- + +## Naming Rules + +| Scope | Convention | Example | +|-------|-----------|---------| +| Top-level sections | `snake_case` | `control_plane`, `session_hosts` | +| Keys within sections | `snake_case` | `subscription_id`, `vm_memory_mb` | +| Booleans | Descriptive name | `enable_entra_id_auth: true` | +| Secrets | `keyvault://` URI | `keyvault://kv-name/secret-name` | +| Example values | IIC fictional identity | `iic.local`, `rg-iic-avd-hp-eus-01`, `kv-iic-platform` | + +--- ## Subscription & Global -| Variable | Type | Default | -|----------|------|---------| -| `azure_subscription_id` | string | — | -| `azure_tenant_id` | string | — | -| `location` | string | `eastus` | -| `resource_group_name` | string | `rg-avd-prod` | +```yaml +subscription: + avd_subscription_id: "00000000-0000-0000-0000-000000000000" + azure_local_subscription_id: "00000000-0000-0000-0000-000000000000" + tenant_id: "00000000-0000-0000-0000-000000000000" + location: "eastus" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `subscription.avd_subscription_id` | string | **Yes** | Azure subscription for AVD control plane and session hosts | — | +| `subscription.azure_local_subscription_id` | string | **Yes** | Azure subscription where the Azure Local cluster is registered | — | +| `subscription.tenant_id` | string | **Yes** | Entra ID tenant | — | +| `subscription.location` | string | **Yes** | Azure region | `eastus` | + +--- + +## Security — Key Vault + +```yaml +security: + key_vault_name: "kv-iic-platform" + key_vault_resource_group: "rg-iic-mgmt-eus-01" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `security.key_vault_name` | string | **Yes** | Platform Key Vault for all `keyvault://` URI resolution | — | +| `security.key_vault_resource_group` | string | **Yes** | Resource group containing the Key Vault | — | + +--- ## Control Plane -| Variable | Type | Default | -|----------|------|---------| -| `host_pool_name` | string | `hp-azurelocal-pool01` | -| `host_pool_type` | string | `Pooled` | -| `load_balancer_type` | string | `BreadthFirst` | -| `max_session_limit` | int | `10` | -| `app_group_name` | string | `ag-avd-desktops` | -| `app_group_type` | string | `Desktop` | -| `workspace_name` | string | `ws-avd-prod` | -| `key_vault_name` | string | `kv-avd-prod-001` | -| `log_analytics_workspace_name` | string | `law-avd-prod` | -| `log_analytics_retention_days` | int | `30` | +```yaml +control_plane: + resource_group: "rg-iic-avd-hp-eus-01" + host_pool_name: "hp-iic-avd-pool01" + host_pool_type: "Pooled" + load_balancer_type: "BreadthFirst" + max_session_limit: 16 + preferred_app_group_type: "Desktop" + personal_assignment_type: "Automatic" + start_vm_on_connect: false + validation_environment: false + custom_rdp_properties: "" + app_group_name: "vdag-iic-avd-eus-01" + app_group_type: "Desktop" + workspace_name: "vdws-iic-avd-eus-01" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `control_plane.resource_group` | string | **Yes** | Resource group for host pool, app group, workspace | — | +| `control_plane.host_pool_name` | string | **Yes** | Host pool name | — | +| `control_plane.host_pool_type` | string | **Yes** | `Pooled` or `Personal` | `Pooled` | +| `control_plane.load_balancer_type` | string | Pooled | `BreadthFirst` or `DepthFirst` | `BreadthFirst` | +| `control_plane.max_session_limit` | integer | Pooled | Max concurrent sessions per host | `16` | +| `control_plane.preferred_app_group_type` | string | **Yes** | `Desktop`, `RailApplications`, or `None` | `Desktop` | +| `control_plane.personal_assignment_type` | string | Personal | `Automatic` or `Direct` | `Automatic` | +| `control_plane.start_vm_on_connect` | boolean | No | Requires Desktop Virtualization Power On Contributor RBAC | `false` | +| `control_plane.validation_environment` | boolean | No | Receives service updates before production | `false` | +| `control_plane.custom_rdp_properties` | string | No | Semicolon-delimited RDP properties | `""` | +| `control_plane.app_group_name` | string | **Yes** | Application group name | — | +| `control_plane.app_group_type` | string | **Yes** | `Desktop` or `RemoteApp` | `Desktop` | +| `control_plane.workspace_name` | string | **Yes** | AVD workspace name | — | + +--- ## Session Hosts -| Variable | Type | Default | -|----------|------|---------| -| `custom_location_id` | string | — | -| `vm_name_prefix` | string | `avd-sh` | -| `vm_count` | int | `2` | -| `vm_size` | string | `Standard_D4s_v3` | -| `image_id` | string | — | -| `vnet_id` | string | — | -| `subnet_name` | string | `default` | -| `key_vault_id` | string | — | +```yaml +session_hosts: + resource_group: "rg-iic-avd-sh-eus-01" + session_host_count: 2 + vm_naming_prefix: "vm-iicavd" + vm_start_index: 1 + vm_processors: 4 + vm_memory_mb: 16384 + vm_admin_username: "avd_admin" + vm_admin_password: "keyvault://kv-iic-platform/avd-local-admin-password" + session_host_os: "Windows-11-Enterprise-Multi-Session" + custom_location_id: "" + logical_network_id: "" + gallery_image_id: "" + storage_path_id: "" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `session_hosts.resource_group` | string | **Yes** | Resource group for session host VMs | — | +| `session_hosts.session_host_count` | integer | **Yes** | Number of session host VMs | `2` | +| `session_hosts.vm_naming_prefix` | string | **Yes** | VMs named `{prefix}-001`, `{prefix}-002`, etc. | `vm-iicavd` | +| `session_hosts.vm_start_index` | integer | No | Starting index for VM numbering | `1` | +| `session_hosts.vm_processors` | integer | **Yes** | vCPUs per session host | `4` | +| `session_hosts.vm_memory_mb` | integer | **Yes** | RAM per VM in MB | `16384` | +| `session_hosts.vm_admin_username` | string | **Yes** | Local admin username | `avd_admin` | +| `session_hosts.vm_admin_password` | string | **Yes** | Key Vault URI — resolved at runtime | — | +| `session_hosts.session_host_os` | string | **Yes** | OS image name | `Windows-11-Enterprise-Multi-Session` | +| `session_hosts.custom_location_id` | string | **Yes** | Azure Local custom location resource ID | — | +| `session_hosts.logical_network_id` | string | **Yes** | Compute logical network resource ID | — | +| `session_hosts.gallery_image_id` | string | **Yes** | Gallery image resource ID | — | +| `session_hosts.storage_path_id` | string | **Yes** | Storage path resource ID | — | + +--- ## Domain Join -| Variable | Type | Default | -|----------|------|---------| -| `domain_fqdn` | string | `iic.local` | -| `domain_join_user` | string | — | -| `ou_path` | string | — | +```yaml +domain: + domain_fqdn: "iic.local" + domain_join_username: "svc.domainjoin" + domain_join_password: "keyvault://kv-iic-platform/domain-join-password" + domain_join_ou_path: "" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `domain.domain_fqdn` | string | **Yes** | Active Directory domain FQDN | `iic.local` | +| `domain.domain_join_username` | string | **Yes** | Service account for domain join | — | +| `domain.domain_join_password` | string | **Yes** | Key Vault URI for domain join password | — | +| `domain.domain_join_ou_path` | string | No | Target OU — empty uses default Computers container | `""` | + +--- + +## Entra ID Authentication + +```yaml +entra_id: + enable_entra_id_auth: false + enroll_in_intune: false + entra_user_login_group_id: "" + entra_admin_login_group_id: "" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `entra_id.enable_entra_id_auth` | boolean | No | Installs AADLoginForWindows extension + RDP SSO | `false` | +| `entra_id.enroll_in_intune` | boolean | No | Register session hosts in Intune MDM | `false` | +| `entra_id.entra_user_login_group_id` | string | No | Entra group object ID for VM User Login RBAC | `""` | +| `entra_id.entra_admin_login_group_id` | string | No | Entra group object ID for VM Administrator Login RBAC | `""` | + +--- ## Tags -| Variable | Type | Default | -|----------|------|---------| -| `environment_tag` | string | `production` | -| `owner_tag` | string | `platform-team` | +```yaml +tags: + Environment: "Production" + Project: "AVD on Azure Local" + ManagedBy: "Infrastructure as Code" + Owner: "Platform Team" + CostCenter: "IT-Infrastructure" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `tags.Environment` | string | No | Environment tag | `Production` | +| `tags.Project` | string | No | Project tag | `AVD on Azure Local` | +| `tags.ManagedBy` | string | No | Managed-by tag | `Infrastructure as Code` | +| `tags.Owner` | string | No | Owner tag | `Platform Team` | +| `tags.CostCenter` | string | No | Cost center tag | `IT-Infrastructure` | + +--- ## Ansible -| Variable | Type | Default | -|----------|------|---------| -| `ansible_connection` | string | `local` | +```yaml +ansible: + ansible_connection: "local" +``` + +| Variable | Type | Required | Description | Default | +|----------|------|:--------:|-------------|---------| +| `ansible.ansible_connection` | string | No | Ansible connection type | `local` | + +--- + +## Key Vault Secret Resolution + +Secrets are never stored in plaintext. The `keyvault://` URI format tells deployment tools to resolve the value at runtime: + +```yaml +vm_admin_password: "keyvault://kv-iic-platform/avd-local-admin-password" +``` + +**Resolution flow:** + +1. Tool parses the URI → vault name: `kv-iic-platform`, secret name: `avd-local-admin-password` +2. Tool calls `az keyvault secret show --vault-name kv-iic-platform --name avd-local-admin-password` +3. Secret value is passed directly to the deployment — never written to disk + +**Required secrets:** + +| Secret Name | Used By | +|------------|---------| +| `avd-local-admin-password` | Local admin password for session host VMs | +| `domain-join-password` | Service account password for domain join | + +--- + +## Tool-Specific Parameter Mapping + +Each automation tool reads from `config/variables.yml` and maps values to its own parameter format: + +| Tool | Parameter File | Location | +|------|---------------|----------| +| **PowerShell** | Reads `config/variables.yml` directly | `config/` | +| **Bicep** | `*.bicepparam` | `avd/bicep/` | +| **Terraform** | `terraform.tfvars` | `avd/terraform/` | +| **ARM** | `*.parameters.json` | `avd/arm/` | +| **Ansible** | `hosts.yml` | `src/ansible/inventory/` | diff --git a/docs/standards/documentation.md b/docs/standards/documentation.md index 9e12156..a8692a5 100644 --- a/docs/standards/documentation.md +++ b/docs/standards/documentation.md @@ -1,66 +1,6 @@ # Documentation Standards -Conventions for all documentation in this repository. Docs are built with MkDocs Material and published via GitHub Actions. +!!! info "Moved to Central Standards" + Documentation standards are now maintained org-wide at the central documentation site. ---- - -## Structure - -All documentation lives in the `docs/` directory. The MkDocs site nav is defined in `mkdocs.yml`. - -``` -docs/ -├── index.md # Home page -├── architecture.md # Architecture overview -├── getting-started.md # Quick start guide -├── avd-deployment-guide.md # Full AVD deployment walkthrough -├── contributing.md # Contribution guidelines -├── reference/ -│ └── variables.md # Variable reference (auto from config) -└── standards/ - ├── index.md # This section overview - ├── scripts.md # Script standards - ├── documentation.md # Documentation standards (this file) - ├── solutions.md # IaC standards - ├── variables.md # Variable/config standards - └── examples.md # Example scenario standards -``` - -## Formatting - -- Use **Markdown** with MkDocs Material extensions (admonitions, tabs, code blocks) -- Use ATX-style headers (`#`, `##`, `###`) -- One sentence per line (for clean diffs) -- Use fenced code blocks with language hints: ` ```powershell `, ` ```yaml `, ` ```bash ` -- Use admonitions for warnings, notes, and tips: - -```markdown -!!! warning - This will delete all data. - -!!! note - Requires PowerShell 7.0+. -``` - -## Style - -- Write in second person ("you") for guides -- Use present tense -- Be direct — avoid filler words -- Use tables for structured reference data -- Link to other docs pages using relative paths (e.g., `[Variables](reference/variables.md)`) - -## MkDocs Build - -The site builds with `mkdocs build --strict`. All warnings are treated as errors: - -- No broken internal links -- No missing nav entries -- No orphaned pages (every `.md` in `docs/` should be in the nav) - -## GitHub Actions - -The deploy workflow (`.github/workflows/deploy-docs.yml`) runs on: - -- Push to `main` when `docs/**` or `mkdocs.yml` change -- Manual dispatch (`workflow_dispatch`) +**Canonical reference:** [Documentation Standards](https://azurelocal.cloud/standards/documentation/documentation-standards) diff --git a/docs/standards/examples.md b/docs/standards/examples.md index 65428a0..761d21d 100644 --- a/docs/standards/examples.md +++ b/docs/standards/examples.md @@ -1,56 +1,6 @@ # Example Standards -Conventions for example scenarios and walkthroughs in this repository. +!!! info "Moved to Central Standards" + Example conventions and the fictional company policy are now maintained org-wide at the central documentation site. ---- - -## Purpose - -Examples demonstrate real-world deployment scenarios using the tools and scripts in this repo. They should be self-contained, reproducible, and educational. - ---- - -## Structure - -Each example should follow this format: - -```markdown -# Example: - -## Overview -Brief description of what this example demonstrates and when you'd use it. - -## Prerequisites -- List of requirements (tools, access, config) - -## Configuration -Show the relevant `config/variables.yml` values for this scenario. - -## Steps -1. Step-by-step instructions -2. With code blocks for each command -3. Expected output after each step - -## Verification -How to confirm the deployment worked correctly. - -## Cleanup -How to tear down resources created by this example. -``` - ---- - -## Guidelines - -- Use **Infinite Improbability Corp (IIC)** as the fictional company in all examples — `iic.local` for domains, `IIC` for NetBIOS, `rg-iic-*` for resources. Never use `contoso`. See [Standards overview](index.md#fictional-identity). -- Show realistic but safe values (no real subscription IDs, IPs, or secrets) -- Include both the happy path and common failure scenarios -- Reference the relevant standards pages for conventions used -- Keep examples focused — one scenario per file -- Use `!!! note` admonitions for important context - ---- - -## Location - -Examples live in `examples/` at the repo root (future). Each example is a self-contained directory or Markdown file. +**Canonical reference:** [Fictional Company Policy](https://azurelocal.cloud/standards/fictional-company-policy) diff --git a/docs/standards/index.md b/docs/standards/index.md index ebb64ce..953a9b2 100644 --- a/docs/standards/index.md +++ b/docs/standards/index.md @@ -1,53 +1,21 @@ # Standards -Standards and conventions for the Azure Virtual Desktop on Azure Local repository. These ensure consistency across all scripts, documentation, infrastructure-as-code, and configuration files. +This repository follows the **org-wide AzureLocal standards** maintained on the central documentation site. ---- - -## Sections - -| Standard | Description | -|----------|-------------| -| [Scripts](scripts.md) | PowerShell, Ansible, and Azure CLI script conventions | -| [Documentation](documentation.md) | MkDocs structure, formatting, and style guide | -| [Solutions](solutions.md) | Bicep, Terraform, ARM, and Ansible IaC conventions | -| [Variables](variables.md) | Central config structure, naming rules, Key Vault patterns | -| [Examples](examples.md) | Example scenario structure and formatting | - ---- - -## Sister Repositories - -| Repository | Purpose | -|------------|---------| -| [azurelocal-avd](https://github.com/AzureLocal/azurelocal-avd) | Azure Virtual Desktop on Azure Local | +!!! info "Central Standards" + All standards are maintained at [azurelocal.cloud/standards](https://azurelocal.cloud/standards/). + This page provides quick links to each relevant standard. --- -## Company & Fictional References - -### Real Identities - -| Name | Usage | -|------|-------| -| **Hybrid Cloud Solutions** | Author/maintainer LLC. Used in script headers (`Author: Hybrid Cloud Solutions`), copyright notices, contact emails (`support@hybridsolutions.cloud`, `info@`, `sales@`, `contact@`), and website links (`hybridsolutions.cloud`). | -| **Azure Local Cloud** | The community project and GitHub org. Shows up in repo URLs, `azurelocal.cloud` site, and org references. Not a fictional identity — this is the real project name. | - -### Fictional Identity - -All example configs, resource names, domains, tenants, and walkthroughs use **one** fictional company: - -| Name | Abbreviation | Domain | Description | -|------|:------------:|--------|-------------| -| **Infinite Improbability Corp** | **IIC** | `iic.local` / `iic.cloud` | The fictional customer/tenant used across all examples. A nod to *The Hitchhiker's Guide to the Galaxy*. | - -**Use IIC everywhere you'd normally reach for `contoso`:** - -- AD domains: `iic.local`, `IIC` (NetBIOS) -- Resource names: `rg-iic-avd-hp-eus-01`, `kv-iic-platform`, `rg-iic-avd-sh-eus-01` -- OUs: `OU=Servers,DC=iic,DC=local` -- User accounts: `svc.iic.deploy`, `admin@iic.local` -- Tenant references: "Infinite Improbability Corp's production AVD environment" - -!!! warning "Consistency" - Never use `contoso`, `fabrikam`, `adventure-works`, or other Microsoft example names. **IIC only.** +## Quick Links + +| Standard | Central Reference | +|----------|------------------| +| Repository Structure | [Repo Structure Standard](https://azurelocal.cloud/standards/repo-structure) | +| Scripting | [Scripting Standards](https://azurelocal.cloud/standards/scripting/scripting-standards) | +| Documentation | [Documentation Standards](https://azurelocal.cloud/standards/documentation/documentation-standards) | +| Infrastructure as Code | [Solution Development Standard](https://azurelocal.cloud/standards/solutions/solution-development-standard) | +| Variables | [Variable Reference](../reference/variables.md) and [Variable Management Standard](https://azurelocal.cloud/docs/implementation/04-variable-management-standard) | +| Examples | [Fictional Company Policy](https://azurelocal.cloud/standards/fictional-company-policy) | +| Naming Conventions | [Naming Conventions](https://azurelocal.cloud/standards/documentation/naming-conventions) | diff --git a/docs/standards/scripts.md b/docs/standards/scripts.md index 6df5e30..bd4f089 100644 --- a/docs/standards/scripts.md +++ b/docs/standards/scripts.md @@ -1,113 +1,6 @@ -# Script Standards +# Scripting Standards -Conventions for all scripts in this repository — PowerShell, Ansible playbooks, and Azure CLI. +!!! info "Moved to Central Standards" + Scripting standards are now maintained org-wide at the central documentation site. ---- - -## PowerShell - -### Logging - -Every script **must** write logs. Use the standard `Write-Log` function: - -```powershell -function Write-Log { - param([string]$Message, [string]$Level = "INFO") - $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - $line = "[$ts] [$Level] $Message" - $line | Out-File -FilePath $script:logFile -Append -Encoding utf8 - switch ($Level) { - "PASS" { Write-Host "[$ts] [PASS] $Message" -ForegroundColor Green } - "FAIL" { Write-Host "[$ts] [FAIL] $Message" -ForegroundColor Red } - "WARN" { Write-Host "[$ts] [WARN] $Message" -ForegroundColor Yellow } - "HEADER" { Write-Host "[$ts] [----] $Message" -ForegroundColor Cyan } - default { Write-Host "[$ts] [INFO] $Message" } - } -} -``` - -**Log levels:** `INFO`, `PASS`, `FAIL`, `WARN`, `HEADER`, `VERBOSE`, `DEBUG` - -**Log file location:** `logs//_.log` - -**Format:** `[yyyy-MM-dd HH:mm:ss] [LEVEL] Message` - -### Secret Management - -- **All secrets** come from Azure Key Vault via `keyvault://` URIs in the config -- Use the `Resolve-KeyVaultRef` function (Az.KeyVault → fallback az CLI) -- **Never** prompt for credentials interactively — fail hard if Key Vault resolution fails -- **Never** put passwords, keys, or tokens in config files, scripts, or logs - -### Idempotency - -Scripts must be safe to re-run: - -- Check if a resource exists before creating it -- Use `-Force` or equivalent where appropriate -- Log "already exists, skipping" rather than failing on duplicates - -### Error Handling - -- Use `try/catch` for operations that can fail -- Log failures with `"FAIL"` level before throwing or exiting -- Exit with non-zero code on fatal errors -- Provide actionable error messages (what failed, what to do about it) - -### Parameters - -- All parameters should have sensible defaults from the central config -- Allow parameter overrides for flexibility: `param > config > error` -- Include a `-WhatIf` switch for dry-run mode -- Include a `-LogPath` parameter to override log file location -- Use `[CmdletBinding()]` and proper param blocks with types - -### Config Loading - -Scripts load `config/variables.yml` as primary, with fallback to legacy paths: - -```powershell -$primaryPath = Join-Path $repoRoot "config\variables.yml" -$legacyPath = Join-Path $repoRoot "solutions\avd\solution-avd.yml" -``` - -### Naming - -- Script files: `Verb-Noun.ps1` (e.g., `Deploy-AVDSessionHosts.ps1`, `Deploy-AVDSessionHosts-ARM.ps1`) -- Functions: `Verb-Noun` (PowerShell approved verbs) -- Variables: `$PascalCase` for parameters, `$camelCase` for local variables - -### Header - -Every script must include a `.SYNOPSIS`, `.DESCRIPTION`, `.PARAMETER`, `.EXAMPLE`, and `.NOTES` comment block. - -### Author Attribution - -Use the real LLC name in script `.NOTES` blocks: - -```powershell -.NOTES - Author: Hybrid Cloud Solutions - Contact: support@hybridsolutions.cloud -``` - -For any example domains, users, or resources in scripts, use **Infinite Improbability Corp (IIC)** — never `contoso`. See [Standards overview](index.md#fictional-identity). - ---- - -## Ansible - -- Playbooks use YAML with 2-space indentation -- Variable names use `snake_case` -- Roles go in `src/ansible/roles/` -- Inventory goes in `src/ansible/inventory/` -- Use `ansible-vault` for any secrets that must be stored locally - ---- - -## Azure CLI - -- Scripts use `#!/bin/bash` shebang -- Check `$?` or use `set -euo pipefail` -- Use `--output none` for commands that don't need output -- Parameterize using environment variables from a `.env` file +**Canonical reference:** [Scripting Standards](https://azurelocal.cloud/standards/scripting/scripting-standards) diff --git a/docs/standards/solutions.md b/docs/standards/solutions.md index 3b891e0..a40b4e3 100644 --- a/docs/standards/solutions.md +++ b/docs/standards/solutions.md @@ -1,111 +1,6 @@ -# Solution Standards +# Solution Development Standards -Conventions for Infrastructure-as-Code (IaC) templates and deployment solutions in this repository. +!!! info "Moved to Central Standards" + IaC and solution development standards are now maintained org-wide at the central documentation site. ---- - -## General Principles - -- **Modular** — break templates into reusable modules/files -- **Parameterized** — no hardcoded values; everything comes from the central config or parameter files -- **Idempotent** — safe to deploy multiple times without side effects -- **Tagged** — all Azure resources get standard tags (project, environment, workload, solution) - ---- - -## Bicep - -| Convention | Standard | -|------------|----------| -| Location | `src/bicep/` | -| Entry point | `main.bicep` | -| Parameters | `main.bicepparam` (example: `main.bicepparam.example`) | -| Modules | `modules/` subdirectory | -| Naming | `camelCase` for parameters, `kebab-case` for resource names | -| API versions | Use the latest stable API version | - -### Module Pattern - -```bicep -// modules/storageAccount.bicep -param storageAccountName string -param location string -param tags object = {} - -resource sa 'Microsoft.Storage/storageAccounts@2023-01-01' = { - name: storageAccountName - location: location - tags: tags - kind: 'StorageV2' - sku: { name: 'Standard_LRS' } -} - -output id string = sa.id -``` - ---- - -## Terraform - -| Convention | Standard | -|------------|----------| -| Location | `src/terraform/` | -| Entry point | `main.tf` | -| Variables | `variables.tf` + `terraform.tfvars.example` | -| Outputs | `outputs.tf` | -| Templates | `templates/` subdirectory | -| State | **Never commit** `*.tfstate`, `*.tfstate.backup`, or `tfplan` | -| Lock file | `.terraform.lock.hcl` is gitignored | - -### File Organization - -``` -src/terraform/ -├── versions.tf # Provider config + version constraints -├── variables.tf # Input variable declarations -├── outputs.tf # Output values -├── locals.tf # Computed locals -├── control-plane.tf # AVD control plane resources -├── session-hosts.tf # Session host resources -├── terraform.tfvars.example # Example variable values -└── README.md -``` - -### Naming - -- Resources: `snake_case` (e.g., `resource "azurerm_resource_group" "avd"`) -- Variables: `snake_case` (e.g., `var.resource_group_name`) -- Outputs: `snake_case` (e.g., `output "storage_account_id"`) - ---- - -## ARM Templates - -| Convention | Standard | -|------------|----------| -| Location | `src/arm/` | -| Template | `azuredeploy.json` | -| Parameters | `azuredeploy.parameters.example.json` | -| Naming | `camelCase` for parameters | - ---- - -## Ansible - -| Convention | Standard | -|------------|----------| -| Location | `src/ansible/` | -| Playbooks | `playbooks/` subdirectory | -| Inventory | `inventory/` subdirectory | -| Roles | `roles/` subdirectory | -| Variable names | `snake_case` | -| YAML indent | 2 spaces | - ---- - -## Security - -- No secrets in IaC files — use Key Vault references or parameter injection -- Use managed identities where possible -- Apply least-privilege RBAC -- Enable resource locks on production resources +**Canonical reference:** [Solution Development Standard](https://azurelocal.cloud/standards/solutions/solution-development-standard) diff --git a/docs/standards/variables.md b/docs/standards/variables.md index baf3936..7303dcc 100644 --- a/docs/standards/variables.md +++ b/docs/standards/variables.md @@ -1,82 +1,11 @@ # 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 legacy prefixes) -- 2-space indentation - -### Sections - -```yaml -subscription: # Azure subscriptions (AVD + Azure Local), tenant, location -security: # Key Vault name and resource group -control_plane: # Host pool, app group, workspace settings -session_hosts: # VM count, prefix, specs, admin creds, Azure Local resource IDs -domain: # AD domain config, join credentials, OU paths -entra_id: # Entra ID SSO, Intune enrollment, RBAC group IDs -tags: # Resource tags -ansible: # Ansible controller details (optional) -``` - -### 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-avd-hp-eus-01`, `kv-iic-platform` | - ---- - -## Key Vault References - -Secrets are **never** stored as plain text in config files. Use the `keyvault://` URI format: - -```yaml -session_hosts: - vm_admin_password: "keyvault://kv-iic-platform/avd-vm-admin-password" -domain: - domain_join_password: "keyvault://kv-iic-platform/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 deploy scripts read the central `config/variables.yml` directly. Legacy config file layouts (e.g., `solution-avd.yml`) are **not** supported — migrate to the new section-based structure. - -- New `config/variables.yml` → required by all deploy scripts -- See `config/variables.example.yml` for a complete reference - ---- - -## Tool-Specific Parameter Files - -| Tool | File | Location | -|------|------|----------| -| PowerShell | `variables.yml` | `config/` | -| Bicep | `*.bicepparam` | `src/bicep/` | -| Terraform | `terraform.tfvars` | `src/terraform/` | -| ARM | `*.parameters.json` | `src/arm/` | -| Ansible | `hosts.yml` | `src/ansible/inventory/` | -| Azure CLI | `parameters.env` | `scripts/` | - -All tool-specific parameter files should derive their values from the central `config/variables.yml`. +- Naming rules and conventions +- Every configuration section with types, defaults, and descriptions +- Key Vault secret resolution +- Tool-specific parameter mapping