Add terraform utility - #103
Conversation
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
❌ Automated Check Results — 2 issue(s) foundAll issues are listed below. Please address all of them before requesting human review.
Once all issues are resolved, push a new commit to re-trigger this check.
|
❌ Automated Check Results — 2 issue(s) foundAll issues are listed below. Please address all of them before requesting human review.
Once all issues are resolved, push a new commit to re-trigger this check.
|
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
Adds utilities/terraform/, a self-contained Terraform module that provisions
a Microsoft Discovery services environment:
* Virtual network with five subnets (workspaceSubnet + agentSubnet
delegated to Microsoft.App/environments)
* User-assigned managed identity + three role assignments
(Storage Blob Data Contributor, Microsoft Discovery Platform Contributor,
AcrPull)
* Storage account (shared-key auth disabled, public network disabled,
TLS1_2, Studio/VS Code CORS) with one AzureRM-managed blob container
* Discovery Supercomputer + node pool
* Discovery Workspace with one chat model deployment + project
* Discovery StorageContainer bound to the AzureRM storage account
Provider split:
* hashicorp/azurerm ~> 4.20 for every platform primitive
* Azure/azapi ~> 2.0 for all Microsoft.Discovery/* resources
(pinned to @2026-02-01-preview) and for the blob container
control-plane call so a data-plane RBAC grant is not required
Also updates utilities/README.md inventory with the new entry.
New scripts in utilities/terraform/: - preflight.sh: deterministic pre-apply checks (RP registration, region allowlist, VM SKU allowlist, compute cores quota) with auto-sourced add-on modules under preflight-checks/ (one concern per file). - destroy.sh: idempotent teardown that pre-drains Discovery children serially, detaches AKS-orphaned NSGs, detects the unrecoverable legionservicelink SAL, and force-deletes surviving MRGs. Terraform changes: - discovery.tf: add explicit depends_on so project creation waits for the chat model deployment to reach Succeeded (RP-side ordering the reference graph does not express). - variables.tf: default location -> uksouth (matches current test region). - .gitignore: exclude bare 'tfplan' and local TESTING-PLAN.md. Docs: - utilities/terraform/README.md: Quickstart TL;DR and preflight callouts in the setup walkthrough. - README.md: trailing newline.
…oy.sh The utility now lets Azure surface the VNet/RG delete failure directly when an orphaned Microsoft.App/environments serviceAssociationLink (legionservicelink) is present. Orchestrating support-ticket workflows and maintaining a quarantined-RG tagging scheme is out of scope for this module. Removes: - --abandon-orphan-vnet CLI flag and ABANDON_ORPHAN_VNET variable. - Step 1.7 (detect_orphaned_sals, print_orphan_sal_guidance) and its ~170 lines of support-ticket template. - SKIP_RG_DELETE branch in step 2. - References to stuck-SAL deadlocks in the header comment block. Kept: - Step 1.5 serial pre-drain of workspaces/supercomputers (still prevents the race that creates orphaned SALs in the first place). - Step 1.6 AKS-orphaned NSG detachment (self-service, unrelated concern).
Bring the Terraform utility up to parity with uami.bicep through the search subnet: - Set NetworkIsolation, discovery.workbench.enableGhcpAiFeatures, and discovery.workbench.enableExtensions tags on the workspace (was version=v2 only). Fixes the broken-hybrid posture where subnet IDs were passed without NetworkIsolation=true, which disabled Cosmos public access with no private endpoint and deadlocked teardown. - Add network_isolation, enable_ghcp_ai_features, enable_extensions variables (default true) and search_subnet_prefix. - Add delegated searchSubnet (sixth subnet, 10.0.6.0/24) to match the bicep VNet. - Update README and terraform.tfvars.example for parity. Out of scope (post-search-subnet in bicep): four-identity least-privilege model and its seven role assignments remain a follow-up.
Port the per-identity least-privilege split from uami.bicep: - Add cluster, kubelet, and workload UAMIs (were all collapsed onto the single workspace identity). - Expand to seven scoped role assignments: workspace gets Discovery Platform Contributor (RG) + Storage Blob Data Contributor (storage); cluster gets Network Contributor (AKS subnet only); kubelet gets Managed Identity Operator (cluster identity) + AcrPull (RG) + Storage Blob Data Contributor (storage); workload gets Storage Blob Data Contributor (storage) only. - Wire the supercomputer clusterIdentity/kubeletIdentity/workloadIdentities to the dedicated identities and update create-ordering depends_on. - Add cluster/kubelet/workload identity name variables, locals, and outputs. - Update README (four identities, seven roles, 27-resource plan).
- Correct README plan summary: thirteen outputs (was twelve) after adding the cluster/kubelet/workload identity outputs. - Add ADR 0001 documenting the network isolation posture decision (default network_isolation=true, always provision the private topology) and link it from the README.
Patch isolationScope = "Regional" onto all four user-assigned managed identities via azapi_update_resource (azurerm 4.x does not expose the property), matching uami.bicep. Regional isolation restricts each identity to source resources in its own region, shrinking the blast radius on compromise and containing identity-plane failures to one region; all source resources here live in var.location. Update README plan summary (31 resources) and the identity section.
…twork - add existing_workspace_id and existing_bookshelf_id (skip their modules) - gate platform supercomputer network behind create_supercomputer_network - root skips the SC network when existing_supercomputer_id is set - resolve created-or-existing IDs for all three; document BYO knobs 🧩 - Generated by Copilot
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
- key chat_model_deployments and projects by a stable logical key - derive the Azure name from an optional name value (defaults to key) - fixes "Invalid for_each argument" on first plan when names derive from the random suffix 🐛 - Generated by Copilot
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
…elf_id - Drop eastus2 from location validation allowlist so terraform validate fails fast instead of misleading users into a ~30m apply failure; mirrors preflight KNOWN_BAD_REGIONS and 06-approved-regions.sh (RI-2) - Point providers.tf comment at the real ADR docs/adr/0001-network-isolation-posture.md (RI-1) - Explain why bookshelf_id uses a ternary instead of coalesce (RI-3) - Sync README region references with the tightened allowlist
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
Teardown is out of scope for this deployment utility; deletion is handled by the root delete-discovery-deployment utility.
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
@microsoft-github-policy-service agree company="Microsoft" |
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
✅ Automated Check Results — All checks passedAll structural, schema, content, documentation, and secret scan checks passed. This PR is ready for human review. The maintainers have been automatically requested.
|
|
|
||
| resource "azurerm_private_endpoint" "blob" { | ||
| name = "pe-blob-${local.storage_account_name}" | ||
| location = data.azurerm_resource_group.rg.location |
There was a problem hiding this comment.
Private endpoint region must follow its subnet's VNet, not the resource group
location resolves to the resource group's region, but subnet_id points at azurerm_subnet.private_endpoint, which lives in azurerm_virtual_network.workspace — created at line 44 with var.location. When the RG's region differs from var.location, apply fails:
Error: creating Private Endpoint (... Private Endpoint Name: "pe-blob-XXXX"):
unexpected status 400 (400 Bad Request) with error: InvalidResourceReference:
Resource .../virtualNetworks/VNET-XXXX referenced by resource
.../privateEndpoints/pe-blob-XXXX was not found. Please make sure that the
referenced resource exists, and that both resources are in the same region.
The message is misleading — the VNet does exist. The real constraint is the region mismatch. Reproduced with an RG in uksouth and location = "swedencentral"; every other resource applied cleanly and this one failed on its own.
| location = data.azurerm_resource_group.rg.location | |
| location = azurerm_virtual_network.workspace.location |
Deriving from the VNet keeps this correct regardless of how var.location is later set.
| # ----------------------------------------------------------------------------- | ||
| resource "azurerm_storage_account" "outputs" { | ||
| name = local.storage_account_name | ||
| location = data.azurerm_resource_group.rg.location |
There was a problem hiding this comment.
Storage account lands in the RG's region instead of var.location
Every other regional resource in this module uses var.location (lines 44, 149, 155, 161, 167). This line and the private endpoint at 230 are the only two using the resource group's location. When they differ, the storage account is provisioned away from the workspace VNet it serves — cross-region latency and egress on all blob output traffic.
| location = data.azurerm_resource_group.rg.location | |
| location = var.location |
Heads-up for anyone who has already applied: location forces replacement on azurerm_storage_account, so existing deployments need a data migration rather than a straight apply. Might be worth calling out in the README.
Summary
A Terraform utility under
utilities/terraform/that provisions a complete Microsoft Discovery environment as an alternative to the Bicep path. It deploys the Discovery control plane (supercomputer with a node pool; workspace with a chat model deployment and project; storage container; optional bookshelf) together with the platform prerequisites it needs (VNets and subnets, four least-privilege managed identities with scoped RBAC, a storage account, and private networking).Approach
Microsoft.Discovery/*resource (not yet in the AzureRM catalog), pinned to GA API2026-06-01; AzureRM handles all platform primitives for strongly-typed schemas and stable state.modules/platform(network, identities, storage, RBAC, storage container) andmodules/control-plane/*(supercomputer, workspace, bookshelf, tool). The root (main.tf) composes them into a single-apply end-to-end deployment — there is one definition of each resource, with no inlined Discovery or platform resources in the root.modules/control-plane/toolships as a standalone control-plane module for provisioning Discovery Tools. It is intentionally not wired into the end-to-end root — it is an extension point for teams that provision Tools, callable directly with your own resource IDs.discovery.overridemrgregiontag, backed by a peered supercomputer VNet and cross-VNet private DNS.common_tagsplus per-resource tag inputs merge so required platform tags are always preserved.preflight.sh(RP registration, region and VM-SKU allowlists, compute-cores quota, pluggable checks underpreflight-checks/) and an idempotentdestroy.shthat handles Discovery-specific teardown ordering.Type of change
Other: New IaC deployment utility under
utilities/terraform/. Parallels the existing Bicep deployment path; no agent, starter-kit, schema, or workflow surface is touched.Related issue / tracking
n/a
Schema impact
Validation checklist
terraform fmtandterraform validatepass for the root and every module.terraform applyinto a fresh resource group (supercomputer + node pool, workspace + chat model + project, storage container).docs/adr/(0001 network isolation posture, 0002 single full-stack path)..terraform/,*.tfplan, and real*.tfvarsare gitignored.Reviewer notes
Microsoft.Discovery/*types are not in the AzureRM catalog yet. See the README intro for the rationale.modules/control-plane/*modules are the only definition. Reusing an existing resource means calling those same modules directly, so there is no duplicate to drift.Succeededchat model, which the reference graph does not otherwise express; the workspace module encodes this withdepends_onbetween its children.preflight-checks/NN-*.share auto-sourced; the contract is documented in the README.eastus,uksouth,swedencentral. (eastus2is advertised by the Discovery RP but rejects new supercomputer creates, so it is excluded by thelocationvalidation andpreflight.sh.)