├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── azure.yaml ├── bicep ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── docs │ ├── architecture.png │ ├── defaults.md │ ├── demo.gif │ ├── examples.md │ ├── how_to_use.md │ └── parameters.md ├── infra │ ├── common │ │ ├── build-cloudinit.yaml │ │ └── types.bicep │ ├── components │ │ ├── bing-search │ │ │ ├── main.bicep │ │ │ └── readme.md │ │ └── vnet-peering │ │ │ └── main.bicep │ ├── helpers │ │ ├── deploy-subnets-to-vnet │ │ │ ├── main.bicep │ │ │ └── readme.md │ │ ├── enrich-subnets-with-nsgs │ │ │ └── main.bicep │ │ └── setup-subnets-for-vnet │ │ │ └── main.bicep │ ├── main.bicep │ ├── main.bicepparam │ └── wrappers │ │ ├── avm.ptn.ai-ml.ai-foundry.bicep │ │ ├── avm.res.api-management.service.bicep │ │ ├── avm.res.app-configuration.configuration-store.bicep │ │ ├── avm.res.app.container-app.bicep │ │ ├── avm.res.app.managed-environment.bicep │ │ ├── avm.res.compute.build-vm.bicep │ │ ├── avm.res.compute.jump-vm.bicep │ │ ├── avm.res.compute.virtual-machine.bicep │ │ ├── avm.res.container-registry.registry.bicep │ │ ├── avm.res.document-db.database-account.bicep │ │ ├── avm.res.insights.component.bicep │ │ ├── avm.res.key-vault.vault.bicep │ │ ├── avm.res.maintenance.maintenance-configuration.bicep │ │ ├── avm.res.network.application-gateway.bicep │ │ ├── avm.res.network.azure-firewall.bicep │ │ ├── avm.res.network.firewall-policy.bicep │ │ ├── avm.res.network.network-security-group.bicep │ │ ├── avm.res.network.private-dns-zone.bicep │ │ ├── avm.res.network.private-endpoint.bicep │ │ ├── avm.res.network.public-ip-address.bicep │ │ ├── avm.res.network.virtual-network.bicep │ │ ├── avm.res.network.waf-policy.bicep │ │ ├── avm.res.operational-insights.workspace.bicep │ │ ├── avm.res.search.search-service.bicep │ │ └── avm.res.storage.storage-account.bicep ├── scripts │ ├── documentation.ps1 │ ├── postprovision.ps1 │ ├── postprovision.sh │ ├── preprovision.ps1 │ └── preprovision.sh └── version.json ├── docs ├── Compute.md ├── Cost-Optimization.md ├── Data.md ├── Governance.md ├── Identity.md ├── Monitoring.md ├── Networking.md ├── Operational-Excellence.md ├── Performance-Efficiency.md ├── Platform-Automation.md ├── Reliability.md ├── Resource-Organization.md ├── Security.md └── service-inventory-config.md ├── media ├── AI-Landing-Zone-with-platform.png └── AI-Landing-Zone-without-platform.png └── terraform ├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── avm_module_issue.yml │ ├── avm_question_feedback.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── 1es-runner-auth-docker │ │ └── action.yml │ ├── avmfix │ │ └── action.yml │ ├── docs-check │ │ └── action.yml │ ├── e2e-getexamples │ │ └── action.yml │ └── linting │ │ └── action.yml ├── copilot-instructions.md ├── policies │ ├── eventResponder.yml │ └── scheduledSearches.yml └── workflows │ ├── pr-check.yml │ └── test-examples-template.yml ├── .gitignore ├── .terraform-docs.yml ├── .vscode ├── extensions.json ├── mcp.json └── settings.json ├── AGENTS.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── _footer.md ├── _header.md ├── avm ├── avm.bat ├── avm.ps1 ├── examples ├── .terraform-docs.yml ├── README.md ├── default │ ├── README.md │ ├── _footer.md │ ├── _header.md │ ├── exceptions │ │ └── avmsec.rego │ ├── main.tf │ └── variables.tf └── standalone │ ├── README.md │ ├── _footer.md │ ├── _header.md │ ├── exceptions │ └── avmsec.rego │ ├── main.tf │ └── variables.tf ├── locals.apim.tf ├── locals.build.tf ├── locals.compute.tf ├── locals.foundry.tf ├── locals.genai_services.tf ├── locals.jumpvm.tf ├── locals.knowledge_sources.tf ├── locals.monitoring.tf ├── locals.networking.firewallpolicy.tf ├── locals.networking.nsgs.tf ├── locals.networking.tf ├── locals.tf ├── main.apim.tf ├── main.build.tf ├── main.compute.tf ├── main.foundry.tf ├── main.genai_app_resources.tf ├── main.genai_services.tf ├── main.jumpvm.tf ├── main.knowledge_sources.tf ├── main.monitoring.tf ├── main.networking.tf ├── main.telemetry.tf ├── main.tf ├── modules ├── .terraform-docs.yml ├── README.md └── example_hub_vnet │ ├── README.md │ ├── _footer.md │ ├── _header.md │ ├── locals.tf │ ├── main.tf │ ├── outputs.tf │ ├── terraform.tf │ └── variables.tf ├── outputs.networking.tf ├── outputs.tf ├── terraform.tf ├── tests └── README.md ├── variables.apim.tf ├── variables.build.tf ├── variables.compute.tf ├── variables.foundry.tf ├── variables.genai_services.tf ├── variables.jumpvm.tf ├── variables.knowledge_sources.tf ├── variables.monitoring.tf ├── variables.networking.tf └── variables.tf /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | AI Landing Zone Core Team will aim to respond within 3 business days to get a meaningful response for any new issues. 6 | 7 | ### Design Framework 8 | 9 | For issues and feedback related to Design Framework, uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. Issues can be created and searched through for existing [issues here](https://github.com/Azure/AI-Landing-Zones/issues). 10 | 11 | ### Reference Architectures 12 | 13 | For issues and feedback related to Reference Architectures, uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. Issues can be created and searched through for existing [issues here](https://github.com/Azure/AI-Landing-Zones/issues). 14 | 15 | ### Portal Implementation 16 | 17 | For issues and feedback related to Portal Implementation, uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. Issues can be created and searched through for existing [issues here](https://github.com/Azure/AI-Landing-Zones/issues). 18 | 19 | ### Terraform Implementation 20 | 21 | For issues and feedback related to Terraform Implementation, use GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. Issues can be created and searched through for existing [issues here](https://github.com/Azure/terraform-azurerm-avm-ptn-aiml-landing-zone/issues). 22 | 23 | ### Bicep Implementation 24 | 25 | For issues and feedback related to Bicep Implementation, use GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. Issues can be created and searched through for existing [issues here](https://github.com/Azure/bicep-avm-ptn-aiml-landing-zone/issues). 26 | 27 | ## Community Support Policy 28 | 29 | AI Landing Zone Core Team will aim to respond within 3 business days to get a meaningful response for any new issues. We may ask you to create an Azure support request once we have triaged the issue following the process documented [here](https://learn.microsoft.com/azure/azure-portal/supportability/how-to-create-azure-support-request). 30 | 31 | ## Microsoft Support Policy 32 | 33 | Support for this project is limited to the resources listed above while in preview. Once GA, customer will be able to create a support ticket with Microsoft Support. 34 | -------------------------------------------------------------------------------- /azure.yaml: -------------------------------------------------------------------------------- 1 | name: ai-landing-zones 2 | metadata: 3 | template: ai-landing-zones 4 | 5 | infra: 6 | path: bicep/deploy 7 | module: ./main 8 | 9 | hooks: 10 | preprovision: 11 | windows: 12 | shell: pwsh 13 | run: ./bicep/scripts/preprovision.ps1 14 | interactive: true 15 | posix: 16 | shell: sh 17 | run: ./bicep/scripts/preprovision.sh 18 | interactive: true 19 | 20 | postprovision: 21 | windows: 22 | shell: pwsh 23 | run: ./bicep/scripts/postprovision.ps1 24 | posix: 25 | shell: sh 26 | run: ./bicep/scripts/postprovision.sh 27 | -------------------------------------------------------------------------------- /bicep/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The latest version of the changelog can be found [here](https://github.com/Azure/bicep-registry-modules/blob/main/avm/ptn/ai-ml/landing-zone/CHANGELOG.md). 4 | 5 | ## 0.1.2 6 | 7 | ### Changes 8 | 9 | - Fixed Linux execution permissions for preprovision.sh and postprovision.sh scripts. 10 | - Added Azure Bastion subnet NSG with required security rules per Microsoft documentation. 11 | - Adapted to new directory structure. 12 | 13 | ## 0.1.1 14 | 15 | ### Changes 16 | 17 | - Adopted **Template Specs** to bypass ARM 4 MB template size limit (wrappers, pre/post provision scripts). 18 | - Simplified and clarified `README.md`. 19 | - Added `docs/defaults.md` with parameter defaults. 20 | - Updated `azure.yaml` (project rename, paths, hooks). 21 | 22 | ### Breaking Changes 23 | 24 | - None 25 | 26 | ## 0.1.0 27 | 28 | ### Changes 29 | 30 | - Initial version 31 | 32 | 33 | ## 0.1.1 34 | 35 | ### Changes 36 | 37 | - Adopted **Template Specs** to bypass ARM 4 MB template size limit (wrappers, pre/post provision scripts). 38 | - Simplified and clarified `README.md`. 39 | - Added `docs/defaults.md` with parameter defaults. 40 | - Updated `azure.yaml` (project rename, paths, hooks). 41 | 42 | ### Breaking Changes 43 | 44 | - None 45 | 46 | ## 0.1.0 47 | 48 | ### Changes 49 | 50 | - Initial version 51 | -------------------------------------------------------------------------------- /bicep/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /bicep/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /bicep/README.md: -------------------------------------------------------------------------------- 1 | # AI Landing Zone — Bicep Implementation 2 | 3 | This repository contains a **Bicep template** that is built on top of **Azure Verified Modules (AVM)** together with a few custom modules. It provisions a secure and configurable environment for **generative AI workloads** on Azure. 4 | 5 | ## Architecture 6 | 7 | This architecture delivers a full **AI Landing Zone** built around **Azure AI Foundry**. The **AI Foundry Agent service**, together with **AI Search, Cosmos DB, Storage, and Key Vault**, operates securely and seamlessly. A dedicated **Azure Container Apps** environment enables custom **GenAI applications**, and supporting services cover configuration, data, and observability. Thanks to its modular design, you can deploy everything or only the components you need. 8 | 9 | ![Architecture](./docs/architecture.png) 10 | *AI Landing Zone* 11 | 12 | Flexibility comes from **deployment options**: you choose whether to create or reuse each service. This approach supports both greenfield deployments and integration with an existing platform landing zone. 13 | 14 | Network isolation is enabled by default, routing all traffic through Private Endpoints. Name resolution uses Private DNS zones created during deployment or linked to existing platform zones. 15 | 16 | ## Documentation 17 | 18 | * [**How to deploy the Landing Zone.**](./docs/how_to_use.md) 19 | Step-by-step instructions on creating or reusing resources, setting up isolation, and configuring parameters. Includes a minimal example and notes on running `azd provision`. 20 | 21 | ![Demo](./docs/demo.gif) 22 | 23 | * [**Parameter reference.**](./docs/parameters.md) 24 | A complete list of parameters and outputs, along with the related resources and modules, aligned with the strongly typed contracts defined in [`types.bicep`](./infra/common/types.bicep). -------------------------------------------------------------------------------- /bicep/SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which 6 | includes all source code repositories in our GitHub organizations. 7 | 8 | **Please do not report security vulnerabilities through public GitHub issues.** 9 | 10 | For security reporting information, locations, contact information, and policies, 11 | please review the latest guidance for Microsoft repositories at 12 | [https://aka.ms/SECURITY.md](https://aka.ms/SECURITY.md). 13 | 14 | -------------------------------------------------------------------------------- /bicep/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. 7 | - **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /bicep/docs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/AI-Landing-Zones/794d40870ba9c7c153feff9c8572c9d0f2f37505/bicep/docs/architecture.png -------------------------------------------------------------------------------- /bicep/docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/AI-Landing-Zones/794d40870ba9c7c153feff9c8572c9d0f2f37505/bicep/docs/demo.gif -------------------------------------------------------------------------------- /bicep/docs/how_to_use.md: -------------------------------------------------------------------------------- 1 | # Deploying AI Landing Zone 2 | 3 | ## Table of Contents 4 | 5 | 1. [Prerequisites](#2-prerequisites) 6 | 2. [Quick start with azd](#3-quick-start-with-azd) 7 | 3. [Configuration options](#4-configuration-options) 8 | 4. [Reference docs](#5-reference-docs) 9 | 5. [Important notes](#6-important-notes) 10 | 6. [CI/CD pipelines (overview)](#7-cicd-pipelines-overview) 11 | 12 | --- 13 | 14 | ## 1) Prerequisites 15 | 16 | * **Azure CLI** and **Azure Developer CLI** installed and signed in 17 | * A **resource group** in your target subscription 18 | * **Owner** or **Contributor + User Access Administrator** permissions on the subscription 19 | 20 | ## 2) Quick start with azd 21 | 22 | ### Deployment steps 23 | 24 | 1. **Sign in to Azure** 25 | 26 | ```bash 27 | az login 28 | ``` 29 | 30 | 2. **Create the resource group** where you're gonna deploy the AI Landing Zone Resources 31 | 32 | ```bash 33 | az group create --name "rg-aiml-dev" --location "eastus2" 34 | ``` 35 | 36 | 3. **Set environment variables** `AZURE_LOCATION`, `AZURE_RESOURCE_GROUP`, `AZURE_SUBSCRIPTION_ID`. 37 | 38 | *PowerShell*: 39 | 40 | ```powershell 41 | $env:AZURE_LOCATION = "eastus2" 42 | $env:AZURE_RESOURCE_GROUP = "rg-aiml-dev" 43 | $env:AZURE_SUBSCRIPTION_ID = "00000000-1111-2222-3333-444444444444" 44 | ``` 45 | 46 | *bash*: 47 | 48 | ```bash 49 | export AZURE_LOCATION="eastus2" 50 | export AZURE_RESOURCE_GROUP="rg-aiml-dev" 51 | export AZURE_SUBSCRIPTION_ID="00000000-1111-2222-3333-444444444444" 52 | ``` 53 | 54 | 4. **Initialize the project** 55 | 56 | In an empty folder (e.g., `deploy`), run: 57 | 58 | ```bash 59 | azd init -t Azure/AI-Landing-Zones -e aiml-dev 60 | ``` 61 | 62 | 5. **(Optional) Customize parameters** 63 | 64 | Edit `bicep/infra/main.bicepparam` if you want to adjust deployment options. 65 | 66 | 6. **Provision the infrastructure** 67 | 68 | ```bash 69 | azd provision 70 | ``` 71 | 72 | > [!NOTE] 73 | > Provisioning uses Template Specs to bypass the 4 MB ARM template size limit. 74 | > Pre-provision scripts build and publish them, while post-provision scripts remove them after success. 75 | 76 | > [!TIP] 77 | > **Alternative deployment with Azure CLI**: If you prefer using Azure CLI instead of `azd`, skip step 4 (initialize) and replace step 6 with `az deployment group create`. Ensure you run the pre-provision script before deployment and the post-provision script after deployment. 78 | 79 | ## 3) Configuration options 80 | 81 | Update parameters in the `bicep/infra/main.bicepparam` file: 82 | 83 | ```bicep 84 | using 'main.bicep' 85 | 86 | param location = 'eastus2' 87 | param baseName = 'myailz' 88 | param deployToggles = { 89 | acaEnvironmentNsg: true 90 | agentNsg: true 91 | apiManagement: true 92 | ... 93 | storageAccount: true 94 | virtualNetwork: true 95 | wafPolicy: true 96 | } 97 | param resourceIds = {} 98 | param flagPlatformLandingZone = false 99 | ``` 100 | 101 | The template supports flexible deployment patterns through parameter configuration: 102 | 103 | ### Platform Integration 104 | 105 | * **Standalone mode**: Creates all networking and DNS resources 106 | * **Platform-integrated mode**: Reuses existing platform DNS zones and networking 107 | 108 | ### Resource Reuse 109 | 110 | * **New resources**: Template creates all components from scratch 111 | * **Existing resources**: Reuse components via `resourceIds` parameter 112 | * **Hybrid**: Mix of new and existing resources as needed 113 | 114 | ### AI Foundry Options 115 | 116 | * **Full setup**: AI Foundry with all dependencies (Search, Cosmos DB, Key Vault, and Storage) 117 | * **Project only**: AI Foundry project only (no Agent Service or dependencies) 118 | * **Custom models**: Configure specific AI model deployments 119 | 120 | --- 121 | 122 | ## 4) Reference docs 123 | 124 | For detailed configuration and examples, see: 125 | 126 | * **[Parameters](./parameters.md)** — Complete parameter reference 127 | * **[Defaults](./defaults.md)** — Default values for all input parameters 128 | * **[Examples](./examples.md)** — Common deployment scenarios 129 | 130 | --- 131 | 132 | ## 5) Important notes 133 | 134 | * **Naming**: If you leave names blank, the template generates valid names from the `baseName` parameter 135 | * **Global resources**: Storage accounts and Container Registry require globally unique names 136 | * **Platform integration**: Set `flagPlatformLandingZone = true` to integrate with existing platform DNS zones 137 | * **VM deployment**: Build/Jump VMs only deploy when required parameters are provided (SSH keys, passwords) 138 | 139 | --- 140 | 141 | ## 6) CI/CD pipelines (overview) 142 | 143 | Basic automation can be added later via `azd pipeline config`, which scaffolds either a GitHub Actions workflow or an Azure DevOps pipeline and sets up identity (OIDC) plus required variables. For deeper guidance, refer to the official docs: [https://learn.microsoft.com/azure/developer/azure-developer-cli/configure-devops-pipeline](https://learn.microsoft.com/azure/developer/azure-developer-cli/configure-devops-pipeline) 144 | 145 | Minimal workflow: 146 | 147 | ```bash 148 | azd pipeline config 149 | ``` 150 | 151 | This is usually enough for most teams to get a provisioning pipeline started; customize retention, approvals, and promotion flows in your organization’s standard DevOps process. 152 | 153 | -------------------------------------------------------------------------------- /bicep/infra/common/build-cloudinit.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | package_update: true 3 | package_upgrade: true 4 | packages: [curl, jq, tar, apt-transport-https] 5 | write_files: 6 | - path: /opt/runner/env.sh 7 | permissions: "0644" 8 | owner: root:root 9 | content: | 10 | RUNNER_KIND="{0}" 11 | AZP_URL="{1}" 12 | AZP_POOL="{2}" 13 | AZP_AGENT_NAME="{3}" 14 | AZP_WORK="{4}" 15 | GH_OWNER="{5}" 16 | GH_REPO="{6}" 17 | GH_LABELS="{7}" 18 | GH_AGENT_NAME="{8}" 19 | GH_WORK="{9}" 20 | AZP_PAT="{10}" 21 | GH_PAT="{11}" 22 | runcmd: 23 | - [ bash, -lc, "useradd -m {12} -s /bin/bash || true" ] 24 | - | 25 | bash -lc "mkdir -p /home/{12}/.ssh && \ 26 | echo '{13}' >> /home/{12}/.ssh/authorized_keys && \ 27 | chown -R {12}:{12} /home/{12}/.ssh && \ 28 | chmod 600 /home/{12}/.ssh/authorized_keys" 29 | - | 30 | bash -lc "if [ \"$RUNNER_KIND\" = \"azdo\" ]; then 31 | AGENT_DIR=\"$AZP_WORK\"; if [ -z \"$AGENT_DIR\" ]; then AGENT_DIR=/opt/azdo-agent; fi; mkdir -p \"$AGENT_DIR\"; cd \"$AGENT_DIR\"; 32 | curl -LsS https://vstsagentpackage.azureedge.net/agent/3.240.1/vsts-agent-linux-x64-3.240.1.tar.gz -o agent.tar.gz; 33 | tar zxvf agent.tar.gz; 34 | ./bin/installdependencies.sh; 35 | A_NAME=\"$AZP_AGENT_NAME\"; if [ -z \"$A_NAME\" ]; then A_NAME=build-{14}; fi; 36 | WORKF=\"$AZP_WORK\"; if [ -z \"$WORKF\" ]; then WORKF=_work; fi; 37 | ./config.sh --unattended --url \"$AZP_URL\" --auth pat --token \"$AZP_PAT\" --pool \"$AZP_POOL\" --agent \"$A_NAME\" --work \"$WORKF\" --replace; 38 | ./svc.sh install; ./svc.sh start; 39 | else 40 | RUNNER_DIR=\"$GH_WORK\"; if [ -z \"$RUNNER_DIR\" ]; then RUNNER_DIR=/opt/github-runner; fi; mkdir -p \"$RUNNER_DIR\"; cd \"$RUNNER_DIR\"; 41 | LATEST=$(curl -s https://api.github.com/repos/actions/runner/releases/latest | jq -r .tag_name); 42 | LATEST_NO_V=$(echo \"$LATEST\" | sed 's/^v//'); 43 | curl -Lo actions-runner-linux-x64-$LATEST_NO_V.tar.gz https://github.com/actions/runner/releases/download/$LATEST/actions-runner-linux-x64-${LATEST_NO_V}.tar.gz; 44 | tar xzf actions-runner-linux-x64-$LATEST_NO_V.tar.gz; 45 | token=$(curl -s -X POST -H \"Authorization: token $GH_PAT\" -H \"Accept: application/vnd.github+json\" https://api.github.com/repos/$GH_OWNER/$GH_REPO/actions/runners/registration-token | jq -r .token); 46 | LABELS=\"$GH_LABELS\"; if [ -z \"$LABELS\" ]; then LABELS=self-hosted,linux,x64; fi; 47 | G_NAME=\"$GH_AGENT_NAME\"; if [ -z \"$G_NAME\" ]; then G_NAME=build-{14}; fi; 48 | WORKG=\"$GH_WORK\"; if [ -z \"$WORKG\" ]; then WORKG=_work; fi; 49 | ./config.sh --unattended --url https://github.com/$GH_OWNER/$GH_REPO --token \"$token\" --labels \"$LABELS\" --name \"$G_NAME\" --work \"$WORKG\" --replace; 50 | ./svc.sh install; ./svc.sh start; 51 | fi" -------------------------------------------------------------------------------- /bicep/infra/components/bing-search/main.bicep: -------------------------------------------------------------------------------- 1 | metadata name = 'bing-search' 2 | metadata description = 'Create-or-reuse a Bing Grounding account and its Cognitive Services connection to be used by Azure AI Foundry.' 3 | 4 | @description('Conditional. The name of the Azure Cognitive Services account to be used for the Bing Search tool. Required if `enableBingSearchConnection` is true.') 5 | param accountName string 6 | 7 | @description('Conditional. The name of the Azure Cognitive Services Project. Required if `enableBingSearchConnection` is true.') 8 | param projectName string 9 | 10 | @description('Conditional. The name to assign to the Bing Search resource instance (used when creating a new account). Required if `enableBingSearchConnection` is true.') 11 | param bingSearchName string 12 | 13 | @description('Conditional. The name to assign to the Bing Search connection in the project. Required if `enableBingSearchConnection` is true.') 14 | param bingConnectionName string = '${bingSearchName}-connection' 15 | 16 | @description('Optional. Existing Bing Grounding account resource ID to reuse instead of creating a new one.') 17 | param existingResourceId string = '' 18 | 19 | // Resolve create vs reuse 20 | var varIsReuse = !empty(existingResourceId) 21 | var varIdSegs = split(existingResourceId, '/') 22 | var varExSub = length(varIdSegs) >= 3 ? varIdSegs[2] : '' 23 | var varExRg = length(varIdSegs) >= 5 ? varIdSegs[4] : '' 24 | var varExName = length(varIdSegs) >= 1 ? last(varIdSegs) : '' 25 | 26 | // Cognitive Services account (same resource group as current deployment) 27 | #disable-next-line BCP081 28 | resource account_name_resource 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = { 29 | name: accountName 30 | scope: resourceGroup() 31 | } 32 | 33 | // Reuse path: declare existing Bing account 34 | #disable-next-line BCP081 35 | resource existingBing 'Microsoft.Bing/accounts@2025-05-01-preview' existing = if (varIsReuse) { 36 | name: varExName 37 | scope: resourceGroup(varExSub, varExRg) 38 | } 39 | 40 | // Create path: create Bing account (global location) 41 | #disable-next-line BCP081 42 | resource bingAccount 'Microsoft.Bing/accounts@2025-05-01-preview' = if (!varIsReuse) { 43 | name: bingSearchName 44 | location: 'global' 45 | kind: 'Bing.Grounding' 46 | sku: { 47 | name: 'G1' 48 | } 49 | } 50 | 51 | // Effective props for both paths 52 | var varBingId = varIsReuse ? existingResourceId : bingAccount.id 53 | var varBingEndpoint = varIsReuse ? existingBing!.properties.endpoint : bingAccount!.properties.endpoint 54 | var varBingKey = varIsReuse ? existingBing!.listKeys().key1 : bingAccount!.listKeys().key1 55 | var varBingLocation = varIsReuse ? existingBing!.location : 'global' 56 | 57 | // Create the Cognitive Services connection under the AI Services account 58 | #disable-next-line BCP081 59 | resource bing_search_account_connection 'Microsoft.CognitiveServices/accounts/connections@2025-06-01' = { 60 | name: bingConnectionName 61 | parent: account_name_resource 62 | properties: { 63 | category: 'GroundingWithBingSearch' 64 | target: varBingEndpoint 65 | authType: 'ApiKey' 66 | credentials: { 67 | key: varBingKey 68 | } 69 | isSharedToAll: true 70 | metadata: { 71 | ApiType: 'Azure' 72 | Location: varBingLocation 73 | ResourceId: varBingId 74 | } 75 | } 76 | } 77 | 78 | // Outputs 79 | @description('Resource ID of the Bing Grounding account (created or reused).') 80 | output resourceId string = varBingId 81 | 82 | @description('Connection ID path under the AI services project.') 83 | output bingConnectionId string = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.CognitiveServices/accounts/${accountName}/projects/${projectName}/connections/${bingConnectionName}' 84 | 85 | @description('Name of the resource group where the Bing Grounding account is deployed (same as the deployment resource group when creating a new account, or the existing account resource group when reusing).') 86 | output resourceGroupName string = resourceGroup().name 87 | -------------------------------------------------------------------------------- /bicep/infra/components/bing-search/readme.md: -------------------------------------------------------------------------------- 1 | # bing-search `[AiMl/LandingZoneComponentsBingSearch]` 2 | 3 | Create-or-reuse a Bing Grounding account and its Cognitive Services connection to be used by Azure AI Foundry. 4 | 5 | ## Navigation 6 | 7 | - [Resource Types](#Resource-Types) 8 | - [Parameters](#Parameters) 9 | - [Outputs](#Outputs) 10 | 11 | ## Resource Types 12 | 13 | | Resource Type | API Version | References | 14 | | :-- | :-- | :-- | 15 | | `Microsoft.Bing/accounts` | 2025-05-01-preview | | 16 | | `Microsoft.CognitiveServices/accounts/connections` | 2025-06-01 | | 17 | 18 | ## Parameters 19 | 20 | **Conditional parameters** 21 | 22 | | Parameter | Type | Description | 23 | | :-- | :-- | :-- | 24 | | [`accountName`](#parameter-accountname) | string | The name of the Azure Cognitive Services account to be used for the Bing Search tool. Required if `enableBingSearchConnection` is true. | 25 | | [`bingConnectionName`](#parameter-bingconnectionname) | string | The name to assign to the Bing Search connection in the project. Required if `enableBingSearchConnection` is true. | 26 | | [`bingSearchName`](#parameter-bingsearchname) | string | The name to assign to the Bing Search resource instance (used when creating a new account). Required if `enableBingSearchConnection` is true. | 27 | | [`projectName`](#parameter-projectname) | string | The name of the Azure Cognitive Services Project. Required if `enableBingSearchConnection` is true. | 28 | 29 | **Optional parameters** 30 | 31 | | Parameter | Type | Description | 32 | | :-- | :-- | :-- | 33 | | [`existingResourceId`](#parameter-existingresourceid) | string | Existing Bing Grounding account resource ID to reuse instead of creating a new one. | 34 | 35 | ### Parameter: `accountName` 36 | 37 | The name of the Azure Cognitive Services account to be used for the Bing Search tool. Required if `enableBingSearchConnection` is true. 38 | 39 | - Required: Yes 40 | - Type: string 41 | 42 | ### Parameter: `bingConnectionName` 43 | 44 | The name to assign to the Bing Search connection in the project. Required if `enableBingSearchConnection` is true. 45 | 46 | - Required: No 47 | - Type: string 48 | - Default: `[format('{0}-connection', parameters('bingSearchName'))]` 49 | 50 | ### Parameter: `bingSearchName` 51 | 52 | The name to assign to the Bing Search resource instance (used when creating a new account). Required if `enableBingSearchConnection` is true. 53 | 54 | - Required: Yes 55 | - Type: string 56 | 57 | ### Parameter: `projectName` 58 | 59 | The name of the Azure Cognitive Services Project. Required if `enableBingSearchConnection` is true. 60 | 61 | - Required: Yes 62 | - Type: string 63 | 64 | ### Parameter: `existingResourceId` 65 | 66 | Existing Bing Grounding account resource ID to reuse instead of creating a new one. 67 | 68 | - Required: No 69 | - Type: string 70 | - Default: `''` 71 | 72 | ## Outputs 73 | 74 | | Output | Type | Description | 75 | | :-- | :-- | :-- | 76 | | `bingConnectionId` | string | Connection ID path under the AI services project. | 77 | | `resourceGroupName` | string | Name of the resource group where the Bing Grounding account is deployed (same as the deployment resource group when creating a new account, or the existing account resource group when reusing). | 78 | | `resourceId` | string | Resource ID of the Bing Grounding account (created or reused). | -------------------------------------------------------------------------------- /bicep/infra/components/vnet-peering/main.bicep: -------------------------------------------------------------------------------- 1 | // Simple VNet peering component 2 | targetScope = 'resourceGroup' 3 | 4 | @description('Required. Local VNet name to create peering from.') 5 | param localVnetName string 6 | 7 | @description('Required. Name for the peering resource.') 8 | param remotePeeringName string 9 | 10 | @description('Required. Resource ID of the remote VNet to peer with.') 11 | param remoteVirtualNetworkResourceId string 12 | 13 | @description('Optional. Allow virtual network access.') 14 | param allowVirtualNetworkAccess bool = true 15 | 16 | @description('Optional. Allow forwarded traffic.') 17 | param allowForwardedTraffic bool = true 18 | 19 | @description('Optional. Allow gateway transit.') 20 | param allowGatewayTransit bool = false 21 | 22 | @description('Optional. Use remote gateways.') 23 | param useRemoteGateways bool = false 24 | 25 | // Create the peering from local to remote VNet 26 | resource vnetPeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2024-03-01' = { 27 | name: '${localVnetName}/${remotePeeringName}' 28 | properties: { 29 | allowVirtualNetworkAccess: allowVirtualNetworkAccess 30 | allowForwardedTraffic: allowForwardedTraffic 31 | allowGatewayTransit: allowGatewayTransit 32 | useRemoteGateways: useRemoteGateways 33 | remoteVirtualNetwork: { 34 | id: remoteVirtualNetworkResourceId 35 | } 36 | } 37 | } 38 | 39 | @description('The resource ID of the created peering.') 40 | output peeringResourceId string = vnetPeering.id 41 | 42 | @description('The name of the created peering.') 43 | output peeringName string = vnetPeering.name 44 | -------------------------------------------------------------------------------- /bicep/infra/helpers/deploy-subnets-to-vnet/main.bicep: -------------------------------------------------------------------------------- 1 | // Subnet deployment to existing VNet 2 | targetScope = 'resourceGroup' 3 | 4 | @description('Required. Subnet configuration array.') 5 | param subnets array 6 | 7 | @description('Required. Existing Virtual Network name or Resource ID. When using Resource ID, the component should be deployed to the target resource group scope.') 8 | param existingVNetName string 9 | 10 | // Parse Resource ID to extract VNet name (supports both name and Resource ID formats) 11 | var vnetIdSegments = split(existingVNetName, '/') 12 | var vnetName = length(vnetIdSegments) > 1 ? last(vnetIdSegments) : existingVNetName 13 | 14 | // Reference existing VNet (assumes component is deployed to correct scope) 15 | resource existingVNet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = { 16 | name: vnetName 17 | } 18 | 19 | // Deploy each subnet to the existing VNet 20 | resource deployedSubnets 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = [for (subnet, index) in subnets: { 21 | name: subnet.name 22 | parent: existingVNet 23 | properties: { 24 | addressPrefix: subnet.?addressPrefix 25 | addressPrefixes: subnet.?addressPrefixes 26 | applicationGatewayIPConfigurations: subnet.?applicationGatewayIPConfigurations 27 | defaultOutboundAccess: subnet.?defaultOutboundAccess 28 | delegations: subnet.?delegation != null ? [ 29 | { 30 | name: '${subnet.name}-delegation' 31 | properties: { 32 | serviceName: subnet.delegation 33 | } 34 | } 35 | ] : [] 36 | natGateway: subnet.?natGatewayResourceId != null ? { 37 | id: subnet.natGatewayResourceId 38 | } : null 39 | networkSecurityGroup: subnet.?networkSecurityGroupResourceId != null ? { 40 | id: subnet.networkSecurityGroupResourceId 41 | } : null 42 | privateEndpointNetworkPolicies: subnet.?privateEndpointNetworkPolicies 43 | privateLinkServiceNetworkPolicies: subnet.?privateLinkServiceNetworkPolicies 44 | routeTable: subnet.?routeTableResourceId != null ? { 45 | id: subnet.routeTableResourceId 46 | } : null 47 | serviceEndpointPolicies: subnet.?serviceEndpointPolicies 48 | serviceEndpoints: subnet.?serviceEndpoints 49 | sharingScope: subnet.?sharingScope 50 | } 51 | }] 52 | 53 | @description('Array of deployed subnet resource IDs.') 54 | output subnetResourceIds array = [for (subnet, index) in subnets: deployedSubnets[index].id] 55 | 56 | @description('The resource ID of the parent Virtual Network.') 57 | output virtualNetworkResourceId string = existingVNet.id 58 | 59 | @description('Array of subnet names.') 60 | output subnetNames array = [for (subnet, index) in subnets: deployedSubnets[index].name] 61 | -------------------------------------------------------------------------------- /bicep/infra/helpers/enrich-subnets-with-nsgs/main.bicep: -------------------------------------------------------------------------------- 1 | // Helper module to enrich user subnets with NSG associations 2 | targetScope = 'resourceGroup' 3 | 4 | @description('Required. User-defined subnet array.') 5 | param userSubnets array 6 | 7 | @description('Optional. Agent NSG Resource ID.') 8 | param agentNsgResourceId string = '' 9 | 10 | @description('Optional. Private Endpoints NSG Resource ID.') 11 | param peNsgResourceId string = '' 12 | 13 | @description('Optional. Application Gateway NSG Resource ID.') 14 | param applicationGatewayNsgResourceId string = '' 15 | 16 | @description('Optional. API Management NSG Resource ID.') 17 | param apiManagementNsgResourceId string = '' 18 | 19 | @description('Optional. Jumpbox NSG Resource ID.') 20 | param jumpboxNsgResourceId string = '' 21 | 22 | @description('Optional. ACA Environment NSG Resource ID.') 23 | param acaEnvironmentNsgResourceId string = '' 24 | 25 | @description('Optional. DevOps Build Agents NSG Resource ID.') 26 | param devopsBuildAgentsNsgResourceId string = '' 27 | 28 | @description('Optional. Bastion NSG Resource ID.') 29 | param bastionNsgResourceId string = '' 30 | 31 | // Enrich subnets with NSGs based on naming conventions 32 | var enrichedSubnets = [for subnet in userSubnets: union(subnet, { 33 | networkSecurityGroupResourceId: subnet.name == 'agent-subnet' && !empty(agentNsgResourceId) ? agentNsgResourceId 34 | : subnet.name == 'pe-subnet' && !empty(peNsgResourceId) ? peNsgResourceId 35 | : subnet.name == 'appgw-subnet' && !empty(applicationGatewayNsgResourceId) ? applicationGatewayNsgResourceId 36 | : subnet.name == 'apim-subnet' && !empty(apiManagementNsgResourceId) ? apiManagementNsgResourceId 37 | : subnet.name == 'jumpbox-subnet' && !empty(jumpboxNsgResourceId) ? jumpboxNsgResourceId 38 | : subnet.name == 'aca-env-subnet' && !empty(acaEnvironmentNsgResourceId) ? acaEnvironmentNsgResourceId 39 | : subnet.name == 'devops-agents-subnet' && !empty(devopsBuildAgentsNsgResourceId) ? devopsBuildAgentsNsgResourceId 40 | : subnet.name == 'AzureBastionSubnet' && !empty(bastionNsgResourceId) ? bastionNsgResourceId 41 | : subnet.?networkSecurityGroupResourceId 42 | })] 43 | 44 | @description('Enriched subnets with NSG associations.') 45 | output enrichedSubnets array = enrichedSubnets 46 | -------------------------------------------------------------------------------- /bicep/infra/helpers/setup-subnets-for-vnet/main.bicep: -------------------------------------------------------------------------------- 1 | // Wrapper component that handles subnet selection and deployment to existing VNet 2 | targetScope = 'resourceGroup' 3 | 4 | @description('Required. Configuration for adding subnets to an existing VNet.') 5 | param existingVNetSubnetsDefinition object 6 | 7 | @description('Required. NSG resource IDs for automatic association with subnets.') 8 | param nsgResourceIds object 9 | 10 | // This wrapper handles subnet selection and deployment logic 11 | 12 | // Default subnets for existing VNet scenario (192.168.x.x addressing) 13 | var defaultExistingVnetSubnets = [ 14 | { 15 | name: 'agent-subnet' 16 | addressPrefix: '192.168.0.0/25' 17 | delegation: 'Microsoft.App/environments' 18 | serviceEndpoints: ['Microsoft.CognitiveServices'] 19 | networkSecurityGroupResourceId: !empty(nsgResourceIds.agentNsgResourceId) ? nsgResourceIds.agentNsgResourceId : null 20 | } 21 | { 22 | name: 'pe-subnet' 23 | addressPrefix: '192.168.1.64/27' 24 | serviceEndpoints: ['Microsoft.AzureCosmosDB'] 25 | privateEndpointNetworkPolicies: 'Disabled' 26 | networkSecurityGroupResourceId: !empty(nsgResourceIds.peNsgResourceId) ? nsgResourceIds.peNsgResourceId : null 27 | } 28 | { 29 | name: 'appgw-subnet' 30 | addressPrefix: '192.168.0.128/26' 31 | networkSecurityGroupResourceId: !empty(nsgResourceIds.applicationGatewayNsgResourceId) ? nsgResourceIds.applicationGatewayNsgResourceId : null 32 | } 33 | { 34 | name: 'AzureBastionSubnet' 35 | addressPrefix: '192.168.0.192/26' 36 | } 37 | { 38 | name: 'AzureFirewallSubnet' 39 | addressPrefix: '192.168.1.0/26' 40 | } 41 | { 42 | name: 'apim-subnet' 43 | addressPrefix: '192.168.1.160/27' 44 | networkSecurityGroupResourceId: !empty(nsgResourceIds.apiManagementNsgResourceId) ? nsgResourceIds.apiManagementNsgResourceId : null 45 | } 46 | { 47 | name: 'jumpbox-subnet' 48 | addressPrefix: '192.168.1.96/28' 49 | networkSecurityGroupResourceId: !empty(nsgResourceIds.jumpboxNsgResourceId) ? nsgResourceIds.jumpboxNsgResourceId : null 50 | } 51 | { 52 | name: 'aca-env-subnet' 53 | addressPrefix: '192.168.1.112/28' 54 | delegation: 'Microsoft.App/environments' 55 | serviceEndpoints: ['Microsoft.AzureCosmosDB'] 56 | networkSecurityGroupResourceId: !empty(nsgResourceIds.acaEnvironmentNsgResourceId) ? nsgResourceIds.acaEnvironmentNsgResourceId : null 57 | } 58 | { 59 | name: 'devops-agents-subnet' 60 | addressPrefix: '192.168.1.128/28' 61 | networkSecurityGroupResourceId: !empty(nsgResourceIds.devopsBuildAgentsNsgResourceId) ? nsgResourceIds.devopsBuildAgentsNsgResourceId : null 62 | } 63 | ] 64 | 65 | // Enrich user subnets with NSG associations (when user provides custom subnets) 66 | module enrichSubnetsWithNsgs '../enrich-subnets-with-nsgs/main.bicep' = if (existingVNetSubnetsDefinition.?useDefaultSubnets == false && !empty(existingVNetSubnetsDefinition.?subnets)) { 67 | name: 'm-enrich-subnets' 68 | params: { 69 | userSubnets: existingVNetSubnetsDefinition.subnets! 70 | agentNsgResourceId: nsgResourceIds.agentNsgResourceId 71 | peNsgResourceId: nsgResourceIds.peNsgResourceId 72 | applicationGatewayNsgResourceId: nsgResourceIds.applicationGatewayNsgResourceId 73 | apiManagementNsgResourceId: nsgResourceIds.apiManagementNsgResourceId 74 | jumpboxNsgResourceId: nsgResourceIds.jumpboxNsgResourceId 75 | acaEnvironmentNsgResourceId: nsgResourceIds.acaEnvironmentNsgResourceId 76 | devopsBuildAgentsNsgResourceId: nsgResourceIds.devopsBuildAgentsNsgResourceId 77 | bastionNsgResourceId: nsgResourceIds.bastionNsgResourceId 78 | } 79 | } 80 | 81 | // Determine which subnets to use: custom subnets (enriched with NSGs), defaults, or raw custom 82 | var subnetsForExistingVnet = existingVNetSubnetsDefinition.?useDefaultSubnets != false && empty(existingVNetSubnetsDefinition.?subnets) 83 | ? defaultExistingVnetSubnets 84 | : existingVNetSubnetsDefinition.?useDefaultSubnets == false && !empty(existingVNetSubnetsDefinition.?subnets) 85 | ? enrichSubnetsWithNsgs!.outputs.enrichedSubnets 86 | : existingVNetSubnetsDefinition.subnets! 87 | 88 | // Deploy subnets to existing VNet 89 | module existingVNetSubnetsDeployment '../deploy-subnets-to-vnet/main.bicep' = { 90 | name: 'm-deploy-subnets' 91 | params: { 92 | existingVNetName: existingVNetSubnetsDefinition.existingVNetName 93 | subnets: subnetsForExistingVnet 94 | } 95 | } 96 | 97 | @description('Array of deployed subnet resource IDs.') 98 | output subnetResourceIds array = existingVNetSubnetsDeployment.outputs.subnetResourceIds 99 | 100 | @description('The resource ID of the parent Virtual Network.') 101 | output virtualNetworkResourceId string = existingVNetSubnetsDeployment.outputs.virtualNetworkResourceId 102 | 103 | @description('Array of subnet names.') 104 | output subnetNames array = existingVNetSubnetsDeployment.outputs.subnetNames 105 | -------------------------------------------------------------------------------- /bicep/infra/main.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | @description('Per-service deployment toggles.') 4 | param deployToggles = { 5 | acaEnvironmentNsg: true 6 | agentNsg: true 7 | apiManagement: true 8 | apiManagementNsg: true 9 | appConfig: true 10 | appInsights: true 11 | applicationGateway: true 12 | applicationGatewayNsg: true 13 | applicationGatewayPublicIp: true 14 | bastionHost: true 15 | bastionNsg: true 16 | buildVm: true 17 | containerApps: true 18 | containerEnv: true 19 | containerRegistry: true 20 | cosmosDb: true 21 | devopsBuildAgentsNsg: true 22 | firewall: true 23 | groundingWithBingSearch: true 24 | jumpVm: true 25 | jumpboxNsg: true 26 | keyVault: true 27 | logAnalytics: true 28 | peNsg: true 29 | searchService: true 30 | storageAccount: true 31 | virtualNetwork: true 32 | wafPolicy: true 33 | } 34 | 35 | @description('Existing resource IDs (empty means create new).') 36 | param resourceIds = {} 37 | 38 | @description('Enable platform landing zone integration. When true, private DNS zones and private endpoints are managed by the platform landing zone.') 39 | param flagPlatformLandingZone = false 40 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.ptn.ai-ml.ai-foundry.bicep: -------------------------------------------------------------------------------- 1 | import { aiFoundryDefinitionType } from '../common/types.bicep' 2 | 3 | @description('Required. AI Foundry deployment configuration object. This object contains all the settings for the AI Foundry account, project, and associated resources.') 4 | param aiFoundry aiFoundryDefinitionType 5 | 6 | @description('Optional. Enable telemetry collection for the module.') 7 | param enableTelemetry bool = true 8 | 9 | // Create the AI Foundry deployment using the AVM pattern module 10 | module inner 'br/public:avm/ptn/ai-ml/ai-foundry:0.4.0' = { 11 | name: 'aif-avm-${aiFoundry.baseName!}' 12 | params: { 13 | // Required 14 | baseName: aiFoundry.baseName! 15 | 16 | // Optional (guarded) 17 | baseUniqueName: aiFoundry.?baseUniqueName 18 | enableTelemetry: enableTelemetry 19 | includeAssociatedResources: aiFoundry.?includeAssociatedResources 20 | location: aiFoundry.?location 21 | lock: aiFoundry.?lock 22 | tags: aiFoundry.?tags 23 | privateEndpointSubnetResourceId: aiFoundry.?privateEndpointSubnetResourceId 24 | aiFoundryConfiguration: aiFoundry.?aiFoundryConfiguration 25 | aiModelDeployments: aiFoundry.?aiModelDeployments 26 | aiSearchConfiguration: aiFoundry.?aiSearchConfiguration 27 | cosmosDbConfiguration: aiFoundry.?cosmosDbConfiguration 28 | keyVaultConfiguration: aiFoundry.?keyVaultConfiguration 29 | storageAccountConfiguration: aiFoundry.?storageAccountConfiguration 30 | } 31 | } 32 | 33 | // Outputs 34 | @description('AI Foundry resource group name.') 35 | output resourceGroupName string = inner.outputs.resourceGroupName 36 | 37 | @description('AI Foundry project name.') 38 | output aiProjectName string = inner.outputs.aiProjectName 39 | 40 | @description('AI Foundry AI Search service name.') 41 | output aiSearchName string = inner.outputs.aiSearchName 42 | 43 | @description('AI Foundry AI Services name.') 44 | output aiServicesName string = inner.outputs.aiServicesName 45 | 46 | @description('AI Foundry Cosmos DB account name.') 47 | output cosmosAccountName string = inner.outputs.cosmosAccountName 48 | 49 | @description('AI Foundry Key Vault name.') 50 | output keyVaultName string = inner.outputs.keyVaultName 51 | 52 | @description('AI Foundry Storage Account name.') 53 | output storageAccountName string = inner.outputs.storageAccountName 54 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.api-management.service.bicep: -------------------------------------------------------------------------------- 1 | // AVM API Management wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { apimDefinitionType } from '../common/types.bicep' 5 | 6 | // ============================================================================ 7 | // Parameters 8 | // ============================================================================ 9 | 10 | @description('Required. API Management service configuration.') 11 | param apiManagement apimDefinitionType 12 | 13 | // ============================================================================ 14 | // Resources 15 | // ============================================================================ 16 | 17 | #disable-next-line BCP081 18 | module apiManagementService 'br/public:avm/res/api-management/service:0.11.1' = { 19 | name: 'apim-service-${apiManagement.name!}' 20 | params: { 21 | name: apiManagement.name 22 | publisherEmail: apiManagement.publisherEmail 23 | publisherName: apiManagement.publisherName 24 | sku: apiManagement.?sku 25 | skuCapacity: apiManagement.?skuCapacity 26 | location: apiManagement.?location 27 | tags: apiManagement.?tags 28 | enableTelemetry: apiManagement.?enableTelemetry 29 | diagnosticSettings: apiManagement.?diagnosticSettings 30 | disableGateway: apiManagement.?disableGateway 31 | enableClientCertificate: apiManagement.?enableClientCertificate 32 | enableDeveloperPortal: apiManagement.?enableDeveloperPortal 33 | hostnameConfigurations: apiManagement.?hostnameConfigurations 34 | identityProviders: apiManagement.?identityProviders 35 | portalsettings: apiManagement.?portalsettings 36 | lock: apiManagement.?lock 37 | loggers: apiManagement.?loggers 38 | managedIdentities: apiManagement.?managedIdentities 39 | minApiVersion: apiManagement.?minApiVersion 40 | namedValues: apiManagement.?namedValues 41 | newGuidValue: apiManagement.?newGuidValue 42 | notificationSenderEmail: apiManagement.?notificationSenderEmail 43 | policies: apiManagement.?policies 44 | products: apiManagement.?products 45 | publicIpAddressResourceId: apiManagement.?publicIpAddressResourceId 46 | restore: apiManagement.?restore 47 | roleAssignments: apiManagement.?roleAssignments 48 | subnetResourceId: apiManagement.?subnetResourceId 49 | subscriptions: apiManagement.?subscriptions 50 | virtualNetworkType: apiManagement.?virtualNetworkType 51 | } 52 | } 53 | 54 | // ============================================================================ 55 | // Outputs 56 | // ============================================================================ 57 | 58 | @description('The resource ID of the API Management service.') 59 | output resourceId string = apiManagementService.outputs.resourceId 60 | 61 | @description('The resource group the API Management service was deployed into.') 62 | output resourceGroupName string = apiManagementService.outputs.resourceGroupName 63 | 64 | @description('The name of the API Management service.') 65 | output name string = apiManagementService.outputs.name 66 | 67 | @description('The principal ID of the system assigned identity.') 68 | output systemAssignedMIPrincipalId string = apiManagementService.outputs.?systemAssignedMIPrincipalId ?? '' 69 | 70 | @description('The location the resource was deployed into.') 71 | output location string = apiManagementService.outputs.location 72 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.app-configuration.configuration-store.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'resourceGroup' 2 | 3 | import { appConfigurationDefinitionType } from '../common/types.bicep' 4 | 5 | @description('App Configuration definition parameter') 6 | param appConfiguration appConfigurationDefinitionType 7 | 8 | module configurationStore 'br/public:avm/res/app-configuration/configuration-store:0.9.2' = { 9 | name: 'appcs-avm-${appConfiguration.name!}' 10 | params: { 11 | name: appConfiguration.name! 12 | location: appConfiguration.?location 13 | enableTelemetry: appConfiguration.?enableTelemetry 14 | tags: appConfiguration.?tags 15 | 16 | // Optional configuration settings 17 | createMode: appConfiguration.?createMode 18 | customerManagedKey: appConfiguration.?customerManagedKey 19 | dataPlaneProxy: appConfiguration.?dataPlaneProxy 20 | diagnosticSettings: appConfiguration.?diagnosticSettings 21 | disableLocalAuth: appConfiguration.?disableLocalAuth 22 | enablePurgeProtection: appConfiguration.?enablePurgeProtection 23 | keyValues: appConfiguration.?keyValues 24 | lock: appConfiguration.?lock 25 | managedIdentities: appConfiguration.?managedIdentities 26 | privateEndpoints: appConfiguration.?privateEndpoints 27 | publicNetworkAccess: appConfiguration.?publicNetworkAccess 28 | replicaLocations: appConfiguration.?replicaLocations 29 | roleAssignments: appConfiguration.?roleAssignments 30 | sku: appConfiguration.?sku 31 | softDeleteRetentionInDays: appConfiguration.?softDeleteRetentionInDays 32 | } 33 | } 34 | 35 | @description('The resource ID of the configuration store.') 36 | output resourceId string = configurationStore.outputs.resourceId 37 | 38 | @description('The name of the configuration store.') 39 | output name string = configurationStore.outputs.name 40 | 41 | @description('The location the resource was deployed into.') 42 | output location string = configurationStore.outputs.location 43 | 44 | @description('The resource group the configuration store was deployed into.') 45 | output resourceGroupName string = configurationStore.outputs.resourceGroupName 46 | 47 | @description('The endpoint of the configuration store.') 48 | output endpoint string = configurationStore.outputs.endpoint 49 | 50 | @description('The system-assigned managed identity principal ID.') 51 | output systemAssignedMIPrincipalId string = configurationStore.outputs.?systemAssignedMIPrincipalId! ?? '' 52 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.app.container-app.bicep: -------------------------------------------------------------------------------- 1 | // AVM Container App wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { containerAppDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Container App configuration.') 7 | param containerApp containerAppDefinitionType 8 | 9 | module inner 'br/public:avm/res/app/container-app:0.18.1' = { 10 | name: 'ca-avm-${containerApp.name}' 11 | params: { 12 | // Required parameters 13 | name: containerApp.name 14 | environmentResourceId: containerApp.environmentResourceId 15 | 16 | // Provide default container configuration 17 | containers: [ 18 | { 19 | name: 'default' 20 | image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' 21 | resources: { 22 | cpu: '0.5' 23 | memory: '1.0Gi' 24 | } 25 | } 26 | ] 27 | 28 | // Optional parameters that pass through safely 29 | location: containerApp.?location ?? resourceGroup().location 30 | tags: containerApp.?tags 31 | workloadProfileName: containerApp.?workloadProfileName 32 | activeRevisionsMode: containerApp.?activeRevisionsMode 33 | clientCertificateMode: containerApp.?clientCertificateMode 34 | lock: containerApp.?lock 35 | managedIdentities: containerApp.?managedIdentities 36 | revisionSuffix: containerApp.?revisionSuffix 37 | roleAssignments: containerApp.?roleAssignments 38 | stickySessionsAffinity: containerApp.?stickySessionsAffinity 39 | trafficLabel: containerApp.?trafficLabel 40 | trafficLatestRevision: containerApp.?trafficLatestRevision 41 | trafficRevisionName: containerApp.?trafficRevisionName 42 | trafficWeight: containerApp.?trafficWeight 43 | } 44 | } 45 | 46 | output resourceId string = inner.outputs.resourceId 47 | output name string = inner.outputs.name 48 | output location string = inner.outputs.location 49 | output resourceGroupName string = inner.outputs.resourceGroupName 50 | output fqdn string = inner.outputs.fqdn 51 | output systemAssignedMIPrincipalId string? = inner.outputs.?systemAssignedMIPrincipalId 52 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.app.managed-environment.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'resourceGroup' 2 | 3 | import { containerAppEnvDefinitionType } from '../common/types.bicep' 4 | 5 | @description('Container App Environment definition parameter') 6 | param containerAppEnv containerAppEnvDefinitionType 7 | 8 | module managedEnvironment 'br/public:avm/res/app/managed-environment:0.11.3' = { 9 | name: 'cae-avm-${containerAppEnv.name!}' 10 | params: { 11 | name: containerAppEnv.name! 12 | location: containerAppEnv.?location 13 | enableTelemetry: containerAppEnv.?enableTelemetry 14 | tags: containerAppEnv.?tags 15 | 16 | // Optional infrastructure settings 17 | dockerBridgeCidr: containerAppEnv.?dockerBridgeCidr 18 | infrastructureResourceGroupName: containerAppEnv.?infrastructureResourceGroupName 19 | infrastructureSubnetResourceId: containerAppEnv.?infrastructureSubnetResourceId 20 | internal: containerAppEnv.?internal 21 | platformReservedCidr: containerAppEnv.?platformReservedCidr 22 | platformReservedDnsIP: containerAppEnv.?platformReservedDnsIP 23 | 24 | // Workload profiles 25 | workloadProfiles: containerAppEnv.?workloadProfiles 26 | 27 | // Observability settings 28 | appInsightsConnectionString: containerAppEnv.?appInsightsConnectionString 29 | appLogsConfiguration: containerAppEnv.?appLogsConfiguration 30 | 31 | // Certificate settings 32 | certificate: containerAppEnv.?certificate 33 | certificatePassword: containerAppEnv.?certificatePassword 34 | certificateValue: containerAppEnv.?certificateValue 35 | 36 | // Dapr settings 37 | daprAIConnectionString: containerAppEnv.?daprAIConnectionString 38 | daprAIInstrumentationKey: containerAppEnv.?daprAIInstrumentationKey 39 | 40 | // Other optional settings 41 | dnsSuffix: containerAppEnv.?dnsSuffix 42 | lock: containerAppEnv.?lock 43 | managedIdentities: containerAppEnv.?managedIdentities 44 | openTelemetryConfiguration: containerAppEnv.?openTelemetryConfiguration 45 | peerTrafficEncryption: containerAppEnv.?peerTrafficEncryption 46 | publicNetworkAccess: containerAppEnv.?publicNetworkAccess 47 | roleAssignments: containerAppEnv.?roleAssignments 48 | storages: containerAppEnv.?storages 49 | zoneRedundant: containerAppEnv.?zoneRedundant 50 | } 51 | } 52 | 53 | @description('The resource ID of the container apps managed environment.') 54 | output resourceId string = managedEnvironment.outputs.resourceId 55 | 56 | @description('The name of the container apps managed environment.') 57 | output name string = managedEnvironment.outputs.name 58 | 59 | @description('The location the resource was deployed into.') 60 | output location string = managedEnvironment.outputs.location 61 | 62 | @description('The resource group the container apps managed environment was deployed into.') 63 | output resourceGroupName string = managedEnvironment.outputs.resourceGroupName 64 | 65 | @description('The default domain of the container apps managed environment.') 66 | output defaultDomain string = managedEnvironment.outputs.defaultDomain 67 | 68 | @description('The static IP of the container apps managed environment.') 69 | output staticIp string = managedEnvironment.outputs.staticIp 70 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.compute.build-vm.bicep: -------------------------------------------------------------------------------- 1 | // AVM Virtual Machine Build VM wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { vmDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Build VM configuration.') 7 | param buildVm vmDefinitionType 8 | 9 | module inner 'br/public:avm/res/compute/virtual-machine:0.20.0' = { 10 | name: 'buildvm-avm-${buildVm.name!}' 11 | params: { 12 | // Required parameters 13 | name: buildVm.name! 14 | adminUsername: buildVm.adminUsername! 15 | vmSize: buildVm.sku! 16 | imageReference: buildVm.imageReference! 17 | osType: buildVm.osType! 18 | 19 | // Optional 20 | location: buildVm.?location 21 | tags: buildVm.?tags 22 | enableTelemetry: buildVm.?enableTelemetry 23 | nicConfigurations: buildVm.nicConfigurations! 24 | osDisk: buildVm.osDisk! 25 | disablePasswordAuthentication: buildVm.?disablePasswordAuthentication ?? false 26 | availabilityZone: buildVm.?availabilityZone ?? -1 27 | lock: buildVm.?lock 28 | managedIdentities: buildVm.?managedIdentities 29 | roleAssignments: buildVm.?roleAssignments 30 | adminPassword: buildVm.?adminPassword 31 | publicKeys: buildVm.?publicKeys 32 | } 33 | } 34 | 35 | output resourceId string = inner.outputs.resourceId 36 | output name string = inner.outputs.name 37 | output location string = inner.outputs.location 38 | output resourceGroupName string = inner.outputs.resourceGroupName 39 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.compute.jump-vm.bicep: -------------------------------------------------------------------------------- 1 | // AVM Virtual Machine Jump VM wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { vmDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Jump VM configuration.') 7 | param jumpVm vmDefinitionType 8 | 9 | module inner 'br/public:avm/res/compute/virtual-machine:0.20.0' = { 10 | name: 'jumpvm-avm-${jumpVm.name!}' 11 | params: { 12 | // Required parameters 13 | name: jumpVm.name! 14 | adminUsername: jumpVm.adminUsername! 15 | vmSize: jumpVm.sku! 16 | imageReference: jumpVm.imageReference! 17 | osType: jumpVm.osType! 18 | 19 | // Optional 20 | nicConfigurations: jumpVm.nicConfigurations! 21 | osDisk: jumpVm.osDisk! 22 | location: jumpVm.?location 23 | tags: jumpVm.?tags 24 | enableTelemetry: jumpVm.?enableTelemetry 25 | adminPassword: jumpVm.?adminPassword 26 | availabilityZone: jumpVm.?availabilityZone ?? -1 27 | lock: jumpVm.?lock 28 | managedIdentities: jumpVm.?managedIdentities 29 | roleAssignments: jumpVm.?roleAssignments 30 | maintenanceConfigurationResourceId: jumpVm.?maintenanceConfigurationResourceId 31 | patchMode: jumpVm.?patchMode 32 | enableAutomaticUpdates: jumpVm.?enableAutomaticUpdates 33 | publicKeys: jumpVm.?publicKeys 34 | } 35 | } 36 | 37 | output resourceId string = inner.outputs.resourceId 38 | output name string = inner.outputs.name 39 | output location string = inner.outputs.location 40 | output resourceGroupName string = inner.outputs.resourceGroupName 41 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.compute.virtual-machine.bicep: -------------------------------------------------------------------------------- 1 | // AVM Virtual Machine wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { vmDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Virtual Machine configuration.') 7 | param virtualMachine vmDefinitionType 8 | 9 | module inner 'br/public:avm/res/compute/virtual-machine:0.20.0' = { 10 | name: 'vm-avm-${virtualMachine.name!}' 11 | params: { 12 | name: virtualMachine.name! 13 | adminUsername: virtualMachine.adminUsername! 14 | vmSize: virtualMachine.sku! 15 | imageReference: virtualMachine.imageReference! 16 | osType: virtualMachine.osType! 17 | location: virtualMachine.?location ?? resourceGroup().location 18 | tags: virtualMachine.?tags 19 | enableTelemetry: virtualMachine.?enableTelemetry ?? true 20 | nicConfigurations: virtualMachine.?nicConfigurations ?? [] 21 | osDisk: virtualMachine.?osDisk ?? { 22 | caching: 'ReadWrite' 23 | createOption: 'FromImage' 24 | deleteOption: 'Delete' 25 | managedDisk: { 26 | storageAccountType: 'Premium_LRS' 27 | } 28 | } 29 | availabilityZone: virtualMachine.?availabilityZone ?? -1 30 | adminPassword: virtualMachine.?adminPassword 31 | lock: virtualMachine.?lock 32 | managedIdentities: virtualMachine.?managedIdentities 33 | roleAssignments: virtualMachine.?roleAssignments 34 | publicKeys: virtualMachine.?publicKeys 35 | disablePasswordAuthentication: virtualMachine.?disablePasswordAuthentication 36 | maintenanceConfigurationResourceId: virtualMachine.?maintenanceConfigurationResourceId 37 | patchMode: virtualMachine.?patchMode 38 | enableAutomaticUpdates: virtualMachine.?enableAutomaticUpdates 39 | } 40 | } 41 | 42 | output resourceId string = inner.outputs.resourceId 43 | output name string = inner.outputs.name 44 | output location string = inner.outputs.location 45 | output resourceGroupName string = inner.outputs.resourceGroupName 46 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.container-registry.registry.bicep: -------------------------------------------------------------------------------- 1 | // AVM Container Registry wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { containerRegistryDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Container Registry definition.') 7 | param acr containerRegistryDefinitionType 8 | 9 | module inner 'br/public:avm/res/container-registry/registry:0.9.3' = { 10 | name: 'acr-avm-${acr.name}' 11 | params: { 12 | name: acr.name 13 | location: acr.?location 14 | roleAssignments: acr.?roleAssignments 15 | cacheRules: acr.?cacheRules 16 | credentialSets: acr.?credentialSets 17 | customerManagedKey: acr.?customerManagedKey 18 | diagnosticSettings: acr.?diagnosticSettings 19 | lock: acr.?lock 20 | managedIdentities: acr.?managedIdentities 21 | networkRuleSetIpRules: acr.?networkRuleSetIpRules 22 | privateEndpoints: acr.?privateEndpoints 23 | publicNetworkAccess: acr.?publicNetworkAccess 24 | replications: acr.?replications 25 | scopeMaps: acr.?scopeMaps 26 | tags: acr.?tags 27 | webhooks: acr.?webhooks 28 | enableTelemetry: acr.?enableTelemetry 29 | } 30 | } 31 | 32 | @description('The resource ID of the container registry.') 33 | output resourceId string = inner.outputs.resourceId 34 | 35 | @description('The name of the container registry.') 36 | output name string = inner.outputs.name 37 | 38 | @description('The resource group the container registry was deployed into.') 39 | output resourceGroupName string = inner.outputs.resourceGroupName 40 | 41 | @description('The location the resource was deployed into.') 42 | output location string = inner.outputs.location 43 | 44 | @description('The principal ID of the system assigned identity.') 45 | output systemAssignedMIPrincipalId string = inner.outputs.?systemAssignedMIPrincipalId ?? '' 46 | 47 | @description('The login server for the container registry.') 48 | output loginServer string = inner.outputs.loginServer 49 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.document-db.database-account.bicep: -------------------------------------------------------------------------------- 1 | // AVM Cosmos DB wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { genAIAppCosmosDbDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Cosmos DB definition.') 7 | param cosmosDb genAIAppCosmosDbDefinitionType 8 | 9 | module inner 'br/public:avm/res/document-db/database-account:0.16.0' = { 10 | name: 'cosmos-avm-${cosmosDb.name}' 11 | params: { 12 | name: cosmosDb.name 13 | location: cosmosDb.?location 14 | automaticFailover: cosmosDb.?automaticFailover 15 | backupIntervalInMinutes: cosmosDb.?backupIntervalInMinutes 16 | backupPolicyContinuousTier: cosmosDb.?backupPolicyContinuousTier 17 | backupPolicyType: cosmosDb.?backupPolicyType 18 | backupRetentionIntervalInHours: cosmosDb.?backupRetentionIntervalInHours 19 | backupStorageRedundancy: cosmosDb.?backupStorageRedundancy 20 | capabilitiesToAdd: cosmosDb.?capabilitiesToAdd 21 | databaseAccountOfferType: cosmosDb.?databaseAccountOfferType 22 | dataPlaneRoleAssignments: cosmosDb.?dataPlaneRoleAssignments 23 | dataPlaneRoleDefinitions: cosmosDb.?dataPlaneRoleDefinitions 24 | defaultConsistencyLevel: cosmosDb.?defaultConsistencyLevel 25 | diagnosticSettings: cosmosDb.?diagnosticSettings 26 | disableKeyBasedMetadataWriteAccess: cosmosDb.?disableKeyBasedMetadataWriteAccess 27 | disableLocalAuthentication: cosmosDb.?disableLocalAuthentication 28 | enableAnalyticalStorage: cosmosDb.?enableAnalyticalStorage 29 | enableFreeTier: cosmosDb.?enableFreeTier 30 | enableMultipleWriteLocations: cosmosDb.?enableMultipleWriteLocations 31 | enableTelemetry: cosmosDb.?enableTelemetry 32 | failoverLocations: cosmosDb.?failoverLocations 33 | gremlinDatabases: cosmosDb.?gremlinDatabases 34 | lock: cosmosDb.?lock 35 | managedIdentities: cosmosDb.?managedIdentities 36 | maxIntervalInSeconds: cosmosDb.?maxIntervalInSeconds 37 | maxStalenessPrefix: cosmosDb.?maxStalenessPrefix 38 | minimumTlsVersion: cosmosDb.?minimumTlsVersion 39 | mongodbDatabases: cosmosDb.?mongodbDatabases 40 | privateEndpoints: cosmosDb.?privateEndpoints 41 | roleAssignments: cosmosDb.?roleAssignments 42 | serverVersion: cosmosDb.?serverVersion 43 | sqlDatabases: cosmosDb.?sqlDatabases 44 | tables: cosmosDb.?tables 45 | tags: cosmosDb.?tags 46 | totalThroughputLimit: cosmosDb.?totalThroughputLimit 47 | zoneRedundant: cosmosDb.?zoneRedundant 48 | } 49 | } 50 | 51 | output resourceId string = inner.outputs.resourceId 52 | output name string = inner.outputs.name 53 | output location string = inner.outputs.location 54 | output resourceGroupName string = inner.outputs.resourceGroupName 55 | output systemAssignedMIPrincipalId string = inner.outputs.?systemAssignedMIPrincipalId ?? '' 56 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.insights.component.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'resourceGroup' 2 | 3 | import { appInsightsDefinitionType } from '../common/types.bicep' 4 | 5 | @description('Application Insights configuration.') 6 | param appInsights appInsightsDefinitionType 7 | 8 | module appInsightsComponent 'br/public:avm/res/insights/component:0.6.0' = { 9 | name: 'appi-avm-${appInsights.name!}' 10 | params: { 11 | name: appInsights.name! 12 | workspaceResourceId: appInsights.workspaceResourceId! 13 | location: appInsights.?location 14 | tags: appInsights.?tags 15 | enableTelemetry: appInsights.?enableTelemetry 16 | applicationType: appInsights.?applicationType 17 | diagnosticSettings: appInsights.?diagnosticSettings 18 | disableIpMasking: appInsights.?disableIpMasking 19 | disableLocalAuth: appInsights.?disableLocalAuth 20 | flowType: appInsights.?flowType 21 | forceCustomerStorageForProfiler: appInsights.?forceCustomerStorageForProfiler 22 | kind: appInsights.?kind 23 | linkedStorageAccountResourceId: appInsights.?linkedStorageAccountResourceId 24 | lock: appInsights.?lock 25 | publicNetworkAccessForIngestion: appInsights.?publicNetworkAccessForIngestion 26 | publicNetworkAccessForQuery: appInsights.?publicNetworkAccessForQuery 27 | requestSource: appInsights.?requestSource 28 | retentionInDays: appInsights.?retentionInDays 29 | roleAssignments: appInsights.?roleAssignments 30 | samplingPercentage: appInsights.?samplingPercentage 31 | } 32 | } 33 | 34 | @description('The resource ID of the Application Insights component.') 35 | output resourceId string = appInsightsComponent.outputs.resourceId 36 | 37 | @description('The name of the Application Insights component.') 38 | output name string = appInsightsComponent.outputs.name 39 | 40 | @description('The resource group the Application Insights component was deployed into.') 41 | output resourceGroupName string = appInsightsComponent.outputs.resourceGroupName 42 | 43 | @description('The location the Application Insights component was deployed into.') 44 | output location string = appInsightsComponent.outputs.location 45 | 46 | @description('The connection string of the Application Insights component.') 47 | output connectionString string = appInsightsComponent.outputs.connectionString 48 | 49 | @description('The instrumentation key of the Application Insights component.') 50 | output instrumentationKey string = appInsightsComponent.outputs.instrumentationKey 51 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.key-vault.vault.bicep: -------------------------------------------------------------------------------- 1 | // AVM Key Vault wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { keyVaultDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Key Vault definition.') 7 | param keyVault keyVaultDefinitionType 8 | 9 | module inner 'br/public:avm/res/key-vault/vault:0.13.3' = { 10 | name: 'kv-avm-${keyVault.name}' 11 | params: { 12 | name: keyVault.name 13 | location: keyVault.?location 14 | accessPolicies: keyVault.?accessPolicies 15 | diagnosticSettings: keyVault.?diagnosticSettings 16 | enablePurgeProtection: keyVault.?enablePurgeProtection 17 | enableRbacAuthorization: keyVault.?enableRbacAuthorization 18 | enableSoftDelete: keyVault.?enableSoftDelete 19 | enableVaultForDeployment: keyVault.?enableVaultForDeployment 20 | enableVaultForDiskEncryption: keyVault.?enableVaultForDiskEncryption 21 | enableVaultForTemplateDeployment: keyVault.?enableVaultForTemplateDeployment 22 | keys: keyVault.?keys 23 | lock: keyVault.?lock 24 | networkAcls: keyVault.?networkAcls 25 | privateEndpoints: keyVault.?privateEndpoints 26 | publicNetworkAccess: keyVault.?publicNetworkAccess 27 | roleAssignments: keyVault.?roleAssignments 28 | secrets: keyVault.?secrets 29 | sku: keyVault.?sku 30 | softDeleteRetentionInDays: keyVault.?softDeleteRetentionInDays 31 | tags: keyVault.?tags 32 | enableTelemetry: keyVault.?enableTelemetry 33 | } 34 | } 35 | 36 | output resourceId string = inner.outputs.resourceId 37 | output name string = inner.outputs.name 38 | output location string = inner.outputs.location 39 | output resourceGroupName string = inner.outputs.resourceGroupName 40 | output uri string = inner.outputs.uri 41 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.maintenance.maintenance-configuration.bicep: -------------------------------------------------------------------------------- 1 | // Wrapper for Maintenance Configuration 2 | // Purpose: Wrap the AVM Maintenance Configuration module to provide a consistent interface 3 | // Version: Wraps avm/res/maintenance/maintenance-configuration:0.3.1 4 | 5 | import { vmMaintenanceDefinitionType } from '../common/types.bicep' 6 | 7 | @description('Maintenance Configuration.') 8 | param maintenanceConfig vmMaintenanceDefinitionType 9 | 10 | // Inner module reference to AVM 11 | module inner 'br/public:avm/res/maintenance/maintenance-configuration:0.3.1' = { 12 | name: 'maint-avm-${maintenanceConfig.name!}' 13 | params: { 14 | // Required 15 | name: maintenanceConfig.name! 16 | 17 | // Pass through optional properties 18 | location: maintenanceConfig.?location 19 | tags: maintenanceConfig.?tags 20 | } 21 | } 22 | 23 | // Outputs 24 | @description('The resource ID of the Maintenance Configuration.') 25 | output resourceId string = inner.outputs.resourceId 26 | 27 | @description('The resource name of the Maintenance Configuration.') 28 | output name string = inner.outputs.name 29 | 30 | @description('The resource group the Maintenance Configuration was deployed into.') 31 | output resourceGroupName string = inner.outputs.resourceGroupName 32 | 33 | @description('The location the resource was deployed into.') 34 | output location string = inner.outputs.location 35 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.application-gateway.bicep: -------------------------------------------------------------------------------- 1 | import { appGatewayDefinitionType } from '../common/types.bicep' 2 | 3 | @description('Required. Application Gateway configuration object.') 4 | param applicationGateway appGatewayDefinitionType 5 | 6 | @description('Optional. Enable telemetry collection for the module.') 7 | param enableTelemetry bool = true 8 | 9 | // Create the Application Gateway using the AVM resource module 10 | module inner 'br/public:avm/res/network/application-gateway:0.7.2' = { 11 | name: 'agw-avm-${applicationGateway.name!}' 12 | params: { 13 | // Required 14 | name: applicationGateway.name! 15 | 16 | // Optional pass-throughs with defaults 17 | sku: applicationGateway.?sku ?? 'WAF_v2' 18 | firewallPolicyResourceId: applicationGateway.?firewallPolicyResourceId 19 | gatewayIPConfigurations: applicationGateway.?gatewayIPConfigurations 20 | frontendIPConfigurations: applicationGateway.?frontendIPConfigurations 21 | frontendPorts: applicationGateway.?frontendPorts 22 | backendAddressPools: applicationGateway.?backendAddressPools 23 | backendHttpSettingsCollection: applicationGateway.?backendHttpSettingsCollection 24 | httpListeners: applicationGateway.?httpListeners 25 | requestRoutingRules: applicationGateway.?requestRoutingRules 26 | probes: applicationGateway.?probes 27 | redirectConfigurations: applicationGateway.?redirectConfigurations 28 | rewriteRuleSets: applicationGateway.?rewriteRuleSets 29 | sslCertificates: applicationGateway.?sslCertificates 30 | trustedRootCertificates: applicationGateway.?trustedRootCertificates 31 | enableHttp2: applicationGateway.?enableHttp2 32 | customErrorConfigurations: applicationGateway.?customErrorConfigurations 33 | capacity: applicationGateway.?capacity 34 | autoscaleMinCapacity: applicationGateway.?autoscaleMinCapacity 35 | autoscaleMaxCapacity: applicationGateway.?autoscaleMaxCapacity 36 | enableTelemetry: enableTelemetry 37 | location: applicationGateway.?location 38 | tags: applicationGateway.?tags 39 | lock: applicationGateway.?lock 40 | managedIdentities: applicationGateway.?managedIdentities 41 | roleAssignments: applicationGateway.?roleAssignments 42 | diagnosticSettings: applicationGateway.?diagnosticSettings 43 | } 44 | } 45 | 46 | // Outputs 47 | @description('Application Gateway resource ID.') 48 | output resourceId string = inner.outputs.resourceId 49 | 50 | @description('Application Gateway name.') 51 | output name string = inner.outputs.name 52 | 53 | @description('Application Gateway resource group name.') 54 | output resourceGroupName string = inner.outputs.resourceGroupName 55 | 56 | @description('Application Gateway location.') 57 | output location string = inner.outputs.location 58 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.azure-firewall.bicep: -------------------------------------------------------------------------------- 1 | import { firewallDefinitionType } from '../common/types.bicep' 2 | 3 | @description('Required. Azure Firewall configuration object.') 4 | param firewall firewallDefinitionType 5 | 6 | @description('Optional. Enable telemetry collection for the module.') 7 | param enableTelemetry bool = true 8 | 9 | // Create the Azure Firewall using the AVM resource module 10 | module inner 'br/public:avm/res/network/azure-firewall:0.8.0' = { 11 | name: 'afw-avm-${firewall.name!}' 12 | params: { 13 | // Required 14 | name: firewall.name! 15 | 16 | // Optional pass-throughs 17 | hubIPAddresses: firewall.?hubIPAddresses 18 | virtualHubResourceId: firewall.?virtualHubResourceId 19 | virtualNetworkResourceId: firewall.?virtualNetworkResourceId 20 | additionalPublicIpConfigurations: firewall.?additionalPublicIpConfigurations 21 | applicationRuleCollections: firewall.?applicationRuleCollections 22 | autoscaleMaxCapacity: firewall.?autoscaleMaxCapacity 23 | autoscaleMinCapacity: firewall.?autoscaleMinCapacity 24 | availabilityZones: firewall.?availabilityZones 25 | azureSkuTier: firewall.?azureSkuTier 26 | diagnosticSettings: firewall.?diagnosticSettings 27 | enableForcedTunneling: firewall.?enableForcedTunneling 28 | enableTelemetry: enableTelemetry 29 | firewallPolicyId: firewall.?firewallPolicyId 30 | location: firewall.?location 31 | lock: firewall.?lock 32 | managementIPAddressObject: firewall.?managementIPAddressObject 33 | managementIPResourceID: firewall.?managementIPResourceID 34 | natRuleCollections: firewall.?natRuleCollections 35 | networkRuleCollections: firewall.?networkRuleCollections 36 | publicIPAddressObject: firewall.?publicIPAddressObject 37 | publicIPResourceID: firewall.?publicIPResourceID 38 | roleAssignments: firewall.?roleAssignments 39 | tags: firewall.?tags 40 | threatIntelMode: firewall.?threatIntelMode 41 | } 42 | } 43 | 44 | // Outputs 45 | @description('Azure Firewall resource ID.') 46 | output resourceId string = inner.outputs.resourceId 47 | 48 | @description('Azure Firewall name.') 49 | output name string = inner.outputs.name 50 | 51 | @description('Azure Firewall resource group name.') 52 | output resourceGroupName string = inner.outputs.resourceGroupName 53 | 54 | @description('Azure Firewall location.') 55 | output location string = inner.outputs.location 56 | 57 | @description('Azure Firewall private IP address.') 58 | output privateIp string = inner.outputs.privateIp 59 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.firewall-policy.bicep: -------------------------------------------------------------------------------- 1 | import { firewallPolicyDefinitionType } from '../common/types.bicep' 2 | 3 | @description('Required. Azure Firewall Policy configuration object.') 4 | param firewallPolicy firewallPolicyDefinitionType 5 | 6 | @description('Optional. Enable telemetry collection for the module.') 7 | param enableTelemetry bool = true 8 | 9 | // Create the Firewall Policy using the AVM resource module 10 | module inner 'br/public:avm/res/network/firewall-policy:0.3.1' = { 11 | name: 'fwp-avm-${firewallPolicy.name!}' 12 | params: { 13 | // Required 14 | name: firewallPolicy.name! 15 | 16 | // Optional pass-throughs 17 | allowSqlRedirect: firewallPolicy.?allowSqlRedirect 18 | basePolicyResourceId: firewallPolicy.?basePolicyResourceId 19 | certificateName: firewallPolicy.?certificateName 20 | defaultWorkspaceResourceId: firewallPolicy.?defaultWorkspaceResourceId 21 | enableProxy: firewallPolicy.?enableProxy 22 | enableTelemetry: enableTelemetry 23 | fqdns: firewallPolicy.?fqdns 24 | insightsIsEnabled: firewallPolicy.?insightsIsEnabled 25 | intrusionDetection: firewallPolicy.?intrusionDetection 26 | ipAddresses: firewallPolicy.?ipAddresses 27 | keyVaultSecretId: firewallPolicy.?keyVaultSecretId 28 | location: firewallPolicy.?location 29 | lock: firewallPolicy.?lock 30 | managedIdentities: firewallPolicy.?managedIdentities 31 | retentionDays: firewallPolicy.?retentionDays 32 | roleAssignments: firewallPolicy.?roleAssignments 33 | ruleCollectionGroups: firewallPolicy.?ruleCollectionGroups 34 | servers: firewallPolicy.?servers 35 | snat: firewallPolicy.?snat 36 | tags: firewallPolicy.?tags 37 | threatIntelMode: firewallPolicy.?threatIntelMode 38 | tier: firewallPolicy.?tier 39 | workspaces: firewallPolicy.?workspaces 40 | } 41 | } 42 | 43 | // Outputs 44 | @description('Firewall Policy resource ID.') 45 | output resourceId string = inner.outputs.resourceId 46 | 47 | @description('Firewall Policy name.') 48 | output name string = inner.outputs.name 49 | 50 | @description('Firewall Policy resource group name.') 51 | output resourceGroupName string = inner.outputs.resourceGroupName 52 | 53 | @description('Firewall Policy location.') 54 | output location string = inner.outputs.location 55 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.network-security-group.bicep: -------------------------------------------------------------------------------- 1 | // AVM Network Security Group wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { nsgDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Network Security Group definition.') 7 | param nsg nsgDefinitionType 8 | 9 | module inner 'br/public:avm/res/network/network-security-group:0.5.1' = { 10 | name: 'nsg-${uniqueString(nsg.name!)}' 11 | params: { 12 | name: nsg.name! 13 | location: nsg.?location 14 | flushConnection: nsg.?flushConnection 15 | securityRules: nsg.?securityRules 16 | tags: nsg.?tags 17 | lock: nsg.?lock 18 | enableTelemetry: nsg.?enableTelemetry 19 | diagnosticSettings: nsg.?diagnosticSettings 20 | roleAssignments: nsg.?roleAssignments 21 | } 22 | } 23 | 24 | @description('Network Security Group resource ID.') 25 | output resourceId string = inner.outputs.resourceId 26 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.private-dns-zone.bicep: -------------------------------------------------------------------------------- 1 | // AVM Private DNS Zone wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { privateDnsZoneDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Private DNS Zone definition.') 7 | param privateDnsZone privateDnsZoneDefinitionType 8 | 9 | module inner 'br/public:avm/res/network/private-dns-zone:0.8.0' = { 10 | name: 'pdns-${uniqueString(privateDnsZone.name!)}' 11 | params: { 12 | name: privateDnsZone.name 13 | location: privateDnsZone.?location ?? 'global' 14 | tags: privateDnsZone.?tags 15 | enableTelemetry: privateDnsZone.?enableTelemetry 16 | virtualNetworkLinks: privateDnsZone.?virtualNetworkLinks 17 | a: privateDnsZone.?a 18 | lock: privateDnsZone.?lock 19 | roleAssignments: privateDnsZone.?roleAssignments 20 | } 21 | } 22 | 23 | @description('Private DNS Zone resource ID.') 24 | output resourceId string = inner.outputs.resourceId 25 | 26 | @description('Private DNS Zone name.') 27 | output name string = inner.outputs.name 28 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.private-endpoint.bicep: -------------------------------------------------------------------------------- 1 | // AVM Private Endpoint wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { privateEndpointDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Private Endpoint definition.') 7 | param privateEndpoint privateEndpointDefinitionType 8 | 9 | module inner 'br/public:avm/res/network/private-endpoint:0.11.0' = { 10 | name: 'pe-avm-${privateEndpoint.name}' 11 | params: { 12 | name: privateEndpoint.name 13 | location: privateEndpoint.?location 14 | subnetResourceId: privateEndpoint.subnetResourceId 15 | privateLinkServiceConnections: privateEndpoint.?privateLinkServiceConnections 16 | manualPrivateLinkServiceConnections: privateEndpoint.?manualPrivateLinkServiceConnections 17 | customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName 18 | privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup 19 | tags: privateEndpoint.?tags 20 | lock: privateEndpoint.?lock 21 | enableTelemetry: privateEndpoint.?enableTelemetry 22 | roleAssignments: privateEndpoint.?roleAssignments 23 | } 24 | } 25 | 26 | @description('Private Endpoint resource ID.') 27 | output resourceId string = inner.outputs.resourceId 28 | 29 | @description('Private Endpoint resource name.') 30 | output name string = inner.outputs.name 31 | 32 | @description('Private Endpoint network interface resource IDs.') 33 | output networkInterfaceResourceIds string[] = inner.outputs.networkInterfaceResourceIds 34 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.public-ip-address.bicep: -------------------------------------------------------------------------------- 1 | // AVM Public IP wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { publicIpDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Public IP Address definition.') 7 | param pip publicIpDefinitionType 8 | 9 | module inner 'br/public:avm/res/network/public-ip-address:0.9.0' = { 10 | name: 'pip-avm-${pip.name}' 11 | params: { 12 | name: pip.name 13 | location: pip.?location 14 | publicIPAllocationMethod: pip.?publicIPAllocationMethod 15 | publicIPAddressVersion: pip.?publicIPAddressVersion 16 | skuName: pip.?skuName 17 | skuTier: pip.?skuTier 18 | availabilityZones: pip.?zones 19 | tags: pip.?tags 20 | lock: pip.?lock 21 | enableTelemetry: pip.?enableTelemetry 22 | diagnosticSettings: pip.?diagnosticSettings 23 | roleAssignments: pip.?roleAssignments 24 | } 25 | } 26 | 27 | @description('Public IP resource ID.') 28 | output resourceId string = inner.outputs.resourceId 29 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.virtual-network.bicep: -------------------------------------------------------------------------------- 1 | // AVM Virtual Network wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { vNetDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Virtual Network definition.') 7 | param vnet vNetDefinitionType 8 | 9 | module inner 'br/public:avm/res/network/virtual-network:0.7.0' = { 10 | name: 'vnet-${uniqueString(vnet.name!)}' 11 | params: { 12 | name: vnet.name 13 | addressPrefixes: vnet.addressPrefixes 14 | subnets: vnet.?subnets 15 | location: vnet.?location 16 | ddosProtectionPlanResourceId: vnet.?ddosProtectionPlanResourceId 17 | diagnosticSettings: vnet.?diagnosticSettings 18 | dnsServers: vnet.?dnsServers 19 | enableTelemetry: vnet.?enableTelemetry 20 | enableVmProtection: vnet.?enableVmProtection 21 | flowTimeoutInMinutes: vnet.?flowTimeoutInMinutes 22 | ipamPoolNumberOfIpAddresses: vnet.?ipamPoolNumberOfIpAddresses 23 | lock: vnet.?lock 24 | peerings: vnet.?peerings 25 | roleAssignments: vnet.?roleAssignments 26 | tags: vnet.?tags 27 | virtualNetworkBgpCommunity: vnet.?virtualNetworkBgpCommunity 28 | vnetEncryption: vnet.?vnetEncryption 29 | vnetEncryptionEnforcement: vnet.?vnetEncryptionEnforcement 30 | } 31 | } 32 | 33 | @description('Virtual Network resource ID.') 34 | output resourceId string = inner.outputs.resourceId 35 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.network.waf-policy.bicep: -------------------------------------------------------------------------------- 1 | import { wafPolicyDefinitionsType } from '../common/types.bicep' 2 | 3 | @description('Required. Web Application Firewall (WAF) policy configuration object.') 4 | param wafPolicy wafPolicyDefinitionsType 5 | 6 | // Create the WAF Policy using native ARM resource 7 | resource wafPolicyDeployment 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2024-01-01' = { 8 | name: wafPolicy.name! 9 | location: wafPolicy.?location ?? resourceGroup().location 10 | tags: wafPolicy.?tags 11 | properties: { 12 | policySettings: wafPolicy.?policySettings ?? { 13 | requestBodyCheck: true 14 | maxRequestBodySizeInKb: 128 15 | fileUploadLimitInMb: 100 16 | state: 'Enabled' 17 | mode: 'Prevention' 18 | } 19 | customRules: wafPolicy.?customRules ?? [] 20 | managedRules: { 21 | managedRuleSets: [for ruleSet in wafPolicy.managedRules!.managedRuleSets: { 22 | ruleSetType: ruleSet.ruleSetType 23 | ruleSetVersion: ruleSet.ruleSetVersion 24 | }] 25 | } 26 | } 27 | } 28 | 29 | // Outputs 30 | @description('WAF Policy resource ID.') 31 | output resourceId string = wafPolicyDeployment.id 32 | 33 | @description('WAF Policy name.') 34 | output name string = wafPolicyDeployment.name 35 | 36 | @description('WAF Policy resource group name.') 37 | output resourceGroupName string = resourceGroup().name 38 | 39 | @description('WAF Policy location.') 40 | output location string = wafPolicyDeployment.location 41 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.operational-insights.workspace.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'resourceGroup' 2 | 3 | import { logAnalyticsDefinitionType } from '../common/types.bicep' 4 | 5 | @description('Log Analytics Workspace configuration.') 6 | param logAnalytics logAnalyticsDefinitionType 7 | 8 | module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = { 9 | name: 'law-avm-${logAnalytics.name!}' 10 | params: { 11 | name: logAnalytics.name! 12 | location: logAnalytics.?location 13 | tags: logAnalytics.?tags 14 | enableTelemetry: logAnalytics.?enableTelemetry 15 | dataRetention: logAnalytics.?dataRetention 16 | linkedStorageAccounts: logAnalytics.?linkedStorageAccounts 17 | dataExports: logAnalytics.?dataExports 18 | tables: logAnalytics.?tables 19 | roleAssignments: logAnalytics.?roleAssignments 20 | dailyQuotaGb: logAnalytics.?dailyQuotaGb 21 | dataSources: logAnalytics.?dataSources 22 | diagnosticSettings: logAnalytics.?diagnosticSettings 23 | gallerySolutions: logAnalytics.?gallerySolutions 24 | lock: logAnalytics.?lock 25 | publicNetworkAccessForIngestion: logAnalytics.?publicNetworkAccessForIngestion 26 | publicNetworkAccessForQuery: logAnalytics.?publicNetworkAccessForQuery 27 | savedSearches: logAnalytics.?savedSearches 28 | storageInsightsConfigs: logAnalytics.?storageInsightsConfigs 29 | skuName: logAnalytics.?skuName 30 | skuCapacityReservationLevel: logAnalytics.?skuCapacityReservationLevel 31 | forceCmkForQuery: logAnalytics.?forceCmkForQuery 32 | features: logAnalytics.?features 33 | managedIdentities: logAnalytics.?managedIdentities 34 | linkedServices: logAnalytics.?linkedServices 35 | } 36 | } 37 | 38 | @description('The resource ID of the Log Analytics workspace.') 39 | output resourceId string = logAnalyticsWorkspace.outputs.resourceId 40 | 41 | @description('The name of the Log Analytics workspace.') 42 | output name string = logAnalyticsWorkspace.outputs.name 43 | 44 | @description('The resource group the Log Analytics workspace was deployed into.') 45 | output resourceGroupName string = logAnalyticsWorkspace.outputs.resourceGroupName 46 | 47 | @description('The location the Log Analytics workspace was deployed into.') 48 | output location string = logAnalyticsWorkspace.outputs.location 49 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.search.search-service.bicep: -------------------------------------------------------------------------------- 1 | // AVM AI Search wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { kSAISearchDefinitionType } from '../common/types.bicep' 5 | 6 | @description('AI Search definition.') 7 | param aiSearch kSAISearchDefinitionType 8 | 9 | module inner 'br/public:avm/res/search/search-service:0.11.1' = { 10 | name: 'search-avm-${aiSearch.name}' 11 | params: { 12 | name: aiSearch.name 13 | location: aiSearch.?location 14 | authOptions: aiSearch.?authOptions 15 | diagnosticSettings: aiSearch.?diagnosticSettings 16 | cmkEnforcement: aiSearch.?cmkEnforcement 17 | hostingMode: aiSearch.?hostingMode 18 | lock: aiSearch.?lock 19 | managedIdentities: aiSearch.?managedIdentities 20 | networkRuleSet: aiSearch.?networkRuleSet 21 | partitionCount: aiSearch.?partitionCount 22 | privateEndpoints: aiSearch.?privateEndpoints 23 | publicNetworkAccess: aiSearch.?publicNetworkAccess 24 | replicaCount: aiSearch.?replicaCount 25 | roleAssignments: aiSearch.?roleAssignments 26 | secretsExportConfiguration: aiSearch.?secretsExportConfiguration 27 | semanticSearch: aiSearch.?semanticSearch 28 | sku: aiSearch.?sku 29 | tags: aiSearch.?tags 30 | enableTelemetry: aiSearch.?enableTelemetry 31 | } 32 | } 33 | 34 | output resourceId string = inner.outputs.resourceId 35 | output name string = inner.outputs.name 36 | output location string = inner.outputs.location 37 | output resourceGroupName string = inner.outputs.resourceGroupName 38 | output systemAssignedMIPrincipalId string = inner.outputs.?systemAssignedMIPrincipalId ?? '' 39 | -------------------------------------------------------------------------------- /bicep/infra/wrappers/avm.res.storage.storage-account.bicep: -------------------------------------------------------------------------------- 1 | // AVM Storage Account wrapper (pass-through) 2 | targetScope = 'resourceGroup' 3 | 4 | import { storageAccountDefinitionType } from '../common/types.bicep' 5 | 6 | @description('Storage Account definition.') 7 | param storageAccount storageAccountDefinitionType 8 | 9 | module inner 'br/public:avm/res/storage/storage-account:0.27.0' = { 10 | name: 'st-avm-${storageAccount.name}' 11 | params: { 12 | name: storageAccount.name 13 | location: storageAccount.?location 14 | kind: storageAccount.?kind 15 | skuName: storageAccount.?skuName 16 | accessTier: storageAccount.?accessTier 17 | allowBlobPublicAccess: storageAccount.?allowBlobPublicAccess 18 | allowCrossTenantReplication: storageAccount.?allowCrossTenantReplication 19 | allowedCopyScope: storageAccount.?allowedCopyScope 20 | allowSharedKeyAccess: storageAccount.?allowSharedKeyAccess 21 | azureFilesIdentityBasedAuthentication: storageAccount.?azureFilesIdentityBasedAuthentication 22 | blobServices: storageAccount.?blobServices 23 | customDomainName: storageAccount.?customDomainName 24 | customDomainUseSubDomainName: storageAccount.?customDomainUseSubDomainName 25 | customerManagedKey: storageAccount.?customerManagedKey 26 | defaultToOAuthAuthentication: storageAccount.?defaultToOAuthAuthentication 27 | diagnosticSettings: storageAccount.?diagnosticSettings 28 | dnsEndpointType: storageAccount.?dnsEndpointType 29 | enableHierarchicalNamespace: storageAccount.?enableHierarchicalNamespace 30 | enableNfsV3: storageAccount.?enableNfsV3 31 | enableSftp: storageAccount.?enableSftp 32 | fileServices: storageAccount.?fileServices 33 | isLocalUserEnabled: storageAccount.?isLocalUserEnabled 34 | keyType: storageAccount.?keyType 35 | largeFileSharesState: storageAccount.?largeFileSharesState 36 | localUsers: storageAccount.?localUsers 37 | lock: storageAccount.?lock 38 | managedIdentities: storageAccount.?managedIdentities 39 | managementPolicyRules: storageAccount.?managementPolicyRules 40 | minimumTlsVersion: storageAccount.?minimumTlsVersion 41 | networkAcls: storageAccount.?networkAcls 42 | privateEndpoints: storageAccount.?privateEndpoints 43 | publicNetworkAccess: storageAccount.?publicNetworkAccess 44 | queueServices: storageAccount.?queueServices 45 | requireInfrastructureEncryption: storageAccount.?requireInfrastructureEncryption 46 | roleAssignments: storageAccount.?roleAssignments 47 | sasExpirationAction: storageAccount.?sasExpirationAction 48 | sasExpirationPeriod: storageAccount.?sasExpirationPeriod 49 | secretsExportConfiguration: storageAccount.?secretsExportConfiguration 50 | supportsHttpsTrafficOnly: storageAccount.?supportsHttpsTrafficOnly 51 | tableServices: storageAccount.?tableServices 52 | tags: storageAccount.?tags 53 | enableTelemetry: storageAccount.?enableTelemetry 54 | } 55 | } 56 | 57 | output resourceId string = inner.outputs.resourceId 58 | output name string = inner.outputs.name 59 | output location string = inner.outputs.location 60 | output resourceGroupName string = inner.outputs.resourceGroupName 61 | output serviceEndpoints object = inner.outputs.serviceEndpoints 62 | output systemAssignedMIPrincipalId string = inner.outputs.?systemAssignedMIPrincipalId ?? '' 63 | -------------------------------------------------------------------------------- /bicep/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", 3 | "version": "0.1" 4 | } -------------------------------------------------------------------------------- /docs/Compute.md: -------------------------------------------------------------------------------- 1 | ## Compute 2 | 3 | | ID | Specification | 4 | |-------|--------------| 5 | | C-R1 | **Standardize compute**: The AI Landing zone must provide implementation of Azure Container Apps as a compute option while providing guidance on the other compute options.

Best Practice:

You need compute resources for certain actions like prompt flows and training models. A service like Machine Learning has different compute options, such as compute instances, clusters, and serverless options. Standardize the compute type, runtimes, and shutdown periods. For service-specific compute options, see [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/create-manage-compute) and [Machine Learning](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-create-attach-compute-studio).| 6 | -------------------------------------------------------------------------------- /docs/Cost-Optimization.md: -------------------------------------------------------------------------------- 1 | ## Cost Optimization 2 | 3 | | ID | Specification | 4 | |-------|--------------| 5 | | CO-R1 | **Costing & Pricing**: The AI Landing Zone must provide clear guidance on the cost of deploying it and follow best practices for planning and managing cost in [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/costs-plan-manage), [Azure OpenAI Service](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/manage-costs), and [Azure Machine Learning](https://learn.microsoft.com/en-us/azure/machine-learning/concept-plan-manage-cost). | 6 | | CO-R2 | **PTU & PAYGO Endpoints**: The AI Landing Zone must provide guidance and implementation of AOAI with two endpoints, one with PTU and one with PAYGO setup.

Best Practice:

If you have predictable workloads, use AI commitment tiers in Azure AI services. For Azure OpenAI models, use [provisioned throughput units](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/provisioned-throughput) (PTUs), which can be less expensive than pay-as-you-go (consumption-based) pricing. It's common to combine PTU endpoints and consumption-based endpoints for cost optimization. Use PTUs on the AI model primary endpoint and a secondary, consumption-based AI endpoint for spillover. For more information, see [Introduce a gateway for multiple Azure OpenAI instances](https://learn.microsoft.com/en-us/azure/architecture/ai-ml/guide/azure-openai-gateway-multi-backend#multiple-azure-openai-instances-in-a-single-region-and-single-subscription). | 7 | | CO-R3 | **Deployment Type**: The AI Landing Zone should provide guidance and implementation of various AOAI deployment types in particular the global deployment type.

Best Practice:

Azure OpenAI models allow you to use different [deployment types](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/deployment-types). Global deployment offers lower cost-per-token pricing on certain OpenAI models. | 8 | | CO-R4 | **Service Selection**: The AI Landing Zone should provide guidance on service selection based on cost and performance.

Best Practice:

Choose the right hosting infrastructure, depending on your solution's needs. For example, for generative AI workloads, options include managed online endpoints, Azure Kubernetes Service (AKS), and Azure App Service, each with its own billing model. Select the option that provides the best balance between performance and cost for your specific requirements. | 9 | | CO-R5 | **Auto-shutdown**: Implement auto shutdown policy for compute resources in non-prod implementation.

Best Practice:

Define and enforce a policy stating that AI resources must use the automatic shutdown feature on virtual machines and compute instances in Azure AI Foundry and Azure Machine Learning. Automatic shutdown is applicable to nonproduction environments and production workloads that you can take offline for certain periods of time. | 10 | 11 | -------------------------------------------------------------------------------- /docs/Data.md: -------------------------------------------------------------------------------- 1 | ## Data 2 | 3 | | ID | Specification | 4 | |-------|--------------| 5 | | D-R1 | The AI Landing Zone must provide deployment for Cosmos DB for chat history, agent state, agent orchestration history.| 6 | | D-R2 | The AI Landing Zone must provide deployment for Cosmos DB, SQL DB or PostgreSQL resources as sources for RAG pattern data sources including Agent memory. | 7 | | D-R3 | The AI Landing Zone must provide guidance on data access and integration patterns for Fabric. | 8 | -------------------------------------------------------------------------------- /docs/Governance.md: -------------------------------------------------------------------------------- 1 | ## Governance 2 | 3 | | ID | Specification | 4 | |------|--------------| 5 | | G-R1 | **Align with built-in policies**: The AI Landing Zone should provide guidance and implantation of built-in AI related policies, and the implementation must comply with those policies.



Best Practice

Automate policy enforcement with Azure Policy to enforce policies automatically across AI deployments, reducing human error. The AI Landing Zone must adhere to recommended Azure Policies for AI resources. Apply AI policies to each management group. Start with baseline policies for each workload type, such as those policies used in [Azure landing zones](https://github.com/Azure/Enterprise-Scale/wiki/ALZ-Policies). Use Azure Policy to apply built-in policy definitions for each AI platform you're using. It includes [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-services/policy-reference?context=%2Fazure%2Fai-studio%2Fcontext%2Fcontext), [Azure Machine Learning](https://learn.microsoft.com/en-us/azure/machine-learning/policy-reference), [Azure AI services](https://learn.microsoft.com/en-us/azure/ai-services/policy-reference), [Azure AI Search](https://learn.microsoft.com/en-us/azure/search/policy-reference), and others. For Azure landing zone users, the [deployment](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/implementation-options#environment-development-approaches) includes a curated set of recommended built-in policies for Azure AI platform services. Select the policy initiative you want to use under the _Workload Specific Compliance_ category during an Azure landing zone deployment. The policies sets include [Azure OpenAI](https://www.azadvertizer.net/azpolicyinitiativesadvertizer/Enforce-Guardrails-OpenAI.html), [Azure Machine Learning](https://www.azadvertizer.net/azpolicyinitiativesadvertizer/Enforce-Guardrails-MachineLearning.html), and [Azure AI Search](https://www.azadvertizer.net/azpolicyinitiativesadvertizer/Enforce-Guardrails-CognitiveServices.html), and [Azure Bot services](https://www.azadvertizer.net/azpolicyinitiativesadvertizer/Enforce-Guardrails-BotService.html). Use the applicable [regulatory compliance initiatives](https://learn.microsoft.com/en-us/azure/governance/policy/samples/#regulatory-compliance) in Azure Policy for your industry. | 6 | | G-R2 | **Industry Standards**: The AI Landing Zone should provide guidance on how to align and comply with industry standard guidance such as the [NIST Artificial Intelligence Risk Management Framework (AI RMF)](https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.100-1.pdf) and [NIST AI RMF Playbook](https://airc.nist.gov/AI_RMF_Knowledge_Base/Playbook) | 7 | | G-R3 | **Responsible AI**: The AI Landing Zone should provide guidance and implementation of [responsible AI standards](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/ai/govern#assess-ai-organizational-risks) using the [Responsible AI dashboard](https://learn.microsoft.com/en-us/azure/machine-learning/concept-responsible-ai-dashboard) to generate reports around model outputs. | 8 | | G-R4 | **AI Content Safety**: The AI Landing Zone should provide guidance and implementation of [Azure AI Content Safety](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/overview) APIs that can be called for testing to facilitate content safety testing.

Best Practice

Use [Azure AI Content Safety](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/overview) to define a baseline content filter for your approved AI models. This safety system runs both the prompt and completion for your model through a group of classification models. These classification models detect and help prevent the output of harmful content across a range of categories. Content Safety provides features like prompt shields, groundedness detection, and protected material text detection. It scans images and text. Create a process for application teams to communicate different governance needs. | 9 | | G-R5 | **Model Availability:** The AI Landing zone should provide guidance and implementation to govern model availability.

Best Practice

Use Azure Policy to manage which specific models your teams are allowed to deploy from the Azure AI Foundry model catalog. You have the option to use a [built-in policy](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/built-in-policy-model-deployment) or create a custom policy. Since this approach uses an allowlist, begin with an _audit_ effect. The _audit_ effect allows you to monitor the models your teams are using without restricting deployments. Only switch to the _deny_ effect once you understand the AI development and experimentation needs of workload teams, so you don't hinder their progress unnecessarily. If you switch a policy to _deny_, it doesn't automatically remove noncompliant models that teams have already deployed. You must remediate those models manually. | 10 | 11 | -------------------------------------------------------------------------------- /docs/Identity.md: -------------------------------------------------------------------------------- 1 | ## Identity 2 | 3 | | ID | Specification | 4 | |-------|--------------| 5 | | I-R1 | The AI Landing Zone must provide implementation of managed identities with least privilege access.

Best Practice:

Use [managed identity](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview) on all supported Azure services. Grant least privilege access to application resources that need to access AI model endpoints. Secure Azure service-to-service interactions. Use managed identity to allow Azure services to authenticate to each other without managing credentials.| 6 | | I-R2 | The AI Landing Zone must provide guidance on leveraging MFA and PIM for sensitive accounts.

Best Practice:

Enable [multifactor authentication](https://learn.microsoft.com/entra/identity/authentication/tutorial-enable-azure-mfa) (MFA) and prefer secondary administrative accounts or just-in-time access with [Privileged Identity Management](https://learn.microsoft.com/entra/id-governance/privileged-identity-management/pim-configure) (PIM) for sensitive accounts. Limit control plane access using services like Azure Bastion as secure entry points into private networks. | 7 | | I-R3 | **Use Microsoft Entra ID for authentication**: The AI Landing Zone must provide guidance on leveraging MFA and PIM for sensitive accounts.

Best Practice:

Wherever possible, eliminate static API keys in favor of Microsoft Entra ID for authentication. This step enhances security through centralized identity management and reduces secret management overhead. Also limit the distribution of API keys. Instead, prefer identities in Microsoft Entra ID over API keys for authentication. Audit the list of individuals with API key access to ensure it's current. For authentication guidance, see [Azure AI Foundry](https://learn.microsoft.com/azure/ai-studio/concepts/rbac-ai-studio), [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/how-to/managed-identity), [Azure AI services](https://learn.microsoft.com/azure/ai-services/authentication), [Azure Machine Learning](https://learn.microsoft.com/azure/machine-learning/how-to-setup-authentication). | 8 | | I-R4 | **Use Conditional Access policies**: The AI Landing Zone must provide guidance on leveraging Conditional Access policies.

Best Practice:

Implement [risk-based Conditional Access policies](https://learn.microsoft.com/entra/id-protection/howto-identity-protection-configure-risk-policies) that respond to unusual sign-in activity or suspicious behavior. Use signals like user location, device state, and sign-in behavior to trigger extra verification steps. Require MFA for accessing critical AI resources to enhance security. Restrict access to AI infrastructure based on geographic locations or trusted IP ranges. Ensure that only compliant devices (those meeting security requirements) can access AI resources. | 9 | | I-R5 | **Configure least privilege access:** The AI Landing Zone must provide implementation and guidance on how to configure least privilege access. Configure least privilege access by implementing role-based access control (RBAC) to provide minimal access to data and services. Assign roles to users and groups based on their responsibilities. Use Azure RBAC to fine-tune access control for specific resources such as virtual machines and storage accounts. Ensure users have only the minimum level of access necessary to perform their tasks. Regularly review and adjust permissions to prevent privilege creep.

1. Assign Azure AI User to develop with this project.
2. Assign Azure AI Project Manager to develop and manage project settings. | 10 | | I-R6 | The AI Landing Zone must provide implementation and guidance on how to disable key based access and only have access to AI Model endpoints using Microsoft Entra ID.

Best Practice:

Secure external access to AI model endpoints. Require clients to authenticate using Microsoft Entra ID when accessing AI model endpoints. | 11 | -------------------------------------------------------------------------------- /docs/Operational-Excellence.md: -------------------------------------------------------------------------------- 1 | ## Operational Excellence 2 | 3 | | ID | Specification | 4 | |------|--------------| 5 | | | | 6 | | | | 7 | | | | 8 | | | | 9 | | | | 10 | | | | 11 | | | | 12 | -------------------------------------------------------------------------------- /docs/Performance-Efficiency.md: -------------------------------------------------------------------------------- 1 | ## Performance Efficiency 2 | 3 | | ID | Specification | 4 | |------|--------------| 5 | | | | 6 | | | | 7 | | | | 8 | | | | 9 | | | | 10 | | | | 11 | | | | 12 | -------------------------------------------------------------------------------- /docs/Platform-Automation.md: -------------------------------------------------------------------------------- 1 | ## Platform Automation 2 | 3 | The Platform Automation design area covers DevOps, MLOps and GenAIOps 4 | 5 | | ID | Specification | 6 | |------|--------------| 7 | | P-R1 | **MLOps & GenAIOps**: The reference architecture and implementation should provide guidance on how customers can leverage the AI Landing Zone with MLOps and GenAIOps. | 8 | 9 | -------------------------------------------------------------------------------- /docs/Reliability.md: -------------------------------------------------------------------------------- 1 | ## Reliability 2 | 3 | | ID | Specification | 4 | |-------|--------------| 5 | | R-R1 | **Multi Region Disaster Recovery**: The AI Landing zone should provide AI endpoints in at least two regions to provide redundancy and ensure high availability.

Best Practice:

Establish a policy for business continuity and disaster recovery for your AI endpoints and AI data. Configure baseline disaster recovery for resources that host your AI model endpoints. These resources include [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/disaster-recovery), [Azure Machine Learning](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-high-availability-machine-learning), [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/business-continuity-disaster-recovery), or Azure AI services. All Azure data stores, such as [Azure Blob Storage](https://learn.microsoft.com/en-us/azure/storage/common/storage-disaster-recovery-guidance), [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/online-backup-and-restore), and [Azure SQL Database](https://learn.microsoft.com/en-us/azure/azure-sql/accelerated-database-recovery), provide reliability and disaster recovery guidance that you should follow. Implement multi-region deployments to ensure high availability and resiliency for both generative and nongenerative AI systems For more information, see multi-region deployment in [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-studio/how-to/disaster-recovery#plan-for-multi-regional-deployment), [Azure Machine Learning](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-high-availability-machine-learning#plan-for-multi-regional-deployment), and [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/business-continuity-disaster-recovery).| 6 | | R-R2 | **APRL:** The AI Landing Zone should align with the service and workload level recommendations from APRL [Artificial Intelligence (GPT-RAG)](https://azure.github.io/Azure-Proactive-Resiliency-Library-v2/azure-specialized-workloads/ai/) and [Azure Resources.](https://azure.github.io/Azure-Proactive-Resiliency-Library-v2/azure-resources/)| 7 | -------------------------------------------------------------------------------- /docs/Resource-Organization.md: -------------------------------------------------------------------------------- 1 | ## Resource Organization 2 | 3 | | ID | Specification | 4 | |------|--------------| 5 | | R-R1 | **Region Selection**: The AI Landing Zone must provide guidance on how to select regions based on the combination of regional availability of Azure Services and their configuration.

Best Practice:

Before deployment, ensure that there's [availability in the region](https://azure.microsoft.com/explore/global-infrastructure/products-by-region/#products-by-region_tab5) for the AI resources that you need. Certain regions might not provide specific AI services or might have limited features, which can affect the functionality of your solution. This limitation can also affect the scalability of your deployment. For example, Azure OpenAI service availability can vary based on your deployment model. These deployment models include global standard, global provisioned, regional standard, and regional provisioned. Check the AI service to confirm that you have access to the necessary resources. | 6 | | R-R2 | **Quota**: The AI Landing Zone must provide guidance on the quota required to deploy the resources.

Best Practice:

Consider the quota or subscription limits in your chosen region as your AI workloads grow. Azure services have regional subscription limits. These limits can affect large-scale AI model deployments, such as large inference workloads. To prevent disruptions, contact Azure support in advance if you foresee a need for extra capacity. | 7 | | R-R3 | **Limits**: The AI Landing Zone should provide guidance on Azure subscription and region quota limits based on [Azure’s subscription quota limitations](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits) to avoid unexpected service disruptions. | 8 | | R-R4 | **AI Foundry Accounts & Project**: The AI Landing Zone must provide guidance and implementation of multi-account and multi-project deployment.

Best Practice:

Azure offers tools like Azure AI Foundry [hubs and projects](https://learn.microsoft.com/en-us/azure/ai-studio/concepts/ai-resources) to enforce governance and security. Azure Machine Learning has similar capabilities with its [hub workspaces](https://learn.microsoft.com/en-us/azure/machine-learning/concept-hub-workspace). Use a hub per billing boundary to allocate costs across different teams. For more information, see [Manage AI deployments](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/scenarios/ai/manage#manage-ai-deployment). Use distinct workspaces to organize and manage AI artifacts like datasets, models, and experiments. Workspaces centralize resource management and simplify access control. For example, use [projects](https://learn.microsoft.com/en-us/azure/ai-studio/concepts/ai-resources#organize-work-in-projects-for-customization) within Azure AI Foundry to manage resources and permissions efficiently, facilitating collaboration while maintaining security boundaries. | 9 | 10 | -------------------------------------------------------------------------------- /docs/Security.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | | ID | Specification | 4 | |-------|--------------| 5 | | S-R1 | **Microsoft Defender for Cloud**: The AI Landing Zone must align with MDC Recommendations by default.

Best Practice:

MDC can help [discover generative AI workloads](https://learn.microsoft.com/en-us/azure/defender-for-cloud/identify-ai-workload-model) and in [predeployment generative AI artifacts.](https://learn.microsoft.com/en-us/azure/defender-for-cloud/explore-ai-risk) Also [AI security posture management](https://learn.microsoft.com/en-us/azure/defender-for-cloud/ai-security-posture) in Microsoft Defender for Cloud can be used to automate detection and remediation of generative AI risks. Defender for Cloud provides a cost-effective approach for detecting configurations in your deployed resources that aren't secure. You should also enable [AI threat protection.](https://learn.microsoft.com/en-us/azure/defender-for-cloud/ai-threat-protection)| 6 | | S-R2 | **Microsoft Cloud Security Baseline**: The AI Landing Zone must comply with [Azure security baselines](https://learn.microsoft.com/en-us/security/benchmark/azure/security-baselines-overview) and follow [Azure Service Guides](https://learn.microsoft.com/en-us/azure/well-architected/service-guides/?product=popular) for security guidance.| 7 | | S-R3 | **Microsoft Purview:** The AI Landing Zone should provide guidance on how Purview can be leveraged to secure data in an AI landing zone.

Best Practice:

Sensitive data in AI workflows increases the risk of insider threats, data leaks and data oversharing. Use tools like [Microsoft Purview Insider Risk Management](https://learn.microsoft.com/en-us/purview/insider-risk-management)to assess enterprise-wide data risks and prioritize them based on data sensitivity. | 8 | | S-R4 | **Industry Security Standards for AI**: The AI Landing Zone should align with frameworks like [MITRE ATLAS](https://atlas.mitre.org/) and [OWASP Generative AI risk](https://genai.owasp.org/) for identifying risks across the architecture. | 9 | | S-R5 | **Monitor outputs and apply prompt shielding:** The AI Landing Zone should implement/guide on using AI Content Safety. 

Best Practice:

Regularly inspect the data returned by AI models to detect and mitigate risks associated with malicious or unpredictable user prompts. Implement [Prompt Shields](https://learn.microsoft.com/en-us/azure/ai-services/content-safety/concepts/jailbreak-detection) to scan text for the risk of a user input attack on generative Al models. | 10 | | S-R6 | The AI Landing Zone must provide implementation and guidance on zero trust. | -------------------------------------------------------------------------------- /docs/service-inventory-config.md: -------------------------------------------------------------------------------- 1 | # AI Landing Zone Bill of Materials (BoM) 2 | 3 | The AI Landing Zone Service Inventory is a curated list of Azure services that form the architecture of the AI Landing Zone. It defines which services are deployed by default, which are feature-flagged, and how they align across Bicep, Terraform, and Portal implementations. This inventory ensures consistency, feature parity, and modularity across deployment options, supporting both generative and non-generative AI workloads. 4 | 5 | ## Service Inventory 6 | 7 | 8 | 9 | ## Service Configuration -------------------------------------------------------------------------------- /media/AI-Landing-Zone-with-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/AI-Landing-Zones/794d40870ba9c7c153feff9c8572c9d0f2f37505/media/AI-Landing-Zone-with-platform.png -------------------------------------------------------------------------------- /media/AI-Landing-Zone-without-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/AI-Landing-Zones/794d40870ba9c7c153feff9c8572c9d0f2f37505/media/AI-Landing-Zone-without-platform.png -------------------------------------------------------------------------------- /terraform/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/azterraform:avm-latest", 3 | "containerEnv": { 4 | "AVM_IN_CONTAINER": "true" 5 | }, 6 | "updateRemoteUserUID": true, 7 | "runArgs": [ 8 | "--cap-add=SYS_PTRACE", 9 | "--security-opt", 10 | "seccomp=unconfined", 11 | "--init", 12 | "--network=host" 13 | ], 14 | "mounts": [ 15 | "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" 16 | ], 17 | "onCreateCommand": "terraform version", 18 | "customizations": { 19 | "vscode": { 20 | "settings": { 21 | "terraform.languageServer.terraform.path": "/home/runtimeuser/tfenv/bin/terraform" 22 | }, 23 | "extensions": [ 24 | "ms-azuretools.vscode-azureterraform", 25 | "EditorConfig.EditorConfig", 26 | "hashicorp.terraform" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /terraform/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /terraform/.gitattributes: -------------------------------------------------------------------------------- 1 | *.tfvars text eol=lf 2 | *.hcl text eol=lf 3 | *.go text eol=lf 4 | *.tf text eol=lf -------------------------------------------------------------------------------- /terraform/.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github/CODEOWNERS @Azure/avm-core-team-technical-terraform 2 | -------------------------------------------------------------------------------- /terraform/.github/ISSUE_TEMPLATE/avm_module_issue.yml: -------------------------------------------------------------------------------- 1 | name: AVM - Module Issue ➕🐛🔒 2 | description: Want to request a new Module feature or report a bug? Let us know! 3 | title: "[AVM Module Issue]: " 4 | labels: ["Needs: Triage :mag:", "Language: Terraform :globe_with_meridians:"] 5 | projects: ["Azure/566"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thank you for submitting this AVM Module Issue! To help us triage your issue, please provide the below details. 11 | 12 | > **NOTE**: If you'd like to propose a new AVM module, please file an [AVM Module Proposal](https://aka.ms/AVM/ModuleProposal). 13 | - type: checkboxes 14 | id: existing-checks 15 | attributes: 16 | label: Check for previous/existing GitHub issues 17 | description: By submitting this issue, you confirm that you have searched for previous/existing GitHub issues to avoid creating a duplicate. 18 | options: 19 | - label: I have checked for previous/existing GitHub issues 20 | required: true 21 | - type: dropdown 22 | id: issue-type 23 | attributes: 24 | label: Issue Type? 25 | description: How would you best describe this issue? Is this a... 26 | options: 27 | - "" 28 | - "Feature Request" 29 | - "Bug" 30 | - "I'm not sure" 31 | validations: 32 | required: true 33 | - type: input 34 | id: module-version 35 | attributes: 36 | label: (Optional) Module Version 37 | description: Please provide which version(s) of the module does this issue apply to. 38 | validations: 39 | required: false 40 | - type: input 41 | id: correlation-id 42 | attributes: 43 | label: (Optional) Correlation Id 44 | description: Please provide a correlation id if available and appropriate. 45 | validations: 46 | required: false 47 | - type: textarea 48 | id: question-feedback-text 49 | attributes: 50 | label: Description 51 | description: | 52 | Please describe the issue! 53 | > **NOTE**: All requested features must already be supported by the provider and Preview Services ([SFR1](https://azure.github.io/Azure-Verified-Modules/specs/shared/#id-sfr1---category-composition---preview-services)) are not supported. 54 | placeholder: | 55 | 59 | validations: 60 | required: true 61 | -------------------------------------------------------------------------------- /terraform/.github/ISSUE_TEMPLATE/avm_question_feedback.yml: -------------------------------------------------------------------------------- 1 | name: AVM - General Question/Feedback ❔ 2 | description: Just got a question or some general feedback? Let us know! 3 | title: "[AVM Question/Feedback]: " 4 | labels: 5 | [ 6 | "Language: Terraform :globe_with_meridians:", 7 | "Type: Question/Feedback :raising_hand:", 8 | "Needs: Triage :mag:", 9 | ] 10 | projects: ["Azure/538"] 11 | body: 12 | - type: markdown 13 | attributes: 14 | value: | 15 | Thank you for your question/feedback! 16 | 17 | > **NOTE**: If your question/request is related to the AVM site/documentation, please file an issue in the [AVM repo](https://github.com/Azure/Azure-Verified-Modules/issues/new?assignees=&labels=Type%3A+Question%2FFeedback+%3Araising_hand%3A%2CNeeds%3A+Triage+%3Amag%3A&projects=&template=question_feedback.yml&title=%5BQuestion%2FFeedback%5D%3A+). 18 | - type: checkboxes 19 | id: existing-checks 20 | attributes: 21 | label: Check for previous/existing GitHub issues 22 | description: By submitting this issue, you confirm that you have searched for previous/existing GitHub issues to avoid creating a duplicate. 23 | options: 24 | - label: I have checked for previous/existing GitHub issues 25 | required: true 26 | - type: textarea 27 | id: question-feedback-text 28 | attributes: 29 | label: Description 30 | description: Let us know your question or feedback here! 31 | validations: 32 | required: true -------------------------------------------------------------------------------- /terraform/.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: New AVM Module Proposal 📝 4 | url: https://aka.ms/AVM/ModuleProposal 5 | about: Want a new AVM Module to exist? Let us know! -------------------------------------------------------------------------------- /terraform/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 12 | 13 | ## Type of Change 14 | 15 | 16 | 17 | - [ ] Non-module change (e.g. CI/CD, documentation, etc.) 18 | - [ ] Azure Verified Module updates: 19 | - [ ] Bugfix containing backwards compatible bug fixes 20 | - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. 21 | - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. 22 | - [ ] Feature update backwards compatible feature updates. 23 | - [ ] Breaking changes. 24 | - [ ] Update to documentation 25 | 26 | # Checklist 27 | 28 | - [ ] I'm sure there are no other open Pull Requests for the same update/change 29 | - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings 30 | - [ ] I did run all [pre-commit](https://azure.github.io/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#5-run-pre-commit-checks) checks 31 | 32 | 33 | -------------------------------------------------------------------------------- /terraform/.github/actions/1es-runner-auth-docker/action.yml: -------------------------------------------------------------------------------- 1 | author: AVM 2 | name: 1ES runner pool run docker command 3 | description: Runs docker commands on AVM docker image after authentication 4 | inputs: 5 | docker-params: 6 | description: Docker parameters to add, other than the madatory ones required for authentication 7 | required: false 8 | default: "" 9 | makefile-target: 10 | description: The markefile target to run 11 | required: true 12 | runs: 13 | using: composite 14 | steps: 15 | - name: init and run docker command 16 | shell: bash 17 | run: | 18 | set -e 19 | MAX_RETRIES=10 20 | RETRY_COUNT=0 21 | until [ $RETRY_COUNT -ge $MAX_RETRIES ] 22 | do 23 | az login --identity --username $MSI_ID > /dev/null && break 24 | RETRY_COUNT=$[$RETRY_COUNT+1] 25 | sleep 10 26 | done 27 | if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then 28 | echo "Failed to login after $MAX_RETRIES attempts." 29 | exit 1 30 | fi 31 | 32 | declare -A secrets 33 | eval "$(echo $SECRETS_CONTEXT | jq -r 'to_entries[] | @sh "secrets[\(.key|tostring)]=\(.value|tostring)"')" 34 | 35 | declare -A variables 36 | eval "$(echo $VARS_CONTEXT | jq -r 'to_entries[] | @sh "variables[\(.key|tostring)]=\(.value|tostring)"')" 37 | 38 | for key in "${!secrets[@]}"; do 39 | if [[ $key = \TF_VAR_* ]]; then 40 | lowerKey=$(echo "$key" | tr '[:upper:]' '[:lower:]') 41 | finalKey=${lowerKey/tf_var_/TF_VAR_} 42 | export "$finalKey"="${secrets[$key]}" 43 | fi 44 | done 45 | 46 | for key in "${!variables[@]}"; do 47 | if [[ $key = \TF_VAR_* ]]; then 48 | lowerKey=$(echo "$key" | tr '[:upper:]' '[:lower:]') 49 | finalKey=${lowerKey/tf_var_/TF_VAR_} 50 | export "$finalKey"="${variables[$key]}" 51 | fi 52 | done 53 | 54 | echo -e "Custom environment variables:\n$(env | grep TF_VAR_ | grep -v ' "TF_VAR_')" 55 | 56 | export ARM_SUBSCRIPTION_ID=$(az login --identity --username $MSI_ID | jq -r '.[0] | .id') 57 | export ARM_TENANT_ID=$(az login --identity --username $MSI_ID | jq -r '.[0] | .tenantId') 58 | export ARM_CLIENT_ID=$(az identity list | jq -r --arg MSI_ID "$MSI_ID" '.[] | select(.principalId == $MSI_ID) | .clientId') 59 | docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/src -w /src --network=host -e TF_IN_AUTOMATION -e TF_VAR_enable_telemetry -e MSI_ID -e ARM_SUBSCRIPTION_ID -e ARM_TENANT_ID -e ARM_CLIENT_ID -e ARM_USE_MSI=true ${{ inputs.docker-params }} mcr.microsoft.com/azterraform:latest make ${{ inputs.makefile-target }} 60 | -------------------------------------------------------------------------------- /terraform/.github/actions/avmfix/action.yml: -------------------------------------------------------------------------------- 1 | author: AVM 2 | name: avmfix 3 | description: Ensures that avmfix has been run. 4 | 5 | runs: 6 | using: composite 7 | steps: 8 | - name: run avmfix 9 | shell: bash 10 | run: | 11 | docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform make autofix 12 | 13 | - name: detect changes 14 | shell: bash 15 | run: | 16 | if [ -z "$(git status -s)" ]; then 17 | echo "No changes detected" 18 | exit 0 19 | else 20 | echo "AVMfix changes detected, please run:" 21 | echo "> docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform make pre-commit" 22 | echo "... or if you have the avm helper script installed:" 23 | echo "> ./avm pre-commit" 24 | echo "> avm.bat pre-commit (on Windows)" 25 | echo 26 | echo "Then commit and push the changes" 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /terraform/.github/actions/docs-check/action.yml: -------------------------------------------------------------------------------- 1 | author: AVM 2 | name: Docs check 3 | description: Checks that documentation has been updated on PR 4 | runs: 5 | using: composite 6 | steps: 7 | - name: fmt check 8 | shell: bash 9 | run: | 10 | docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform make fmtcheck 11 | 12 | - name: docs check 13 | shell: bash 14 | run: | 15 | docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform make docscheck -------------------------------------------------------------------------------- /terraform/.github/actions/e2e-getexamples/action.yml: -------------------------------------------------------------------------------- 1 | author: AVM 2 | name: e2e - getexamples 3 | description: Gets example directories from `examples/` and outputs them to the next step 4 | inputs: 5 | github-token: 6 | description: The GitHub token to use for the API calls 7 | required: true 8 | outputs: 9 | examples: 10 | description: The examples to test 11 | value: ${{ steps.getexamples.outputs.examples }} 12 | runs: 13 | using: composite 14 | steps: 15 | - name: get examples 16 | id: getexamples 17 | run: | 18 | # Get all the folders in the examples directory 19 | $folders = Get-ChildItem -Directory 20 | 21 | $examples = @() 22 | $e2eTests = @() 23 | 24 | foreach ($folder in $folders) { 25 | # Check if the folder contains at least one .tf file 26 | $files = Get-ChildItem -Path $folder.FullName -File -Filter "*.tf" -Force 27 | if($files.Count -ne 0) { 28 | $examples += $folder.Name 29 | 30 | # Check if the folder contains a .e2eignore file 31 | $ignore = Get-ChildItem -Path $folder.FullName -File -Filter ".e2eignore" -Force 32 | if($ignore.Count -eq 0) { 33 | $e2eTests += $folder.Name 34 | } 35 | } 36 | } 37 | 38 | if($examples.Count -eq 0) { 39 | throw "At least one example must be present for Azure Verified Modules" 40 | } 41 | 42 | $e2eTestsJson = ConvertTo-Json $e2eTests -Compress 43 | Write-Host $e2eTestsJson 44 | 45 | Write-Output "examples=$e2eTestsJson" >> $env:GITHUB_OUTPUT 46 | 47 | working-directory: examples 48 | shell: pwsh 49 | -------------------------------------------------------------------------------- /terraform/.github/actions/linting/action.yml: -------------------------------------------------------------------------------- 1 | author: AVM 2 | name: linting 3 | description: Tests the example supplied in the input. Needs checkout and Azure login prior. 4 | inputs: 5 | github-token: 6 | description: The GitHub token 7 | required: true 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | 13 | - name: terraform fmt check 14 | shell: bash 15 | run: | 16 | docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform make tffmtcheck 17 | 18 | - name: terraform validate 19 | shell: bash 20 | run: | 21 | docker run --rm -v $(pwd):/src -w /src -e ARM_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000 mcr.microsoft.com/azterraform make tfvalidatecheck 22 | 23 | - name: terrafmt check 24 | shell: bash 25 | run: | 26 | docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform make terrafmtcheck 27 | 28 | - name: tflint 29 | shell: bash 30 | run: | 31 | docker run --rm -v $(pwd):/src -w /src -e ARM_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000 mcr.microsoft.com/azterraform make tflint 32 | 33 | - name: tfsec 34 | shell: bash 35 | run: | 36 | echo "Bypass tfsec for now" 37 | -------------------------------------------------------------------------------- /terraform/.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: PR Check 3 | on: 4 | pull_request: 5 | types: ['opened', 'reopened', 'synchronize'] 6 | merge_group: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | check: 11 | permissions: {} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checking for fork 15 | shell: pwsh 16 | run: | 17 | $isFork = "${{ github.event.pull_request.head.repo.fork }}" 18 | if($isFork -eq "true") { 19 | echo "### WARNING: This workflow is disabled for forked repositories. Please follow the [release branch process](https://azure.github.io/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#5-create-a-pull-request-to-the-upstream-repository) if end to end tests are required." >> $env:GITHUB_STEP_SUMMARY 20 | } 21 | 22 | run-managed-workflow: 23 | if: github.event.pull_request.head.repo.fork == false 24 | uses: Azure/avm-terraform-governance/.github/workflows/managed-pr-check.yml@main 25 | name: run managed workflow 26 | secrets: inherit 27 | permissions: 28 | id-token: write 29 | contents: read 30 | -------------------------------------------------------------------------------- /terraform/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Ignore Terraform lock file 30 | .terraform.lock.hcl 31 | 32 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 33 | # example: *tfplan* 34 | 35 | # Ignore CLI configuration files 36 | .terraformrc 37 | terraform.rc 38 | avmmakefile 39 | README-generated.md 40 | avm.tflint.hcl 41 | avm.tflint_example.hcl 42 | avm.tflint_module.hcl 43 | avm.tflint.merged.hcl 44 | avm.tflint_example.merged.hcl 45 | avm.tflint_module.merged.hcl 46 | *tfplan* 47 | *.md.tmp 48 | # MacOS 49 | .DS_Store 50 | 51 | # conftest policy 52 | examples/*/policy 53 | *.mptfbackup 54 | .avm 55 | -------------------------------------------------------------------------------- /terraform/.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ### To generate the output file to partially incorporate in the README.md, 2 | ### Execute this command in the Terraform module's code folder: 3 | # terraform-docs -c .terraform-docs.yml . 4 | 5 | formatter: "markdown document" # this is required 6 | 7 | version: "~> 0.18" 8 | 9 | header-from: "_header.md" 10 | footer-from: "_footer.md" 11 | 12 | recursive: 13 | enabled: false 14 | path: modules 15 | 16 | sections: 17 | hide: [] 18 | show: [] 19 | 20 | content: |- 21 | 22 | {{ .Header }} 23 | 24 | 25 | {{ .Requirements }} 26 | 27 | {{ .Resources }} 28 | 29 | 30 | {{ .Inputs }} 31 | 32 | {{ .Outputs }} 33 | 34 | {{ .Modules }} 35 | 36 | {{ .Footer }} 37 | 38 | output: 39 | file: README.md 40 | mode: replace 41 | template: |- 42 | 43 | {{ .Content }} 44 | 45 | output-values: 46 | enabled: false 47 | from: "" 48 | 49 | sort: 50 | enabled: true 51 | by: required 52 | 53 | settings: 54 | anchor: true 55 | color: true 56 | default: true 57 | description: false 58 | escape: true 59 | hide-empty: false 60 | html: true 61 | indent: 2 62 | lockfile: false 63 | read-comments: true 64 | required: true 65 | sensitive: true 66 | type: true 67 | -------------------------------------------------------------------------------- /terraform/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "azapi-vscode.azapi", 4 | "EditorConfig.EditorConfig", 5 | "hashicorp.terraform" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /terraform/.vscode/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": { 3 | "terraform-mcp-eva": { 4 | "type": "stdio", 5 | "command": "docker", 6 | "args": [ 7 | "run", 8 | "-i", 9 | "--rm", 10 | "-v", 11 | "${workspaceFolder}:/workspace", 12 | "-w", 13 | "/workspace", 14 | "-e", 15 | "TRANSPORT_MODE=stdio", 16 | "-e", 17 | "GITHUB_TOKEN", 18 | "--pull=always", 19 | "ghcr.io/lonegunmanb/terraform-mcp-eva" 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /terraform/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "chat.agent.maxRequests": 500, 3 | "chat.math.enabled": true, 4 | "chat.todoListTool.enabled": true, 5 | "github.copilot.chat.agent.thinkingTool": true 6 | } 7 | -------------------------------------------------------------------------------- /terraform/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /terraform/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /terraform/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /terraform/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | AVM_MAKEFILE_REF := main 3 | 4 | $(shell curl -H 'Cache-Control: no-cache, no-store' -sSL "https://raw.githubusercontent.com/Azure/avm-terraform-governance/$(AVM_MAKEFILE_REF)/Makefile" -o avmmakefile) 5 | -include avmmakefile 6 | -------------------------------------------------------------------------------- /terraform/SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /terraform/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | > ⚠️**Note:** For the full details on the support statements, SLAs, and more for the Azure Verified Modules (AVM) initiative please visit [aka.ms/AVM/Support](https://aka.ms/avm/support) ⚠️ 4 | 5 | ## How to file issues and get help 6 | 7 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. 8 | 9 | Issues can be created and searched through for existing [issues here](../../issues). 10 | 11 | Please provide as much information as possible when filing an issue. Include screenshots or correlation IDs if possible (please redact any sensitive information). 12 | 13 | For instructions on how to get deployments and correlation ID, please follow this link [here](https://learn.microsoft.com/azure/azure-resource-manager/templates/deployment-history?tabs=azure-portal#get-deployments-and-correlation-id). 14 | 15 | We may ask you to create an Azure support request once we have triaged the issue following the process documented [here](https://learn.microsoft.com/azure/azure-portal/supportability/how-to-create-azure-support-request). 16 | -------------------------------------------------------------------------------- /terraform/_footer.md: -------------------------------------------------------------------------------- 1 | 2 | ## Data Collection 3 | 4 | The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. 5 | -------------------------------------------------------------------------------- /terraform/_header.md: -------------------------------------------------------------------------------- 1 | # terraform-azurerm-avm-ptn-aiml-landing-zone 2 | 3 | This pattern module creates the full AI/ML landing zone which supports multiple ai project scenarios. 4 | -------------------------------------------------------------------------------- /terraform/avm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | usage () { 6 | echo "Usage: avm " 7 | } 8 | 9 | # We need to do this because bash doesn't like it when a script is updated in place. 10 | if [ -z ${AVM_SCRIPT_FORKED} ]; then 11 | # If AVM_SCRIPT_FORKED is not set, we are running the script from the original repository 12 | # Set AVM_SCRIPT_FORKED to true to avoid running this block again 13 | export AVM_SCRIPT_FORKED=true 14 | 15 | # Make a copy of this script in the current directory 16 | # and run that copy. 17 | cp "$0" .avm 18 | chmod +x .avm 19 | exec ./.avm "$@" 20 | fi 21 | 22 | # Default values for environment variables 23 | CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-"docker"} 24 | CONTAINER_IMAGE=${CONTAINER_IMAGE:-"mcr.microsoft.com/azterraform:avm-latest"} 25 | CONTAINER_PULL_POLICY=${CONTAINER_PULL_POLICY:-"always"} 26 | AVM_MAKEFILE_REF=${AVM_MAKEFILE_REF:-"main"} 27 | AVM_PORCH_REF=${AVM_PORCH_REF:-"main"} 28 | 29 | if [ ! "$(command -v "${CONTAINER_RUNTIME}")" ] && [ -z "${AVM_IN_CONTAINER}" ]; then 30 | echo "Error: ${CONTAINER_RUNTIME} is not installed. Please install ${CONTAINER_RUNTIME} first." 31 | exit 1 32 | fi 33 | 34 | if [ -z "$1" ]; then 35 | echo "Error: Please provide a make target. See https://github.com/Azure/avm-terraform-governance/blob/main/Makefile for available targets." 36 | echo 37 | usage 38 | exit 1 39 | fi 40 | 41 | # Check if AZURE_CONFIG_DIR is set, if not, set it to ~/.azure 42 | if [ -z "${AZURE_CONFIG_DIR}" ]; then 43 | AZURE_CONFIG_DIR="${HOME}/.azure" 44 | fi 45 | 46 | # Check if AZURE_CONFIG_DIR exists, if it does, mount it to the container 47 | if [ -d "${AZURE_CONFIG_DIR}" ]; then 48 | AZURE_CONFIG_MOUNT="-v ${AZURE_CONFIG_DIR}:/home/runtimeuser/.azure" 49 | fi 50 | 51 | # If the host Docker socket exists, mount it into the container so the container can talk to the host docker daemon 52 | if [ -S /var/run/docker.sock ]; then 53 | DOCKER_SOCK_MOUNT="-v /var/run/docker.sock:/var/run/docker.sock" 54 | fi 55 | 56 | # If we are in GitHub Copilot Coding Agent, we need to mount the SSL certificates from the host 57 | SSL_CERT_MOUNTS="" 58 | if [ -n "${COPILOT_AGENT_ACTION}" ]; then 59 | # Mount host's CA bundle to container's expected paths 60 | SSL_CERT_MOUNTS="${SSL_CERT_MOUNTS} -v /etc/ssl/certs/ca-certificates.crt:/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem:ro" 61 | SSL_CERT_MOUNTS="${SSL_CERT_MOUNTS} -v /etc/ssl/certs/ca-certificates.crt:/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt:ro" 62 | fi 63 | 64 | # New: allow overriding TUI behavior with PORCH_FORCE_TUI and PORCH_NO_TUI environment variables. 65 | # - If PORCH_FORCE_TUI is set, force TUI and interactive mode (even in GH Actions). 66 | # - If PORCH_NO_TUI is set, explicitly disable TUI. 67 | # - Otherwise, fallback to previous behavior: enable TUI only when not in GitHub Actions and NO_COLOR is not set. 68 | if [ -n "${PORCH_FORCE_TUI}" ]; then 69 | TUI="--tui" 70 | DOCKER_INTERACTIVE="-it" 71 | export FORCE_COLOR=1 72 | elif [ -n "${PORCH_NO_TUI}" ]; then 73 | # Explicitly disable TUI and interactive flags 74 | TUI="" 75 | DOCKER_INTERACTIVE="" 76 | else 77 | # If we are not in GitHub Actions and NO_COLOR is not set, we want to use TUI and interactive mode 78 | if [ -z "${GITHUB_RUN_ID}" ] && [ -z "${NO_COLOR}" ]; then 79 | TUI="--tui" 80 | DOCKER_INTERACTIVE="-it" 81 | export FORCE_COLOR=1 82 | fi 83 | fi 84 | 85 | # if AVM_PORCH_BASE_URL is set, we want to add it to the make command 86 | if [ -n "${AVM_PORCH_BASE_URL}" ]; then 87 | PORCH_BASE_URL_MAKE_ADD="PORCH_BASE_URL=${AVM_PORCH_BASE_URL}" 88 | fi 89 | 90 | # Check if we are running in a container 91 | # If we are then just run make directly 92 | if [ -z "${AVM_IN_CONTAINER}" ]; then 93 | ${CONTAINER_RUNTIME} run \ 94 | --pull "${CONTAINER_PULL_POLICY}" \ 95 | --user "$(id -u):$(id -g)" \ 96 | --rm \ 97 | ${DOCKER_INTERACTIVE} \ 98 | -v "$(pwd)":/src \ 99 | ${AZURE_CONFIG_MOUNT:-} \ 100 | ${DOCKER_SOCK_MOUNT:-} \ 101 | ${SSL_CERT_MOUNTS:-} \ 102 | -e ARM_CLIENT_ID \ 103 | -e ARM_OIDC_REQUEST_TOKEN \ 104 | -e ARM_OIDC_REQUEST_URL \ 105 | -e ARM_SUBSCRIPTION_ID \ 106 | -e ARM_TENANT_ID \ 107 | -e ARM_USE_OIDC \ 108 | -e FORCE_COLOR \ 109 | -e GITHUB_TOKEN \ 110 | -e NO_COLOR \ 111 | -e PORCH_LOG_LEVEL \ 112 | -e TF_IN_AUTOMATION=1 \ 113 | --env-file <(env | grep '^TF_VAR_') \ 114 | --env-file <(env | grep '^AVM_') \ 115 | "${CONTAINER_IMAGE}" \ 116 | make \ 117 | TUI="${TUI}" \ 118 | AVM_MAKEFILE_REF="${AVM_MAKEFILE_REF}" \ 119 | "${PORCH_BASE_URL_MAKE_ADD}" \ 120 | AVM_PORCH_REF="${AVM_PORCH_REF}" \ 121 | "$1" 122 | else 123 | make TUI="${TUI}" AVM_MAKEFILE_REF="${AVM_MAKEFILE_REF}" ${PORCH_BASE_URL_MAKE_ADD} AVM_PORCH_REF="${AVM_PORCH_REF}" "$1" 124 | fi 125 | -------------------------------------------------------------------------------- /terraform/avm.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | .\avm.ps1 %1 3 | -------------------------------------------------------------------------------- /terraform/examples/.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ### To generate the output file to partially incorporate in the README.md, 2 | ### Execute this command in the Terraform module's code folder: 3 | # terraform-docs -c .terraform-docs.yml . 4 | 5 | formatter: "markdown document" # this is required 6 | 7 | version: "~> 0.18" 8 | 9 | header-from: "_header.md" 10 | footer-from: "_footer.md" 11 | 12 | recursive: 13 | enabled: false 14 | path: modules 15 | 16 | sections: 17 | hide: [] 18 | show: [] 19 | 20 | content: |- 21 | 22 | {{ .Header }} 23 | 24 | ```hcl 25 | {{ include "main.tf" }} 26 | ``` 27 | 28 | 29 | {{ .Requirements }} 30 | 31 | {{ .Resources }} 32 | 33 | 34 | {{ .Inputs }} 35 | 36 | {{ .Outputs }} 37 | 38 | {{ .Modules }} 39 | 40 | {{ .Footer }} 41 | output: 42 | file: README.md 43 | mode: replace 44 | template: |- 45 | 46 | {{ .Content }} 47 | 48 | output-values: 49 | enabled: false 50 | from: "" 51 | 52 | sort: 53 | enabled: true 54 | by: required 55 | 56 | settings: 57 | anchor: true 58 | color: true 59 | default: true 60 | description: false 61 | escape: true 62 | hide-empty: false 63 | html: true 64 | indent: 2 65 | lockfile: false 66 | read-comments: true 67 | required: true 68 | sensitive: true 69 | type: true 70 | -------------------------------------------------------------------------------- /terraform/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | - Create a directory for each example. 4 | - Create a `_header.md` file in each directory to describe the example. 5 | - See the `default` example provided as a skeleton - this must remain, but you can add others. 6 | - Run `make fmt && make docs` from the repo root to generate the required documentation. 7 | - If you want an example to be ignored by the end to end pipeline add a `.e2eignore` file to the example directory. 8 | 9 | > **Note:** Examples must be deployable and idempotent. Ensure that no input variables are required to run the example and that random values are used to ensure unique resource names. E.g. use the [naming module](https://registry.terraform.io/modules/Azure/naming/azurerm/latest) to generate a unique name for a resource. 10 | -------------------------------------------------------------------------------- /terraform/examples/default/_footer.md: -------------------------------------------------------------------------------- 1 | 2 | ## Data Collection 3 | 4 | The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. 5 | -------------------------------------------------------------------------------- /terraform/examples/default/_header.md: -------------------------------------------------------------------------------- 1 | # Default example 2 | 3 | This example deploys the version of the module with the platform landing zone flag set to true. In this configuration, the assumption is that a hub Vnet hosting DNS has been provided and that the landing zone will attach to a hub Vnet for all the standard network services. (DNS, Hybrid Connectivity, Firewalls, and etc.) 4 | -------------------------------------------------------------------------------- /terraform/examples/default/exceptions/avmsec.rego: -------------------------------------------------------------------------------- 1 | package avmsec 2 | import rego.v1 3 | exception contains rules if { 4 | rules = ["AVM_SEC_178"] 5 | } 6 | -------------------------------------------------------------------------------- /terraform/examples/default/variables.tf: -------------------------------------------------------------------------------- 1 | variable "enable_telemetry" { 2 | type = bool 3 | default = true 4 | description = <. 7 | If it is set to false, then no telemetry will be collected. 8 | DESCRIPTION 9 | } 10 | -------------------------------------------------------------------------------- /terraform/examples/standalone/_footer.md: -------------------------------------------------------------------------------- 1 | 2 | ## Data Collection 3 | 4 | The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. 5 | -------------------------------------------------------------------------------- /terraform/examples/standalone/_header.md: -------------------------------------------------------------------------------- 1 | # Standalone example 2 | 3 | This example demonstrates a configuration when the platform landing zone flag is set to false. In this case, all supporting services are included as part of AI landing zone deployment. 4 | -------------------------------------------------------------------------------- /terraform/examples/standalone/exceptions/avmsec.rego: -------------------------------------------------------------------------------- 1 | package avmsec 2 | import rego.v1 3 | exception contains rules if { 4 | rules = ["AVM_SEC_178"] 5 | } 6 | -------------------------------------------------------------------------------- /terraform/examples/standalone/variables.tf: -------------------------------------------------------------------------------- 1 | variable "enable_telemetry" { 2 | type = bool 3 | default = true 4 | description = <. 7 | If it is set to false, then no telemetry will be collected. 8 | DESCRIPTION 9 | } 10 | -------------------------------------------------------------------------------- /terraform/locals.apim.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | apim_default_role_assignments = {} 3 | apim_name = try(var.apim_definition.name, null) != null ? var.apim_definition.name : (var.name_prefix != null ? "${var.name_prefix}-apim-${random_string.name_suffix.result}" : "ai-alz-apim-${random_string.name_suffix.result}") 4 | apim_role_assignments = merge( 5 | local.apim_default_role_assignments, 6 | try(var.apim_definition.role_assignments, {}) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /terraform/locals.build.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | build_vm_name = try(var.buildvm_definition.name, null) != null ? var.buildvm_definition.name : (var.name_prefix != null ? "${var.name_prefix}-build" : "ai-alz-buildvm") 3 | } 4 | -------------------------------------------------------------------------------- /terraform/locals.compute.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | cae_log_analytics_workspace_resource_id = var.container_app_environment_definition.log_analytics_workspace_resource_id != null ? var.container_app_environment_definition.log_analytics_workspace_resource_id : module.log_analytics_workspace[0].resource_id 3 | container_app_environment_default_role_assignments = {} 4 | container_app_environment_name = try(var.container_app_environment_definition.name, null) != null ? var.container_app_environment_definition.name : (var.name_prefix != null ? "${var.name_prefix}-container-app-env" : "ai-alz-container-app-env-${random_string.name_suffix.result}") 5 | container_app_environment_role_assignments = merge( 6 | local.container_app_environment_default_role_assignments, 7 | var.container_app_environment_definition.role_assignments 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /terraform/locals.foundry.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | ai_foundry_name = try(var.ai_foundry_definition.name, null) != null ? var.ai_foundry_definition.name : (var.name_prefix != null ? "${var.name_prefix}-ai-foundry-${random_string.name_suffix.result}" : "ai-foundry-${random_string.name_suffix.result}") 3 | foundry_ai_foundry = merge( 4 | var.ai_foundry_definition.ai_foundry, { 5 | name = local.ai_foundry_name 6 | network_injections = [{ 7 | scenario = "agent" 8 | subnetArmId = module.ai_lz_vnet.subnets["AIFoundrySubnet"].resource_id 9 | useMicrosoftManagedNetwork = false 10 | }] 11 | private_dns_zone_resource_ids = [ 12 | (var.flag_platform_landing_zone ? module.private_dns_zones.ai_foundry_openai_zone.resource_id : local.private_dns_zones_existing.ai_foundry_openai_zone.resource_id), 13 | (var.flag_platform_landing_zone ? module.private_dns_zones.ai_foundry_ai_services_zone.resource_id : local.private_dns_zones_existing.ai_foundry_ai_services_zone.resource_id), 14 | (var.flag_platform_landing_zone ? module.private_dns_zones.ai_foundry_cognitive_services_zone.resource_id : local.private_dns_zones_existing.ai_foundry_cognitive_services_zone.resource_id) 15 | ] 16 | } 17 | ) 18 | foundry_ai_search_definition = { for key, value in var.ai_foundry_definition.ai_search_definition : key => merge( 19 | var.ai_foundry_definition.ai_search_definition[key], { 20 | private_dns_zone_resource_id = var.flag_platform_landing_zone ? module.private_dns_zones.ai_search_zone.resource_id : local.private_dns_zones_existing.ai_search_zone.resource_id 21 | } 22 | ) } 23 | foundry_cosmosdb_definition = { for key, value in var.ai_foundry_definition.cosmosdb_definition : key => merge( 24 | var.ai_foundry_definition.cosmosdb_definition[key], { 25 | private_dns_zone_resource_id = var.flag_platform_landing_zone ? module.private_dns_zones.cosmos_sql_zone.resource_id : local.private_dns_zones_existing.cosmos_sql_zone.resource_id 26 | } 27 | ) } 28 | foundry_key_vault_definition = { for key, value in var.ai_foundry_definition.key_vault_definition : key => merge( 29 | var.ai_foundry_definition.key_vault_definition[key], { 30 | private_dns_zone_resource_id = var.flag_platform_landing_zone ? module.private_dns_zones.key_vault_zone.resource_id : local.private_dns_zones_existing.key_vault_zone.resource_id 31 | } 32 | ) } 33 | foundry_storage_account_definition = { for key, value in var.ai_foundry_definition.storage_account_definition : key => merge( 34 | var.ai_foundry_definition.storage_account_definition[key], { 35 | endpoints = { 36 | for ek, ev in value.endpoints : 37 | ek => { 38 | private_dns_zone_resource_id = var.flag_platform_landing_zone ? module.private_dns_zones["storage_${lower(ek)}_zone"].resource_id : local.private_dns_zones_existing["storage_${lower(ek)}_zone"].resource_id 39 | type = lower(ek) 40 | } 41 | } 42 | } 43 | ) } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /terraform/locals.genai_services.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | genai_app_configuration_default_role_assignments = {} 3 | genai_app_configuration_name = try(var.genai_app_configuration_definition.name, null) != null ? var.genai_app_configuration_definition.name : (var.name_prefix != null ? "${var.name_prefix}-genai-appconfig-${random_string.name_suffix.result}" : "genai-appconfig-${random_string.name_suffix.result}") 4 | genai_app_configuration_role_assignments = merge( 5 | local.genai_app_configuration_default_role_assignments, 6 | var.genai_app_configuration_definition.role_assignments 7 | ) 8 | genai_container_registry_default_role_assignments = {} 9 | genai_container_registry_name = try(var.genai_container_registry_definition.name, null) != null ? var.genai_container_registry_definition.name : (var.name_prefix != null ? "${var.name_prefix}genaicr${random_string.name_suffix.result}" : "genaicr${random_string.name_suffix.result}") 10 | genai_container_registry_role_assignments = merge( 11 | local.genai_container_registry_default_role_assignments, 12 | var.genai_container_registry_definition.role_assignments 13 | ) 14 | genai_cosmosdb_name = try(var.genai_cosmosdb_definition.name, null) != null ? var.genai_cosmosdb_definition.name : (var.name_prefix != null ? "${var.name_prefix}-genai-cosmosdb-${random_string.name_suffix.result}" : "genai-cosmosdb-${random_string.name_suffix.result}") 15 | # Handle secondary regions logic: 16 | # - If null, set to empty list 17 | # - If empty list, set to paired region details(default?) 18 | # - Otherwise, use the provided list 19 | genai_cosmosdb_secondary_regions = var.genai_cosmosdb_definition.secondary_regions == null ? [] : ( 20 | try(length(var.genai_cosmosdb_definition.secondary_regions) == 0, false) ? [ 21 | { 22 | location = local.paired_region 23 | zone_redundant = false #length(local.paired_region_zones) > 1 ? true : false TODO: set this back to dynamic based on region zone availability after testing. Our subs don't have quota for zonal deployments. 24 | failover_priority = 1 25 | }, 26 | { 27 | location = azurerm_resource_group.this.location 28 | zone_redundant = false #length(local.region_zones) > 1 ? true : false 29 | failover_priority = 0 30 | } 31 | ] : var.genai_cosmosdb_definition.secondary_regions 32 | ) 33 | genai_key_vault_default_role_assignments = { 34 | } 35 | genai_key_vault_name = try(var.genai_key_vault_definition.name, null) != null ? var.genai_key_vault_definition.name : (var.name_prefix != null ? "${var.name_prefix}-genai-kv-${random_string.name_suffix.result}" : "genai-kv-${random_string.name_suffix.result}") 36 | genai_key_vault_role_assignments = merge( 37 | local.genai_key_vault_default_role_assignments, 38 | var.genai_key_vault_definition.role_assignments 39 | ) 40 | genai_storage_account_default_role_assignments = { 41 | } 42 | genai_storage_account_name = try(var.genai_storage_account_definition.name, null) != null ? var.genai_storage_account_definition.name : (var.name_prefix != null ? "${var.name_prefix}genaisa${random_string.name_suffix.result}" : "genaisa${random_string.name_suffix.result}") 43 | genai_storage_account_role_assignments = merge( 44 | local.genai_storage_account_default_role_assignments, 45 | var.genai_storage_account_definition.role_assignments 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /terraform/locals.jumpvm.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | jump_vm_name = try(var.jumpvm_definition.name, null) != null ? var.jumpvm_definition.name : (var.name_prefix != null ? "${var.name_prefix}-jump" : "ai-alz-jumpvm") 3 | } 4 | -------------------------------------------------------------------------------- /terraform/locals.knowledge_sources.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | ks_ai_search_name = try(var.ks_ai_search_definition.name, null) != null ? var.ks_ai_search_definition.name : (var.name_prefix != null ? "${var.name_prefix}-ks-ai-search" : "ai-alz-ks-ai-search-${random_string.name_suffix.result}") 3 | ks_bing_grounding_name = try(var.ks_bing_grounding_definition.name, null) != null ? var.ks_bing_grounding_definition.name : (var.name_prefix != null ? "${var.name_prefix}-ks-bing-grounding" : "ai-alz-ks-bing-grounding-${random_string.name_suffix.result}") 4 | } 5 | -------------------------------------------------------------------------------- /terraform/locals.monitoring.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | log_analytics_workspace_name = try(var.law_definition.name, null) != null ? var.law_definition.name : (var.name_prefix != null ? "${var.name_prefix}-law" : "ai-alz-law") 3 | } 4 | 5 | -------------------------------------------------------------------------------- /terraform/locals.networking.firewallpolicy.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | default_outbound_network_ruleset = [ 3 | { 4 | name = "OutboundToInternet" 5 | description = "Allow traffic outbound to the Internet" 6 | destination_addresses = ["0.0.0.0/0"] 7 | destination_ports = ["443", "80"] 8 | source_addresses = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] 9 | protocols = ["TCP", "UDP"] 10 | } 11 | ] 12 | firewall_policy_network_ruleset = concat(local.default_outbound_network_ruleset, try(var.firewall_policy_definition.network_ruleset, [])) 13 | firewall_policy_rule_collection_group_name = var.firewall_policy_definition.network_policy_rule_collection_group_name != null ? var.firewall_policy_definition.network_policy_rule_collection_group_name : "NetworkRuleCollectionGroup" 14 | firewall_policy_rule_collection_group_network_rule_collection = [ 15 | { 16 | action = "Allow" 17 | name = local.firewall_policy_rule_collection_group_name 18 | priority = local.firewall_policy_rule_collection_group_priority 19 | rule = local.firewall_policy_network_ruleset 20 | } 21 | ] 22 | firewall_policy_rule_collection_group_priority = var.firewall_policy_definition.network_policy_rule_collection_group_priority != null ? var.firewall_policy_definition.network_policy_rule_collection_group_priority : 400 23 | } 24 | -------------------------------------------------------------------------------- /terraform/locals.networking.nsgs.tf: -------------------------------------------------------------------------------- 1 | #TODO: Come up with a standard set of NSG rules for the AI ALZ. This is a starting point. 2 | locals { 3 | base_nsg_rules = { 4 | "rule01" = { 5 | name = "Allow-RFC-1918-Any" 6 | access = "Allow" 7 | destination_address_prefixes = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] 8 | destination_port_range = "*" 9 | direction = "Outbound" 10 | priority = 100 11 | protocol = "*" 12 | source_address_prefixes = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] 13 | source_port_range = "*" 14 | } 15 | "appgw_rule01" = { 16 | name = "Allow-AppGW_Management" 17 | access = "Allow" 18 | destination_address_prefixes = try(var.vnet_definition.subnets["AppGatewaySubnet"].address_prefix, null) != null ? [var.vnet_definition.subnets["AppGatewaySubnet"].address_prefix] : [cidrsubnet(var.vnet_definition.address_space, 4, 5)] 19 | destination_port_range = "65200-65535" 20 | direction = "Inbound" 21 | priority = 110 22 | protocol = "*" 23 | source_address_prefix = "Internet" 24 | source_port_range = "*" 25 | } 26 | "appgw_rule02" = { 27 | name = "Allow-AppGW_Web" 28 | access = "Allow" 29 | destination_address_prefixes = try(var.vnet_definition.subnets["AppGatewaySubnet"].address_prefix, null) != null ? [var.vnet_definition.subnets["AppGatewaySubnet"].address_prefix] : [cidrsubnet(var.vnet_definition.address_space, 4, 5)] 30 | destination_port_ranges = ["80", "443"] 31 | direction = "Inbound" 32 | priority = 120 33 | protocol = "Tcp" 34 | source_address_prefix = "*" 35 | source_port_range = "*" 36 | } 37 | "appgw_rule03" = { 38 | name = "Allow-AppGW_LoadBalancer" 39 | access = "Allow" 40 | destination_address_prefixes = try(var.vnet_definition.subnets["AppGatewaySubnet"].address_prefix, null) != null ? [var.vnet_definition.subnets["AppGatewaySubnet"].address_prefix] : [cidrsubnet(var.vnet_definition.address_space, 4, 5)] 41 | destination_port_range = "*" 42 | direction = "Inbound" 43 | priority = 4000 44 | protocol = "*" 45 | source_address_prefix = "AzureLoadBalancer" 46 | source_port_range = "*" 47 | } 48 | 49 | } 50 | nsg_name = try(var.nsgs_definition.name, null) != null ? var.nsgs_definition.name : (var.name_prefix != null ? "${var.name_prefix}-ai-alz-nsg" : "ai-alz-nsg") 51 | nsg_rules = merge( 52 | local.base_nsg_rules, 53 | var.nsgs_definition.security_rules 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /terraform/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | paired_region = [for region in module.avm_utl_regions.regions : region if(lower(region.name) == lower(azurerm_resource_group.this.location) || (lower(region.display_name) == lower(azurerm_resource_group.this.location)))][0].paired_region_name 3 | #paired_region_zones = local.paired_region_zones_lookup != null ? local.paired_region_zones_lookup : [] 4 | #paired_region_zones_lookup = [for region in module.avm_utl_regions.regions : region if(lower(region.name) == lower(local.paired_region) || (lower(region.display_name) == lower(local.paired_region)))][0].zones 5 | region_zones = local.region_zones_lookup != null ? local.region_zones_lookup : [] 6 | region_zones_lookup = [for region in module.avm_utl_regions.regions : region if(lower(region.name) == lower(azurerm_resource_group.this.location) || (lower(region.display_name) == lower(azurerm_resource_group.this.location)))][0].zones 7 | } 8 | -------------------------------------------------------------------------------- /terraform/main.apim.tf: -------------------------------------------------------------------------------- 1 | 2 | 3 | module "apim" { 4 | source = "Azure/avm-res-apimanagement-service/azurerm" 5 | version = "0.0.5" 6 | count = var.apim_definition.deploy ? 1 : 0 7 | 8 | location = azurerm_resource_group.this.location 9 | name = local.apim_name 10 | publisher_email = var.apim_definition.publisher_email 11 | resource_group_name = azurerm_resource_group.this.name 12 | additional_location = var.apim_definition.additional_locations 13 | certificate = var.apim_definition.certificate 14 | client_certificate_enabled = var.apim_definition.client_certificate_enabled 15 | diagnostic_settings = { 16 | storage = { 17 | name = "sendToLogAnalytics-apim-${random_string.name_suffix.result}" 18 | workspace_resource_id = var.law_definition.resource_id != null ? var.law_definition.resource_id : module.log_analytics_workspace[0].resource_id 19 | } 20 | } 21 | enable_telemetry = var.enable_telemetry 22 | hostname_configuration = var.apim_definition.hostname_configuration 23 | min_api_version = var.apim_definition.min_api_version 24 | notification_sender_email = var.apim_definition.notification_sender_email 25 | private_endpoints = { 26 | endpoint1 = { 27 | private_dns_zone_resource_ids = var.flag_platform_landing_zone ? [module.private_dns_zones.apim_zone.resource_id] : [local.private_dns_zones_existing.apim_zone.resource_id] 28 | subnet_resource_id = module.ai_lz_vnet.subnets["PrivateEndpointSubnet"].resource_id 29 | } 30 | } 31 | protocols = var.apim_definition.protocols 32 | public_network_access_enabled = true 33 | publisher_name = var.apim_definition.publisher_name 34 | role_assignments = local.apim_role_assignments 35 | sign_in = var.apim_definition.sign_in 36 | sign_up = var.apim_definition.sign_up 37 | sku_name = "${var.apim_definition.sku_root}_${var.apim_definition.sku_capacity}" 38 | tags = var.apim_definition.tags 39 | tenant_access = var.apim_definition.tenant_access 40 | virtual_network_subnet_id = null 41 | virtual_network_type = "None" 42 | zones = local.region_zones 43 | } 44 | 45 | -------------------------------------------------------------------------------- /terraform/main.build.tf: -------------------------------------------------------------------------------- 1 | module "buildvm" { 2 | source = "Azure/avm-res-compute-virtualmachine/azurerm" 3 | version = "0.19.3" 4 | count = var.flag_platform_landing_zone && var.buildvm_definition.deploy ? 1 : 0 5 | 6 | location = azurerm_resource_group.this.location 7 | name = local.build_vm_name 8 | network_interfaces = { 9 | network_interface_1 = { 10 | name = "${local.build_vm_name}-nic1" 11 | ip_configurations = { 12 | ip_configuration_1 = { 13 | name = "${local.build_vm_name}-nic1-ipconfig1" 14 | private_ip_subnet_resource_id = module.ai_lz_vnet.subnets["DevOpsBuildSubnet"].resource_id 15 | } 16 | } 17 | } 18 | } 19 | resource_group_name = azurerm_resource_group.this.name 20 | zone = length(local.region_zones) > 0 ? random_integer.zone_index[0].result : null 21 | account_credentials = { 22 | key_vault_configuration = { 23 | resource_id = module.avm_res_keyvault_vault.resource_id 24 | secret_configuration = { 25 | name = "azureuser-password" 26 | } 27 | } 28 | password_authentication_disabled = false 29 | } 30 | enable_telemetry = var.enable_telemetry 31 | managed_identities = { 32 | system_assigned = true 33 | } 34 | os_type = "Linux" 35 | role_assignments_system_managed_identity = { 36 | rg_owner = { 37 | scope_resource_id = azurerm_resource_group.this.id 38 | role_definition_id_or_name = "Owner" 39 | description = "Assign the owner role to the build machine's system assigned identity on the resource group." 40 | } 41 | } 42 | sku_size = var.buildvm_definition.sku 43 | source_image_reference = { #TODO: Determine if we want to provide flexibility for the VM sku type being created 44 | publisher = "Canonical" 45 | offer = "0001-com-ubuntu-server-focal" 46 | sku = "20_04-lts-gen2" 47 | version = "latest" 48 | } 49 | tags = var.buildvm_definition.tags 50 | } 51 | 52 | -------------------------------------------------------------------------------- /terraform/main.compute.tf: -------------------------------------------------------------------------------- 1 | module "container_apps_managed_environment" { 2 | source = "Azure/avm-res-app-managedenvironment/azurerm" 3 | version = "0.3.0" 4 | count = var.container_app_environment_definition.deploy ? 1 : 0 5 | 6 | location = azurerm_resource_group.this.location 7 | name = local.container_app_environment_name 8 | resource_group_name = azurerm_resource_group.this.name 9 | diagnostic_settings = var.container_app_environment_definition.enable_diagnostic_settings ? { 10 | to_law = { 11 | name = "sendToLogAnalytics-cae-${random_string.name_suffix.result}" 12 | workspace_resource_id = var.law_definition.resource_id != null ? var.law_definition.resource_id : module.log_analytics_workspace[0].resource_id 13 | log_analytics_destination_type = "AzureDiagnostics" 14 | } 15 | } : {} 16 | enable_telemetry = var.enable_telemetry 17 | infrastructure_resource_group_name = "rg-managed-${azurerm_resource_group.this.name}" 18 | infrastructure_subnet_id = module.ai_lz_vnet.subnets["ContainerAppEnvironmentSubnet"].resource_id 19 | internal_load_balancer_enabled = var.container_app_environment_definition.internal_load_balancer_enabled 20 | log_analytics_workspace = { 21 | resource_id = local.cae_log_analytics_workspace_resource_id 22 | } 23 | managed_identities = { 24 | system_assigned = true 25 | user_assigned_resource_ids = var.container_app_environment_definition.user_assigned_managed_identity_ids 26 | } 27 | role_assignments = local.container_app_environment_role_assignments 28 | tags = var.container_app_environment_definition.tags 29 | workload_profile = var.container_app_environment_definition.workload_profile 30 | zone_redundancy_enabled = length(local.region_zones) > 1 ? var.container_app_environment_definition.zone_redundancy_enabled : false 31 | } 32 | -------------------------------------------------------------------------------- /terraform/main.foundry.tf: -------------------------------------------------------------------------------- 1 | module "foundry_ptn" { 2 | source = "Azure/avm-ptn-aiml-ai-foundry/azurerm" 3 | version = "0.6.0" 4 | 5 | #configure the base resource 6 | base_name = coalesce(var.name_prefix, "foundry") 7 | location = azurerm_resource_group.this.location 8 | resource_group_resource_id = azurerm_resource_group.this.id 9 | #pass through the resource definitions 10 | ai_foundry = local.foundry_ai_foundry 11 | ai_model_deployments = var.ai_foundry_definition.ai_model_deployments 12 | ai_projects = var.ai_foundry_definition.ai_projects 13 | ai_search_definition = local.foundry_ai_search_definition 14 | cosmosdb_definition = local.foundry_cosmosdb_definition 15 | create_byor = var.ai_foundry_definition.create_byor 16 | create_private_endpoints = true 17 | enable_telemetry = var.enable_telemetry 18 | key_vault_definition = local.foundry_key_vault_definition 19 | law_definition = var.ai_foundry_definition.law_definition 20 | private_endpoint_subnet_resource_id = module.ai_lz_vnet.subnets["PrivateEndpointSubnet"].resource_id 21 | storage_account_definition = local.foundry_storage_account_definition 22 | 23 | depends_on = [azapi_resource_action.purge_ai_foundry] 24 | } 25 | 26 | resource "azapi_resource_action" "purge_ai_foundry" { 27 | count = var.ai_foundry_definition.purge_on_destroy ? 1 : 0 28 | 29 | method = "DELETE" 30 | resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.CognitiveServices/locations/${azurerm_resource_group.this.location}/resourceGroups/${azurerm_resource_group.this.name}/deletedAccounts/${local.ai_foundry_name}" 31 | type = "Microsoft.Resources/resourceGroups/deletedAccounts@2021-04-30" 32 | when = "destroy" 33 | 34 | depends_on = [time_sleep.purge_ai_foundry_cooldown] 35 | } 36 | 37 | resource "time_sleep" "purge_ai_foundry_cooldown" { 38 | count = var.ai_foundry_definition.purge_on_destroy ? 1 : 0 39 | 40 | destroy_duration = "900s" # 10m 41 | 42 | depends_on = [module.ai_lz_vnet] 43 | } 44 | -------------------------------------------------------------------------------- /terraform/main.genai_app_resources.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/AI-Landing-Zones/794d40870ba9c7c153feff9c8572c9d0f2f37505/terraform/main.genai_app_resources.tf -------------------------------------------------------------------------------- /terraform/main.jumpvm.tf: -------------------------------------------------------------------------------- 1 | resource "random_integer" "zone_index" { 2 | count = length(local.region_zones) > 0 ? 1 : 0 3 | 4 | max = length(local.region_zones) 5 | min = 1 6 | } 7 | 8 | module "jumpvm" { 9 | source = "Azure/avm-res-compute-virtualmachine/azurerm" 10 | version = "0.19.3" 11 | count = var.flag_platform_landing_zone && var.jumpvm_definition.deploy ? 1 : 0 12 | 13 | location = azurerm_resource_group.this.location 14 | name = local.jump_vm_name 15 | network_interfaces = { 16 | network_interface_1 = { 17 | name = "${local.jump_vm_name}-nic1" 18 | ip_configurations = { 19 | ip_configuration_1 = { 20 | name = "${local.jump_vm_name}-nic1-ipconfig1" 21 | private_ip_subnet_resource_id = module.ai_lz_vnet.subnets["JumpboxSubnet"].resource_id 22 | } 23 | } 24 | } 25 | } 26 | resource_group_name = azurerm_resource_group.this.name 27 | zone = length(local.region_zones) > 0 ? random_integer.zone_index[0].result : null 28 | account_credentials = { 29 | key_vault_configuration = { 30 | resource_id = module.avm_res_keyvault_vault.resource_id 31 | } 32 | } 33 | enable_telemetry = var.enable_telemetry 34 | sku_size = var.jumpvm_definition.sku 35 | tags = var.jumpvm_definition.tags 36 | 37 | depends_on = [module.avm_res_keyvault_vault, azurerm_role_assignment.deployment_user_kv_admin] 38 | } 39 | 40 | 41 | #TODO 42 | # feature toggle if not required 43 | # credential to vault (ordering issues) 44 | ## Move the private endpoint for the vault outside the avm module ? 45 | # Consider adding options for different OS versions 46 | 47 | 48 | -------------------------------------------------------------------------------- /terraform/main.knowledge_sources.tf: -------------------------------------------------------------------------------- 1 | module "search_service" { 2 | source = "Azure/avm-res-search-searchservice/azurerm" 3 | version = "0.1.5" 4 | count = var.ks_ai_search_definition.deploy ? 1 : 0 5 | 6 | location = azurerm_resource_group.this.location 7 | name = local.ks_ai_search_name 8 | resource_group_name = azurerm_resource_group.this.name 9 | diagnostic_settings = var.ks_ai_search_definition.enable_diagnostic_settings ? { 10 | search = { 11 | name = "sendToLogAnalytics-search-${random_string.name_suffix.result}" 12 | workspace_resource_id = var.law_definition.resource_id != null ? var.law_definition.resource_id : module.log_analytics_workspace[0].resource_id 13 | } 14 | } : {} 15 | enable_telemetry = var.enable_telemetry # see variables.tf 16 | local_authentication_enabled = var.ks_ai_search_definition.local_authentication_enabled 17 | partition_count = var.ks_ai_search_definition.partition_count 18 | private_endpoints = { 19 | primary = { 20 | private_dns_zone_resource_ids = var.flag_platform_landing_zone ? [module.private_dns_zones.ai_search_zone.resource_id] : [local.private_dns_zones_existing.ai_search_zone.resource_id] 21 | subnet_resource_id = module.ai_lz_vnet.subnets["PrivateEndpointSubnet"].resource_id 22 | } 23 | } 24 | public_network_access_enabled = var.ks_ai_search_definition.public_network_access_enabled 25 | replica_count = var.ks_ai_search_definition.replica_count 26 | semantic_search_sku = var.ks_ai_search_definition.semantic_search_sku 27 | sku = var.ks_ai_search_definition.sku 28 | 29 | depends_on = [module.private_dns_zones, module.hub_vnet_peering] 30 | } 31 | 32 | resource "azapi_resource" "bing_grounding" { 33 | count = var.ks_bing_grounding_definition.deploy ? 1 : 0 34 | 35 | location = "global" 36 | name = local.ks_bing_grounding_name 37 | parent_id = azurerm_resource_group.this.id 38 | type = "Microsoft.Bing/accounts@2025-05-01-preview" 39 | body = { 40 | kind = "Bing.Grounding" 41 | sku = { 42 | name = var.ks_bing_grounding_definition.sku 43 | } 44 | } 45 | create_headers = var.enable_telemetry ? { "User-Agent" : local.avm_azapi_header } : null 46 | delete_headers = var.enable_telemetry ? { "User-Agent" : local.avm_azapi_header } : null 47 | read_headers = var.enable_telemetry ? { "User-Agent" : local.avm_azapi_header } : null 48 | schema_validation_enabled = false 49 | tags = var.ks_bing_grounding_definition.tags 50 | update_headers = var.enable_telemetry ? { "User-Agent" : local.avm_azapi_header } : null 51 | } 52 | -------------------------------------------------------------------------------- /terraform/main.monitoring.tf: -------------------------------------------------------------------------------- 1 | module "log_analytics_workspace" { 2 | source = "Azure/avm-res-operationalinsights-workspace/azurerm" 3 | version = "0.4.2" 4 | count = var.law_definition.resource_id == null ? 1 : 0 5 | 6 | location = azurerm_resource_group.this.location 7 | name = local.log_analytics_workspace_name 8 | resource_group_name = azurerm_resource_group.this.name 9 | enable_telemetry = var.enable_telemetry 10 | log_analytics_workspace_retention_in_days = var.law_definition.retention 11 | log_analytics_workspace_sku = var.law_definition.sku 12 | } 13 | -------------------------------------------------------------------------------- /terraform/main.telemetry.tf: -------------------------------------------------------------------------------- 1 | data "azapi_client_config" "telemetry" { 2 | count = var.enable_telemetry ? 1 : 0 3 | } 4 | 5 | data "modtm_module_source" "telemetry" { 6 | count = var.enable_telemetry ? 1 : 0 7 | 8 | module_path = path.module 9 | } 10 | 11 | locals { 12 | main_location = var.location 13 | } 14 | 15 | resource "random_uuid" "telemetry" { 16 | count = var.enable_telemetry ? 1 : 0 17 | } 18 | 19 | resource "modtm_telemetry" "telemetry" { 20 | count = var.enable_telemetry ? 1 : 0 21 | 22 | tags = merge({ 23 | subscription_id = one(data.azapi_client_config.telemetry).subscription_id 24 | tenant_id = one(data.azapi_client_config.telemetry).tenant_id 25 | module_source = one(data.modtm_module_source.telemetry).module_source 26 | module_version = one(data.modtm_module_source.telemetry).module_version 27 | random_id = one(random_uuid.telemetry).result 28 | }, { location = local.main_location }) 29 | } 30 | locals { 31 | fork_avm = !anytrue([for r in local.valid_module_source_regex : can(regex(r, one(data.modtm_module_source.telemetry).module_source))]) 32 | } 33 | 34 | locals { 35 | valid_module_source_regex = [ 36 | "registry.terraform.io/[A|a]zure/.+", 37 | "registry.opentofu.io/[A|a]zure/.+", 38 | "git::https://github\\.com/[A|a]zure/.+", 39 | "git::ssh:://git@github\\.com/[A|a]zure/.+", 40 | ] 41 | } 42 | 43 | locals { 44 | avm_azapi_headers = !var.enable_telemetry ? {} : (local.fork_avm ? { 45 | fork_avm = "true" 46 | random_id = one(random_uuid.telemetry).result 47 | } : { 48 | avm = "true" 49 | random_id = one(random_uuid.telemetry).result 50 | avm_module_source = one(data.modtm_module_source.telemetry).module_source 51 | avm_module_version = one(data.modtm_module_source.telemetry).module_version 52 | }) 53 | } 54 | 55 | locals { 56 | # tflint-ignore: terraform_unused_declarations 57 | avm_azapi_header = join(" ", [for k, v in local.avm_azapi_headers : "${k}=${v}"]) 58 | } 59 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "this" { 2 | location = var.location 3 | name = var.resource_group_name 4 | tags = var.tags 5 | } 6 | 7 | # used to randomize resource names that are globally unique 8 | resource "random_string" "name_suffix" { 9 | length = 4 10 | special = false 11 | upper = false 12 | } 13 | 14 | data "azurerm_client_config" "current" {} 15 | 16 | module "avm_utl_regions" { 17 | source = "Azure/avm-utl-regions/azurerm" 18 | version = "0.5.2" 19 | 20 | recommended_filter = false 21 | } 22 | 23 | -------------------------------------------------------------------------------- /terraform/modules/.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ### To generate the output file to partially incorporate in the README.md, 2 | ### Execute this command in the Terraform module's code folder: 3 | # terraform-docs -c .terraform-docs.yml . 4 | 5 | formatter: "markdown document" # this is required 6 | 7 | version: "~> 0.18" 8 | 9 | header-from: "_header.md" 10 | footer-from: "_footer.md" 11 | 12 | recursive: 13 | enabled: false 14 | path: modules 15 | 16 | sections: 17 | hide: [] 18 | show: [] 19 | 20 | content: |- 21 | 22 | {{ .Header }} 23 | 24 | 25 | {{ .Requirements }} 26 | 27 | {{ .Resources }} 28 | 29 | 30 | {{ .Inputs }} 31 | 32 | {{ .Outputs }} 33 | 34 | {{ .Modules }} 35 | 36 | {{ .Footer }} 37 | 38 | output: 39 | file: README.md 40 | mode: replace 41 | template: |- 42 | 43 | {{ .Content }} 44 | 45 | output-values: 46 | enabled: false 47 | from: "" 48 | 49 | sort: 50 | enabled: true 51 | by: required 52 | 53 | settings: 54 | anchor: true 55 | color: true 56 | default: true 57 | description: false 58 | escape: true 59 | hide-empty: false 60 | html: true 61 | indent: 2 62 | lockfile: false 63 | read-comments: true 64 | required: true 65 | sensitive: true 66 | type: true 67 | -------------------------------------------------------------------------------- /terraform/modules/README.md: -------------------------------------------------------------------------------- 1 | # Sub-modules 2 | 3 | Create directories for each sub-module if required. 4 | README.md files will be automatically generated for each sub-module using `terraform-docs`. 5 | -------------------------------------------------------------------------------- /terraform/modules/example_hub_vnet/_footer.md: -------------------------------------------------------------------------------- 1 | 2 | ## Data Collection 3 | 4 | The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. 5 | -------------------------------------------------------------------------------- /terraform/modules/example_hub_vnet/_header.md: -------------------------------------------------------------------------------- 1 | # example hub vnet for testing 2 | 3 | This sub module creates a basic hub for use in testing the default configuration for the landing zone where the created AI LZA vnet will attach to a hub vnet. 4 | -------------------------------------------------------------------------------- /terraform/modules/example_hub_vnet/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | bastion_name = var.name_prefix != null ? "${var.name_prefix}-example-bastion" : "ai-alz-example-bastion" 3 | default_outbound_network_ruleset = [ 4 | { 5 | name = "OutboundToInternet" 6 | description = "Allow traffic outbound to the Internet" 7 | destination_addresses = ["0.0.0.0/0"] 8 | destination_ports = ["443", "80"] 9 | source_addresses = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] 10 | protocols = ["TCP", "UDP"] 11 | } 12 | ] 13 | deployed_subnets = { for subnet_name, subnet in local.subnets : subnet_name => subnet if subnet.enabled } 14 | firewall_name = var.name_prefix != null ? "${var.name_prefix}-example-fw" : "ai-alz-example-fw" 15 | firewall_policy_network_ruleset = local.default_outbound_network_ruleset 16 | firewall_policy_rule_collection_group_name = "NetworkRuleCollectionGroup" 17 | firewall_policy_rule_collection_group_network_rule_collection = [ 18 | { 19 | action = "Allow" 20 | name = local.firewall_policy_rule_collection_group_name 21 | priority = local.firewall_policy_rule_collection_group_priority 22 | rule = local.firewall_policy_network_ruleset 23 | } 24 | ] 25 | firewall_policy_rule_collection_group_priority = 400 26 | jump_vm_name = "ai-alz-jumpvm" 27 | kv_name = var.name_prefix != null ? "${var.name_prefix}-kv-${random_string.name_suffix.result}" : "ai-alz-keyvault-${random_string.name_suffix.result}" 28 | log_analytics_workspace_name = var.name_prefix != null ? "${var.name_prefix}-example-law" : "ai-alz-example-law" 29 | nat_gateway_name = var.name_prefix != null ? "${var.name_prefix}-example-nat-gateway" : "ai-alz-example-nat-gateway" 30 | private_dns_zones = { 31 | key_vault_zone = { 32 | name = "privatelink.vaultcore.azure.net" 33 | } 34 | apim_zone = { 35 | name = "privatelink.azure-api.net" 36 | } 37 | cosmos_sql_zone = { 38 | name = "privatelink.documents.azure.com" 39 | } 40 | cosmos_mongo_zone = { 41 | name = "privatelink.mongo.cosmos.azure.com" 42 | } 43 | cosmos_cassandra_zone = { 44 | name = "privatelink.cassandra.cosmos.azure.com" 45 | } 46 | cosmos_gremlin_zone = { 47 | name = "privatelink.gremlin.cosmos.azure.com" 48 | } 49 | cosmos_table_zone = { 50 | name = "privatelink.table.cosmos.azure.com" 51 | } 52 | cosmos_analytical_zone = { 53 | name = "privatelink.analytics.cosmos.azure.com" 54 | } 55 | cosmos_postgres_zone = { 56 | name = "privatelink.postgres.cosmos.azure.com" 57 | } 58 | storage_blob_zone = { 59 | name = "privatelink.blob.core.windows.net" 60 | } 61 | storage_queue_zone = { 62 | name = "privatelink.queue.core.windows.net" 63 | } 64 | storage_table_zone = { 65 | name = "privatelink.table.core.windows.net" 66 | } 67 | storage_file_zone = { 68 | name = "privatelink.file.core.windows.net" 69 | } 70 | storage_dlfs_zone = { 71 | name = "privatelink.dfs.core.windows.net" 72 | } 73 | storage_web_zone = { 74 | name = "privatelink.web.core.windows.net" 75 | } 76 | ai_search_zone = { 77 | name = "privatelink.search.windows.net" 78 | } 79 | container_registry_zone = { 80 | name = "privatelink.azurecr.io" 81 | } 82 | app_configuration_zone = { 83 | name = "privatelink.azconfig.io" 84 | } 85 | ai_foundry_openai_zone = { 86 | name = "privatelink.openai.azure.com" 87 | } 88 | ai_foundry_ai_services_zone = { 89 | name = "privatelink.services.ai.azure.com" 90 | } 91 | ai_foundry_cognitive_services_zone = { 92 | name = "privatelink.cognitiveservices.azure.com" 93 | } 94 | } 95 | region_zones = local.region_zones_lookup != null ? local.region_zones_lookup : [] 96 | region_zones_lookup = [for region in module.avm_utl_regions.regions : region if(lower(region.name) == lower(azurerm_resource_group.this.location) || (lower(region.display_name) == lower(azurerm_resource_group.this.location)))][0].zones 97 | subnets = { 98 | AzureBastionSubnet = { 99 | enabled = true 100 | name = "AzureBastionSubnet" 101 | address_prefixes = [cidrsubnet(var.vnet_definition.address_space, 2, 0)] 102 | } 103 | JumpboxSubnet = { 104 | enabled = true 105 | name = "JumpboxSubnet" 106 | address_prefixes = [cidrsubnet(var.vnet_definition.address_space, 2, 1)] 107 | nat_gateway = { 108 | id = module.natgateway.resource_id 109 | } 110 | } 111 | AzureFirewallSubnet = { 112 | enabled = true 113 | name = "AzureFirewallSubnet" 114 | address_prefixes = [cidrsubnet(var.vnet_definition.address_space, 2, 2)] 115 | } 116 | DNSResolverInbound = { 117 | enabled = true 118 | name = "DNSResolverInbound" 119 | address_prefixes = [cidrsubnet(var.vnet_definition.address_space, 2, 3)] 120 | delegation = [{ 121 | name = "DNSResolverInboundDelegation" 122 | service_delegation = { 123 | name = "Microsoft.Network/dnsResolvers" 124 | } 125 | }] 126 | } 127 | } 128 | vnet_name = var.name_prefix != null ? "${var.name_prefix}-example-vnet" : "ai-alz-example-vnet" 129 | } 130 | -------------------------------------------------------------------------------- /terraform/modules/example_hub_vnet/outputs.tf: -------------------------------------------------------------------------------- 1 | output "dns_resolver_inbound_ip_addresses" { 2 | description = "The inbound IP address of the DNS resolver in the hub virtual network" 3 | value = module.private_resolver.inbound_endpoint_ips 4 | } 5 | 6 | output "firewall_ip_address" { 7 | description = "The IP address of the Azure Firewall in the hub virtual network" 8 | value = module.firewall.resource.ip_configuration[0].private_ip_address 9 | } 10 | 11 | output "resource_group_resource_id" { 12 | description = "The resource ID of the resource group where the hub virtual network is deployed" 13 | value = azurerm_resource_group.this.id 14 | } 15 | 16 | output "resource_id" { 17 | description = "Duplicating the vnet resource ID output to keep the linter happy." 18 | value = "" 19 | } 20 | 21 | output "virtual_network_resource_id" { 22 | description = "Azure Resource ID for the hub virtual network" 23 | value = module.ai_lz_vnet.resource_id 24 | } 25 | -------------------------------------------------------------------------------- /terraform/modules/example_hub_vnet/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.9, < 2.0" 3 | 4 | required_providers { 5 | azurerm = { 6 | source = "hashicorp/azurerm" 7 | version = ">= 3.116, < 5.0" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = "~> 3.5" 12 | } 13 | } 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /terraform/modules/example_hub_vnet/variables.tf: -------------------------------------------------------------------------------- 1 | variable "deployer_ip_address" { 2 | type = string 3 | description = "The Ip address of the compute resource deploying the module. This is used to allow access Key vault for the jump box secrets." 4 | } 5 | 6 | variable "location" { 7 | type = string 8 | description = <. 45 | If it is set to false, then no telemetry will be collected. 46 | DESCRIPTION 47 | nullable = false 48 | } 49 | 50 | variable "jump_vm_definition" { 51 | type = object({ 52 | name = optional(string) 53 | sku = optional(string, "Standard_B2s") 54 | tags = optional(map(string), {}) 55 | enable_telemetry = optional(bool, true) 56 | }) 57 | default = {} 58 | description = <. 27 | If it is set to false, then no telemetry will be collected. 28 | DESCRIPTION 29 | nullable = false 30 | } 31 | 32 | variable "flag_platform_landing_zone" { 33 | type = bool 34 | default = true 35 | description = <