├── .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 | 
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 | 
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 |
- [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.bing_accounts.html)
- [Template reference](https://learn.microsoft.com/en-us/azure/templates)
|
16 | | `Microsoft.CognitiveServices/accounts/connections` | 2025-06-01 | - [AzAdvertizer](https://www.azadvertizer.net/azresourcetypes/microsoft.cognitiveservices_accounts_connections.html)
- [Template reference](https://learn.microsoft.com/en-us/azure/templates/Microsoft.CognitiveServices/2025-06-01/accounts/connections)
|
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.- To capture and use completions data, you will need to make sure your resource region is included in: swedencentral, northcentralus, eastus2
- Azure OpenAI Evaluation is only supported in the following regions: [eastus2, northcentralus, swedencentral, switzerlandwest, uaenorth], and your region is eastus. Change to a valid region to use Azure OpenAI Evaluation.
- Real-time audio is only available in the following regions: eastus2swedencentral
- Assistants are only available in the following regions: australiaeast, centraluseuap, eastus, eastus2, francecentral, japaneast, norwayeast, southindia, swedencentral, uksouth, westus, westus3
|
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 = <