2.Image.Builder/image.builder.tf (326 lines of code) (raw):

############################################################################################# # Image Builder (https://learn.microsoft.com/azure/virtual-machines/image-builder-overview) # ############################################################################################# variable imageBuilder { type = object({ templates = list(object({ enable = bool name = string source = object({ imageDefinition = object({ name = string }) }) build = object({ machineType = string machineSize = string gpuProvider = string imageVersion = string osDiskSizeGB = number timeoutMinutes = number jobSchedulers = list(string) jobProcessors = list(string) }) })) distribute = object({ replicaCount = number replicaRegions = list(string) storageAccount = object({ type = string }) }) errorHandling = object({ validationMode = string customizationMode = string }) }) } locals { blobStorage = merge(data.terraform_remote_state.core.outputs.storage.blob, { authTokenUrl = "${data.terraform_remote_state.core.outputs.storage.blob.authTokenUrl}&msi_res_id=${data.azurerm_user_assigned_identity.studio.id}" }) appVersion = { jobSchedulerDeadline = data.azurerm_app_configuration_keys.studio.items[index(data.azurerm_app_configuration_keys.studio.items[*].key, data.terraform_remote_state.core.outputs.appConfig.key.jobSchedulerDeadline)].value jobSchedulerSlurm = data.azurerm_app_configuration_keys.studio.items[index(data.azurerm_app_configuration_keys.studio.items[*].key, data.terraform_remote_state.core.outputs.appConfig.key.jobSchedulerSlurm)].value jobProcessorPBRT = data.azurerm_app_configuration_keys.studio.items[index(data.azurerm_app_configuration_keys.studio.items[*].key, data.terraform_remote_state.core.outputs.appConfig.key.jobProcessorPBRT)].value jobProcessorBlender = data.azurerm_app_configuration_keys.studio.items[index(data.azurerm_app_configuration_keys.studio.items[*].key, data.terraform_remote_state.core.outputs.appConfig.key.jobProcessorBlender)].value nvidiaCUDAWindows = data.azurerm_app_configuration_keys.studio.items[index(data.azurerm_app_configuration_keys.studio.items[*].key, data.terraform_remote_state.core.outputs.appConfig.key.nvidiaCUDAWindows)].value hpAnywareAgent = data.azurerm_app_configuration_keys.studio.items[index(data.azurerm_app_configuration_keys.studio.items[*].key, data.terraform_remote_state.core.outputs.appConfig.key.hpAnywareAgent)].value } authCredential = { adminUsername = data.azurerm_key_vault_secret.admin_username.value adminPassword = data.azurerm_key_vault_secret.admin_password.value serviceUsername = data.azurerm_key_vault_secret.service_username.value servicePassword = data.azurerm_key_vault_secret.service_password.value } } resource azurerm_role_assignment compute_gallery_artifacts_publisher { role_definition_name = "Compute Gallery Artifacts Publisher" # https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/compute#compute-gallery-artifacts-publisher principal_id = data.azurerm_user_assigned_identity.studio.principal_id scope = azurerm_resource_group.image_gallery.id } resource time_sleep image_builder_rbac { create_duration = "30s" depends_on = [ azurerm_role_assignment.compute_gallery_artifacts_publisher ] } resource azapi_resource linux { for_each = { for imageTemplate in var.imageBuilder.templates : imageTemplate.name => imageTemplate if module.core.image.linux.enable && imageTemplate.enable && lower(imageTemplate.source.imageDefinition.name) == "linux" } name = each.value.name type = "Microsoft.VirtualMachineImages/imageTemplates@2024-02-01" parent_id = azurerm_resource_group.image_builder.id location = azurerm_resource_group.image_builder.location identity { type = "UserAssigned" identity_ids = [ data.azurerm_user_assigned_identity.studio.id ] } body = { properties = { buildTimeoutInMinutes = each.value.build.timeoutMinutes vmProfile = { vmSize = each.value.build.machineSize osDiskSizeGB = each.value.build.osDiskSizeGB userAssignedIdentities = [ data.azurerm_user_assigned_identity.studio.id ] vnetConfig = { subnetId = data.azurerm_subnet.studio.id } } source = { type = "PlatformImage" publisher = var.computeGallery.imageDefinitions[index(var.computeGallery.imageDefinitions.*.name, each.value.source.imageDefinition.name)].publisher offer = var.computeGallery.imageDefinitions[index(var.computeGallery.imageDefinitions.*.name, each.value.source.imageDefinition.name)].offer sku = var.computeGallery.imageDefinitions[index(var.computeGallery.imageDefinitions.*.name, each.value.source.imageDefinition.name)].sku version = module.core.image.linux.version } optimize = { vmBoot = { state = "Enabled" } } errorHandling = { onValidationError = var.imageBuilder.errorHandling.validationMode onCustomizerError = var.imageBuilder.errorHandling.customizationMode } customize = concat( [ { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/0.Core.Foundation/functions.sh" destination = "/tmp/functions.sh" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Linux/customize.core.sh" destination = "/tmp/customize.core.sh" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Linux/customize.core.gpu.sh" destination = "/tmp/customize.core.gpu.sh" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Linux/customize.job.scheduler.sh" destination = "/tmp/customize.job.scheduler.sh" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Linux/customize.job.processor.sh" destination = "/tmp/customize.job.processor.sh" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Linux/terminate.sh" destination = "/tmp/terminate.sh" } ], [ { type = "Shell" inline = [ "hostname ${each.value.name}" ] }, { type = "Shell" inline = [ "cat /tmp/customize.core.sh | tr -d \r | buildConfigEncoded=${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))} /bin/bash", "cat /tmp/customize.core.gpu.sh | tr -d \r | buildConfigEncoded=${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))} /bin/bash", "cat /tmp/customize.job.scheduler.sh | tr -d \r | buildConfigEncoded=${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))} /bin/bash", "cat /tmp/customize.job.processor.sh | tr -d \r | buildConfigEncoded=${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))} /bin/bash" ] } ] ) distribute = [ { type = "SharedImage" runOutputName = "${each.value.name}-${each.value.build.imageVersion}" galleryImageId = "${azurerm_shared_image.studio[each.value.source.imageDefinition.name].id}/versions/${each.value.build.imageVersion}" targetRegions = [ for location in concat([azurerm_shared_image_gallery.studio.location], var.imageBuilder.distribute.replicaRegions) : { name = location replicaCount = var.imageBuilder.distribute.replicaCount storageAccountType = var.imageBuilder.distribute.storageAccount.type } ] versioning = { scheme = "Latest" major = tonumber(split(".", each.value.build.imageVersion)[0]) } artifactTags = { imageTemplateName = each.value.name } } ] } } schema_validation_enabled = false lifecycle { ignore_changes = [ body ] } depends_on = [ time_sleep.image_builder_rbac ] } resource azapi_resource windows { for_each = { for imageTemplate in var.imageBuilder.templates : imageTemplate.name => imageTemplate if module.core.image.windows.enable && imageTemplate.enable && startswith(imageTemplate.source.imageDefinition.name, "Win") } name = each.value.name type = "Microsoft.VirtualMachineImages/imageTemplates@2024-02-01" parent_id = azurerm_resource_group.image_builder.id location = azurerm_resource_group.image_builder.location identity { type = "UserAssigned" identity_ids = [ data.azurerm_user_assigned_identity.studio.id ] } body = { properties = { buildTimeoutInMinutes = each.value.build.timeoutMinutes vmProfile = { vmSize = each.value.build.machineSize osDiskSizeGB = each.value.build.osDiskSizeGB userAssignedIdentities = [ data.azurerm_user_assigned_identity.studio.id ] vnetConfig = { subnetId = data.azurerm_subnet.studio.id } } source = { type = "PlatformImage" publisher = var.computeGallery.imageDefinitions[index(var.computeGallery.imageDefinitions.*.name, each.value.source.imageDefinition.name)].publisher offer = var.computeGallery.imageDefinitions[index(var.computeGallery.imageDefinitions.*.name, each.value.source.imageDefinition.name)].offer sku = var.computeGallery.imageDefinitions[index(var.computeGallery.imageDefinitions.*.name, each.value.source.imageDefinition.name)].sku version = module.core.image.windows.version } optimize = { vmBoot = { state = "Enabled" } } errorHandling = { onValidationError = var.imageBuilder.errorHandling.validationMode onCustomizerError = var.imageBuilder.errorHandling.customizationMode } customize = concat( [ { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/0.Core.Foundation/functions.ps1" destination = "C:\\AzureData\\functions.ps1" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Windows/customize.core.ps1" destination = "C:\\AzureData\\customize.core.ps1" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Windows/customize.core.gpu.ps1" destination = "C:\\AzureData\\customize.core.gpu.ps1" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Windows/customize.job.scheduler.ps1" destination = "C:\\AzureData\\customize.job.scheduler.ps1" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Windows/customize.job.processor.ps1" destination = "C:\\AzureData\\customize.job.processor.ps1" }, { type = "File" sourceUri = "https://raw.githubusercontent.com/Azure/ArtistAnywhere/main/2.Image.Builder/Windows/terminate.ps1" destination = "C:\\AzureData\\terminate.ps1" } ], [ { type = "PowerShell" inline = [ "Rename-Computer -NewName ${each.value.name} -Force" ] }, { type = "WindowsRestart" }, { type = "PowerShell" inline = [ "C:\\AzureData\\customize.core.ps1 -buildConfigEncoded ${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))}", "C:\\AzureData\\customize.core.gpu.ps1 -buildConfigEncoded ${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))}", "C:\\AzureData\\customize.job.scheduler.ps1 -buildConfigEncoded ${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))}", "C:\\AzureData\\customize.job.processor.ps1 -buildConfigEncoded ${base64encode(jsonencode(merge(each.value.build, {blobStorage = local.blobStorage}, {appVersion = local.appVersion}, {authCredential = local.authCredential})))}" ] runElevated = true runAsSystem = true }, { type = "WindowsRestart" } ] ) distribute = [ { type = "SharedImage" runOutputName = "${each.value.name}-${each.value.build.imageVersion}" galleryImageId = "${azurerm_shared_image.studio[each.value.source.imageDefinition.name].id}/versions/${each.value.build.imageVersion}" targetRegions = [ for location in concat([azurerm_shared_image_gallery.studio.location], var.imageBuilder.distribute.replicaRegions) : { name = location replicaCount = var.imageBuilder.distribute.replicaCount storageAccountType = var.imageBuilder.distribute.storageAccount.type } ] versioning = { scheme = "Latest" major = tonumber(split(".", each.value.build.imageVersion)[0]) } artifactTags = { imageTemplateName = each.value.name } } ] } } schema_validation_enabled = false lifecycle { ignore_changes = [ body ] } depends_on = [ time_sleep.image_builder_rbac ] }