Skip to content

Commit 65030d0

Browse files
committed
ml template
1 parent 4a0d5bb commit 65030d0

6 files changed

Lines changed: 368 additions & 0 deletions

File tree

8_ai-ml/machine-learning/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Terraform Template - Azure Machine Learning Workspace
2+
3+
Costa Rica
4+
5+
[![GitHub](https://img.shields.io/badge/--181717?logo=github&logoColor=ffffff)](https://github.com/)
6+
[brown9804](https://github.com/brown9804)
7+
8+
Last updated: 2026-02-16
9+
10+
------------------------------------------
11+
12+
> This template creates an Azure Machine Learning workspace and its common dependencies (Storage Account, Key Vault, Application Insights, Container Registry).
13+
14+
## Usage
15+
16+
```sh
17+
az login
18+
terraform init -upgrade
19+
terraform validate
20+
terraform plan
21+
terraform apply -auto-approve
22+
```

8_ai-ml/machine-learning/main.tf

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
data "azurerm_client_config" "current" {}
2+
3+
resource "azapi_resource" "resource_group" {
4+
type = "Microsoft.Resources/resourceGroups@2022-09-01"
5+
name = var.resource_group_name
6+
location = var.location
7+
parent_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}"
8+
9+
body = jsonencode({
10+
tags = var.tags
11+
})
12+
13+
response_export_values = [
14+
"id",
15+
"name"
16+
]
17+
}
18+
19+
resource "random_string" "suffix" {
20+
length = var.random_suffix_length
21+
upper = false
22+
special = false
23+
numeric = true
24+
25+
keepers = {
26+
resource_group_name = var.resource_group_name
27+
location = var.location
28+
workspace_base = var.workspace_name
29+
}
30+
}
31+
32+
locals {
33+
suffix = var.append_random_suffix ? random_string.suffix.result : ""
34+
hyphen_suffix = var.append_random_suffix ? "-${local.suffix}" : ""
35+
36+
workspace_name = "${var.workspace_name}${local.hyphen_suffix}"
37+
38+
storage_account_name_raw = lower("${var.storage_account_name_prefix}${local.suffix}")
39+
storage_account_name = substr(join("", regexall("[a-z0-9]", local.storage_account_name_raw)), 0, 24)
40+
41+
key_vault_name_raw = lower("${var.key_vault_name_prefix}${local.suffix}")
42+
key_vault_name = substr(join("", regexall("[a-z0-9-]", local.key_vault_name_raw)), 0, 24)
43+
44+
container_registry_name_raw = lower("${var.container_registry_name_prefix}${local.suffix}")
45+
container_registry_name = substr(join("", regexall("[a-z0-9]", local.container_registry_name_raw)), 0, 50)
46+
47+
application_insights_name = "${var.application_insights_name_prefix}${local.hyphen_suffix}"
48+
}
49+
50+
resource "azurerm_storage_account" "sa" {
51+
name = local.storage_account_name
52+
resource_group_name = var.resource_group_name
53+
location = var.location
54+
account_tier = "Standard"
55+
account_replication_type = "LRS"
56+
57+
shared_access_key_enabled = false
58+
default_to_oauth_authentication = true
59+
60+
tags = var.tags
61+
62+
depends_on = [
63+
azapi_resource.resource_group
64+
]
65+
66+
lifecycle {
67+
precondition {
68+
condition = length(local.storage_account_name) >= 3
69+
error_message = "Generated storage account name is too short. Adjust storage_account_name_prefix/random_suffix_length."
70+
}
71+
}
72+
}
73+
74+
resource "azurerm_key_vault" "kv" {
75+
name = local.key_vault_name
76+
location = var.location
77+
resource_group_name = var.resource_group_name
78+
79+
tenant_id = data.azurerm_client_config.current.tenant_id
80+
sku_name = "standard"
81+
82+
soft_delete_retention_days = 7
83+
purge_protection_enabled = false
84+
enable_rbac_authorization = true
85+
public_network_access_enabled = true
86+
87+
tags = var.tags
88+
89+
depends_on = [
90+
azapi_resource.resource_group
91+
]
92+
93+
lifecycle {
94+
precondition {
95+
condition = length(local.key_vault_name) >= 3
96+
error_message = "Generated key vault name is too short. Adjust key_vault_name_prefix/random_suffix_length."
97+
}
98+
}
99+
}
100+
101+
resource "azurerm_application_insights" "appi" {
102+
name = local.application_insights_name
103+
location = var.location
104+
resource_group_name = var.resource_group_name
105+
application_type = "web"
106+
107+
lifecycle {
108+
ignore_changes = [
109+
workspace_id
110+
]
111+
}
112+
113+
tags = var.tags
114+
115+
depends_on = [
116+
azapi_resource.resource_group
117+
]
118+
}
119+
120+
resource "azurerm_container_registry" "acr" {
121+
name = local.container_registry_name
122+
location = var.location
123+
resource_group_name = var.resource_group_name
124+
sku = "Basic"
125+
admin_enabled = false
126+
127+
tags = var.tags
128+
129+
depends_on = [
130+
azapi_resource.resource_group
131+
]
132+
133+
lifecycle {
134+
precondition {
135+
condition = length(local.container_registry_name) >= 5
136+
error_message = "Generated container registry name is too short. Adjust container_registry_name_prefix/random_suffix_length."
137+
}
138+
}
139+
}
140+
141+
resource "azurerm_machine_learning_workspace" "mlw" {
142+
name = local.workspace_name
143+
location = var.location
144+
resource_group_name = var.resource_group_name
145+
146+
application_insights_id = azurerm_application_insights.appi.id
147+
key_vault_id = azurerm_key_vault.kv.id
148+
storage_account_id = azurerm_storage_account.sa.id
149+
container_registry_id = azurerm_container_registry.acr.id
150+
151+
public_network_access_enabled = var.public_network_access_enabled
152+
153+
dynamic "identity" {
154+
for_each = var.enable_system_assigned_identity ? [1] : []
155+
content {
156+
type = "SystemAssigned"
157+
}
158+
}
159+
160+
tags = var.tags
161+
162+
depends_on = [
163+
azapi_resource.resource_group
164+
]
165+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
output "resource_group_id" {
2+
description = "The resource ID of the Resource Group."
3+
value = azapi_resource.resource_group.id
4+
}
5+
6+
output "machine_learning_workspace_id" {
7+
description = "The resource ID of the Azure Machine Learning workspace."
8+
value = azurerm_machine_learning_workspace.mlw.id
9+
}
10+
11+
output "machine_learning_workspace_name" {
12+
description = "The name of the Azure Machine Learning workspace."
13+
value = azurerm_machine_learning_workspace.mlw.name
14+
}
15+
16+
output "storage_account_name" {
17+
description = "The generated storage account name used by the workspace."
18+
value = azurerm_storage_account.sa.name
19+
}
20+
21+
output "key_vault_name" {
22+
description = "The generated key vault name used by the workspace."
23+
value = azurerm_key_vault.kv.name
24+
}
25+
26+
output "container_registry_name" {
27+
description = "The generated container registry name used by the workspace."
28+
value = azurerm_container_registry.acr.name
29+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
terraform {
2+
required_version = ">= 1.8, < 2.0"
3+
4+
required_providers {
5+
azurerm = {
6+
source = "hashicorp/azurerm"
7+
version = "~> 3.116"
8+
}
9+
10+
azapi = {
11+
source = "Azure/azapi"
12+
version = "~> 1.13"
13+
}
14+
15+
random = {
16+
source = "hashicorp/random"
17+
version = "~> 3.6"
18+
}
19+
}
20+
}
21+
22+
provider "azurerm" {
23+
features {
24+
resource_group {
25+
prevent_deletion_if_contains_resources = false
26+
}
27+
key_vault {
28+
purge_soft_delete_on_destroy = false
29+
recover_soft_deleted_key_vaults = true
30+
}
31+
}
32+
33+
# Required when the environment/policies disable Storage Account shared-key auth.
34+
# This makes the AzureRM provider use Azure AD for storage data-plane operations.
35+
storage_use_azuread = true
36+
37+
# Uses the current Azure CLI context (az login + az account set)
38+
skip_provider_registration = false
39+
}
40+
41+
provider "azapi" {
42+
# Uses the current Azure CLI context (az login + az account set)
43+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
resource_group_name = "rg-ai-ml-dev"
2+
location = "eastus"
3+
4+
append_random_suffix = true
5+
random_suffix_length = 6
6+
7+
workspace_name = "mlw-dev"
8+
9+
storage_account_name_prefix = "stml"
10+
key_vault_name_prefix = "kvml"
11+
container_registry_name_prefix = "acrml"
12+
application_insights_name_prefix = "appi-ml"
13+
14+
public_network_access_enabled = true
15+
enable_system_assigned_identity = true
16+
17+
tags = {
18+
env = "dev"
19+
area = "ai-ml"
20+
iac = "terraform"
21+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
variable "resource_group_name" {
2+
description = "The name of the Azure Resource Group to deploy into. This template will create the RG if it does not exist (idempotent ARM PUT)."
3+
type = string
4+
5+
validation {
6+
condition = length(trimspace(var.resource_group_name)) > 0
7+
error_message = "resource_group_name must not be empty."
8+
}
9+
}
10+
11+
variable "location" {
12+
description = "The Azure region for the deployment."
13+
type = string
14+
15+
validation {
16+
condition = length(trimspace(var.location)) > 0
17+
error_message = "location must not be empty."
18+
}
19+
}
20+
21+
variable "append_random_suffix" {
22+
description = "Whether to append a random suffix to names to reduce naming collisions."
23+
type = bool
24+
default = true
25+
}
26+
27+
variable "random_suffix_length" {
28+
description = "Length of the random suffix appended when append_random_suffix is true."
29+
type = number
30+
default = 6
31+
32+
validation {
33+
condition = var.random_suffix_length >= 4 && var.random_suffix_length <= 16
34+
error_message = "random_suffix_length must be between 4 and 16."
35+
}
36+
}
37+
38+
variable "workspace_name" {
39+
description = "Base name of the Azure Machine Learning workspace. If append_random_suffix is true, the final name will be '<base>-<suffix>'."
40+
type = string
41+
42+
validation {
43+
condition = length(trimspace(var.workspace_name)) >= 3 && length(var.workspace_name) <= 64
44+
error_message = "workspace_name must be between 3 and 64 characters."
45+
}
46+
}
47+
48+
variable "storage_account_name_prefix" {
49+
description = "Prefix used to generate the Storage Account name for the AML workspace (will be sanitized to meet naming rules)."
50+
type = string
51+
default = "stml"
52+
}
53+
54+
variable "key_vault_name_prefix" {
55+
description = "Prefix used to generate the Key Vault name for the AML workspace (will be sanitized to meet naming rules)."
56+
type = string
57+
default = "kvml"
58+
}
59+
60+
variable "container_registry_name_prefix" {
61+
description = "Prefix used to generate the Container Registry name for the AML workspace (will be sanitized to meet naming rules)."
62+
type = string
63+
default = "acrml"
64+
}
65+
66+
variable "application_insights_name_prefix" {
67+
description = "Prefix used to generate the Application Insights name for the AML workspace."
68+
type = string
69+
default = "appi-ml"
70+
}
71+
72+
variable "public_network_access_enabled" {
73+
description = "Whether public network access is enabled for the Azure Machine Learning workspace."
74+
type = bool
75+
default = true
76+
}
77+
78+
variable "enable_system_assigned_identity" {
79+
description = "Whether to enable a System Assigned Managed Identity on the Azure Machine Learning workspace."
80+
type = bool
81+
default = true
82+
}
83+
84+
variable "tags" {
85+
description = "A map of tags to assign to resources."
86+
type = map(string)
87+
default = {}
88+
}

0 commit comments

Comments
 (0)