Skip to content

A module used to deploy a Windows VM and various component resources in an easy way πŸͺŸ

License

Notifications You must be signed in to change notification settings

cyber-scot/terraform-azurerm-windows-virtual-machine

Repository files navigation

resource "azurerm_public_ip" "pip" {
  for_each = { for vm in var.windows_vms : vm.name => vm if vm.public_ip_sku != null }

  name                = each.value.pip_name != null ? each.value.pip_name : "pip-${each.value.name}"
  location            = each.value.location
  resource_group_name = each.value.rg_name
  allocation_method   = each.value.allocation_method
  domain_name_label   = try(each.value.pip_custom_dns_label, each.value.computer_name, null)
  sku                 = each.value.public_ip_sku

  lifecycle {
    ignore_changes = [domain_name_label]
  }
}

resource "azurerm_network_interface" "nic" {
  for_each = { for vm in var.windows_vms : vm.name => vm }

  name                          = each.value.nic_name != null ? each.value.nic_name : "nic-${each.value.name}"
  location                      = each.value.location
  resource_group_name           = each.value.rg_name
  enable_accelerated_networking = each.value.enable_accelerated_networking

  ip_configuration {
    name                          = each.value.nic_ipconfig_name != null ? each.value.nic_ipconfig_name : "nic-ipcon-${each.value.name}"
    primary                       = true
    private_ip_address_allocation = each.value.static_private_ip == null ? "Dynamic" : "Static"
    private_ip_address            = each.value.static_private_ip
    public_ip_address_id          = lookup(each.value, "public_ip_sku", null) == null ? null : azurerm_public_ip.pip[each.key].id
    subnet_id                     = each.value.subnet_id
  }
  tags = each.value.tags

  timeouts {
    create = "5m"
    delete = "10m"
  }
}

resource "azurerm_application_security_group" "asg" {
  for_each = { for vm in var.windows_vms : vm.name => vm if vm.create_asg == true }

  name                = each.value.asg_name != null ? each.value.asg_name : "asg-${each.value.name}"
  location            = each.value.location
  resource_group_name = each.value.rg_name
  tags                = each.value.tags
}

resource "azurerm_network_interface_application_security_group_association" "asg_association" {
  for_each = { for vm in var.windows_vms : vm.name => vm }

  network_interface_id          = azurerm_network_interface.nic[each.key].id
  application_security_group_id = each.value.asg_id != null ? each.value.asg_id : azurerm_application_security_group.asg[each.key].id
}


resource "random_integer" "zone" {
  for_each = { for vm in var.windows_vms : vm.name => vm if vm.availability_zone == "random" }
  min      = 1
  max      = 3
}

locals {
  sanitized_names = { for vm in var.windows_vms : vm.name => upper(replace(replace(replace(vm.name, " ", ""), "-", ""), "_", "")) }
  netbios_names   = { for key, value in local.sanitized_names : key => substr(value, 0, min(length(value), 15)) }
  random_zones    = { for idx, vm in var.windows_vms : vm.name => vm.availability_zone == "random" ? tostring(idx + 1) : vm.availability_zone }
}

resource "azurerm_windows_virtual_machine" "this" {
  for_each = { for vm in var.windows_vms : vm.name => vm }

  // Forces acceptance of marketplace terms before creating a VM
  depends_on = [
    azurerm_marketplace_agreement.plan_acceptance_simple,
    azurerm_marketplace_agreement.plan_acceptance_custom
  ]

  name                         = each.value.name
  resource_group_name          = each.value.rg_name
  location                     = each.value.location
  network_interface_ids        = [azurerm_network_interface.nic[each.key].id]
  license_type                 = each.value.license_type
  patch_mode                   = each.value.patch_mode
  enable_automatic_updates     = each.value.enable_automatic_updates
  computer_name                = each.value.computer_name != null ? each.value.computer_name : local.netbios_names[each.key]
  admin_username               = each.value.admin_username
  admin_password               = each.value.admin_password
  size                         = each.value.vm_size
  source_image_id              = try(each.value.use_custom_image, null) == true ? each.value.custom_source_image_id : null
  zone                         = local.random_zones[each.key]
  availability_set_id          = each.value.availability_set_id
  virtual_machine_scale_set_id = each.value.virtual_machine_scale_set_id
  timezone                     = each.value.timezone
  user_data                    = each.value.user_data
  custom_data                  = each.value.custom_data
  tags                         = each.value.tags

  encryption_at_host_enabled = each.value.enable_encryption_at_host
  allow_extension_operations = each.value.allow_extension_operations
  provision_vm_agent         = each.value.provision_vm_agent

  dynamic "additional_capabilities" {
    for_each = each.value.ultra_ssd_enabled ? [1] : []
    content {
      ultra_ssd_enabled = each.value.ultra_ssd_enabled
    }
  }

  # Use simple image
  dynamic "source_image_reference" {
    for_each = try(each.value.use_simple_image, null) == true && try(each.value.use_simple_image_with_plan, null) == false && try(each.value.use_custom_image, null) == false ? [1] : []
    content {
      publisher = coalesce(each.value.vm_os_publisher, module.os_calculator[each.value.name].calculated_value_os_publisher)
      offer     = coalesce(each.value.vm_os_offer, module.os_calculator[each.value.name].calculated_value_os_offer)
      sku       = coalesce(each.value.vm_os_sku, module.os_calculator[each.value.name].calculated_value_os_sku)
      version   = coalesce(each.value.vm_os_version, "latest")
    }
  }


  # Use custom image reference
  dynamic "source_image_reference" {
    for_each = try(each.value.use_simple_image, null) == false && try(each.value.use_simple_image_with_plan, null) == false && try(length(each.value.source_image_reference), 0) > 0 && try(length(each.value.plan), 0) == 0 && try(each.value.use_custom_image, null) == false ? [1] : []

    content {
      publisher = lookup(each.value.source_image_reference, "publisher", null)
      offer     = lookup(each.value.source_image_reference, "offer", null)
      sku       = lookup(each.value.source_image_reference, "sku", null)
      version   = lookup(each.value.source_image_reference, "version", null)
    }
  }

  dynamic "source_image_reference" {
    for_each = try(each.value.use_simple_image, null) == true && try(each.value.use_simple_image_with_plan, null) == true && try(each.value.use_custom_image, null) == false ? [1] : []

    content {
      publisher = coalesce(each.value.vm_os_publisher, module.os_calculator_with_plan[each.value.name].calculated_value_os_publisher)
      offer     = coalesce(each.value.vm_os_offer, module.os_calculator_with_plan[each.value.name].calculated_value_os_offer)
      sku       = coalesce(each.value.vm_os_sku, module.os_calculator_with_plan[each.value.name].calculated_value_os_sku)
      version   = coalesce(each.value.vm_os_version, "latest")
    }
  }


  dynamic "plan" {
    for_each = try(each.value.use_simple_image, null) == false && try(each.value.use_simple_image_with_plan, null) == false && try(length(each.value.plan), 0) > 0 && try(each.value.use_custom_image, null) == false ? [1] : []

    content {
      name      = coalesce(each.value.vm_os_sku, module.os_calculator_with_plan[each.value.name].calculated_value_os_sku)
      product   = coalesce(each.value.vm_os_offer, module.os_calculator_with_plan[each.value.name].calculated_value_os_offer)
      publisher = coalesce(each.value.vm_os_publisher, module.os_calculator_with_plan[each.value.name].calculated_value_os_publisher)
    }
  }


  dynamic "plan" {
    for_each = try(each.value.use_simple_image, null) == false && try(each.value.use_simple_image_with_plan, null) == false && try(length(each.value.plan), 0) > 0 && try(each.value.use_custom_image, null) == false ? [1] : []

    content {
      name      = lookup(each.value.plan, "name", null)
      product   = lookup(each.value.plan, "product", null)
      publisher = lookup(each.value.plan, "publisher", null)
    }
  }


  dynamic "identity" {
    for_each = each.value.identity_type == "SystemAssigned" ? [each.value.identity_type] : []
    content {
      type = each.value.identity_type
    }
  }

  dynamic "identity" {
    for_each = each.value.identity_type == "SystemAssigned, UserAssigned" ? [each.value.identity_type] : []
    content {
      type         = each.value.identity_type
      identity_ids = try(each.value.identity_ids, [])
    }
  }

  dynamic "identity" {
    for_each = each.value.identity_type == "UserAssigned" ? [each.value.identity_type] : []
    content {
      type         = each.value.identity_type
      identity_ids = length(try(each.value.identity_ids, [])) > 0 ? each.value.identity_ids : []
    }
  }


  priority        = try(each.value.spot_instance, false) ? "Spot" : "Regular"
  max_bid_price   = try(each.value.spot_instance, false) ? each.value.spot_instance_max_bid_price : null
  eviction_policy = try(each.value.spot_instance, false) ? each.value.spot_instance_eviction_policy : null

  os_disk {
    name                             = each.value.os_disk.name != null ? each.value.os_disk.name : "osdisk-${each.value.name}"
    caching                          = each.value.os_disk.caching
    storage_account_type             = each.value.os_disk.os_disk_type
    disk_size_gb                     = each.value.os_disk.disk_size_gb
    disk_encryption_set_id           = each.value.os_disk.disk_encryption_set_id
    secure_vm_disk_encryption_set_id = each.value.os_disk.secure_vm_disk_encryption_set_id
    security_encryption_type         = each.value.os_disk.security_encryption_type
    write_accelerator_enabled        = each.value.os_disk.write_accelerator_enabled

    dynamic "diff_disk_settings" {
      for_each = each.value.os_disk.diff_disk_settings != null ? [each.value.os_disk.diff_disk_settings] : []
      content {
        option = diff_disk_settings.value.option
      }
    }
  }

  dynamic "boot_diagnostics" {
    for_each = each.value.boot_diagnostics_storage_account_uri != null ? [each.value.boot_diagnostics_storage_account_uri] : [null]
    content {
      storage_account_uri = boot_diagnostics.value
    }
  }


  dynamic "additional_unattend_content" {
    for_each = each.value.additional_unattend_content != null ? each.value.additional_unattend_content : []
    content {
      content = additional_unattend_content.value.content
      setting = additional_unattend_content.value.setting
    }
  }

  dynamic "secret" {
    for_each = each.value.secrets != null ? each.value.secrets : []
    content {
      key_vault_id = secret.value.key_vault_id

      dynamic "certificate" {
        for_each = secret.value.certificates
        content {
          store = certificate.value.store
          url   = certificate.value.url
        }
      }
    }
  }

  dynamic "termination_notification" {
    for_each = each.value.termination_notification != null ? [each.value.termination_notification] : []
    content {
      enabled = termination_notification.value.enabled
      timeout = lookup(termination_notification.value, "timeout", "PT5M")
    }
  }

  dynamic "winrm_listener" {
    for_each = each.value.winrm_listener != null ? each.value.winrm_listener : []
    content {
      protocol        = winrm_listener.value.protocol
      certificate_url = winrm_listener.value.certificate_url
    }
  }
}

module "os_calculator" {
  source       = "cyber-scot/windows-virtual-machine-os-sku-calculator/azurerm"
  for_each     = { for vm in var.windows_vms : vm.name => vm if try(vm.use_simple_image, null) == true }
  vm_os_simple = each.value.vm_os_simple
}

module "os_calculator_with_plan" {
  source       = "cyber-scot/windows-virtual-machine-os-sku-with-plan-calculator/azurerm"
  for_each     = { for vm in var.windows_vms : vm.name => vm if try(vm.use_simple_image_with_plan, null) == true }
  vm_os_simple = each.value.vm_os_simple
}

resource "azurerm_marketplace_agreement" "plan_acceptance_simple" {
  for_each = { for vm in var.windows_vms : vm.name => vm if try(vm.use_simple_image_with_plan, null) == true && try(vm.accept_plan, null) == true && try(vm.use_custom_image, null) == false }

  publisher = coalesce(each.value.vm_os_publisher, module.os_calculator_with_plan[each.key].calculated_value_os_publisher)
  offer     = coalesce(each.value.vm_os_offer, module.os_calculator_with_plan[each.key].calculated_value_os_offer)
  plan      = coalesce(each.value.vm_os_sku, module.os_calculator_with_plan[each.key].calculated_value_os_sku)
}

resource "azurerm_marketplace_agreement" "plan_acceptance_custom" {
  for_each = { for vm in var.windows_vms : vm.name => vm if try(vm.use_custom_image_with_plan, null) == true && try(vm.accept_plan, null) == true && try(vm.use_custom_image, null) == true }

  publisher = lookup(each.value.plan, "publisher", null)
  offer     = lookup(each.value.plan, "product", null)
  plan      = lookup(each.value.plan, "name", null)
}

resource "azurerm_virtual_machine_extension" "windows_vm_inline_command" {
  for_each   = { for vm in var.windows_vms : vm.name => vm if try(vm.run_vm_command.inline, null) != null }
  depends_on = [azurerm_windows_virtual_machine.this]

  name                       = each.value.run_vm_command.extension_name != null ? each.value.run_vm_command.extension_name : "run-command-${each.value.name}"
  publisher                  = "Microsoft.CPlat.Core"
  type                       = "RunCommandWindows"
  type_handler_version       = "1.1"
  auto_upgrade_minor_version = true

  settings = jsonencode({
    script = tolist([each.value.run_vm_command.inline])
  })

  tags               = each.value.tags
  virtual_machine_id = azurerm_windows_virtual_machine.this[each.key].id

  lifecycle {
    ignore_changes = all
  }
}

resource "azurerm_virtual_machine_extension" "windows_vm_file_command" {
  for_each   = { for vm in var.windows_vms : vm.name => vm if try(vm.run_vm_command.script_file, null) != null }
  depends_on = [azurerm_windows_virtual_machine.this]

  name                       = each.value.run_vm_command.extension_name != null ? each.value.run_vm_command.extension_name : "run-command-file-${each.value.name}"
  publisher                  = "Microsoft.CPlat.Core"
  type                       = "RunCommandWindows"
  type_handler_version       = "1.1"
  auto_upgrade_minor_version = true

  settings = jsonencode({
    script = compact(tolist([each.value.run_vm_command.script_file]))
  })

  tags               = each.value.tags
  virtual_machine_id = azurerm_windows_virtual_machine.this[each.key].id

  lifecycle {
    ignore_changes = all
  }
}

resource "azurerm_virtual_machine_extension" "windows_vm_uri_command" {
  for_each   = { for vm in var.windows_vms : vm.name => vm if try(vm.run_vm_command.script_uri, null) != null }
  depends_on = [azurerm_windows_virtual_machine.this]

  name                       = each.value.run_vm_command.extension_name != null ? each.value.run_vm_command.extension_name : "run-command-uri-${each.value.name}"
  publisher                  = "Microsoft.CPlat.Core"
  type                       = "RunCommandWindows"
  type_handler_version       = "1.1"
  auto_upgrade_minor_version = true

  settings = jsonencode({
    script = compact(tolist([each.value.run_vm_command.script_uri]))
  })

  tags               = each.value.tags
  virtual_machine_id = azurerm_windows_virtual_machine.this[each.key].id

  lifecycle {
    ignore_changes = all
  }
}

Requirements

No requirements.

Providers

Name Version
azurerm n/a
random n/a

Modules

Name Source Version
os_calculator cyber-scot/windows-virtual-machine-os-sku-calculator/azurerm n/a
os_calculator_with_plan cyber-scot/windows-virtual-machine-os-sku-with-plan-calculator/azurerm n/a

Resources

Name Type
azurerm_application_security_group.asg resource
azurerm_marketplace_agreement.plan_acceptance_custom resource
azurerm_marketplace_agreement.plan_acceptance_simple resource
azurerm_network_interface.nic resource
azurerm_network_interface_application_security_group_association.asg_association resource
azurerm_public_ip.pip resource
azurerm_virtual_machine_extension.windows_vm_file_command resource
azurerm_virtual_machine_extension.windows_vm_inline_command resource
azurerm_virtual_machine_extension.windows_vm_uri_command resource
azurerm_windows_virtual_machine.this resource
random_integer.zone resource

Inputs

Name Description Type Default Required
windows_vms List of VM configurations.
list(object({
accept_plan = optional(bool, false)
additional_unattend_content = optional(list(object({
content = string
setting = string
})))
admin_password = string
admin_username = string
allocation_method = optional(string, "Static")
allow_extension_operations = optional(bool, true)
asg_id = optional(string, null)
asg_name = optional(string, null)
availability_set_id = optional(string)
availability_zone = optional(string, "random")
boot_diagnostics_storage_account_uri = optional(string, null)
secrets = optional(list(object({
key_vault_id = string
certificates = list(object({
store = string
url = string
}))
})))
computer_name = optional(string)
create_asg = optional(bool, true)
custom_data = optional(string)
custom_source_image_id = optional(string, null)
enable_accelerated_networking = optional(bool, false)
enable_automatic_updates = optional(bool, true)
enable_encryption_at_host = optional(bool, false)
identity_ids = optional(list(string))
identity_type = optional(string)
license_type = optional(string)
location = string
name = string
nic_ipconfig_name = optional(string)
nic_name = optional(string, null)
os_disk = object({
caching = optional(string, "ReadWrite")
os_disk_type = optional(string, "StandardSSD_LRS")
diff_disk_settings = optional(object({
option = string
}))
disk_encryption_set_id = optional(string, null)
disk_size_gb = optional(number, "127")
name = optional(string, null)
secure_vm_disk_encryption_set_id = optional(string, null)
security_encryption_type = optional(string, null)
write_accelerator_enabled = optional(bool, false)
})
patch_mode = optional(string, "AutomaticByOS")
pip_custom_dns_label = optional(string)
pip_name = optional(string)
provision_vm_agent = optional(bool, true)
public_ip_sku = optional(string, null)
rg_name = string
source_image_reference = optional(map(string))
spot_instance = optional(bool, false)
spot_instance_eviction_policy = optional(string)
spot_instance_max_bid_price = optional(string)
static_private_ip = optional(string)
subnet_id = string
termination_notification = optional(object({
enabled = bool
timeout = optional(string)
}))
tags = map(string)
run_vm_command = optional(object({
extension_name = optional(string)
inline = optional(string)
script_file = optional(string)
script_uri = optional(string)
}))
timezone = optional(string)
ultra_ssd_enabled = optional(bool, false)
use_custom_image = optional(bool, false)
use_custom_image_with_plan = optional(bool, false)
use_simple_image = optional(bool, true)
use_simple_image_with_plan = optional(bool, false)
user_data = optional(string, null)
virtual_machine_scale_set_id = optional(string, null)
vm_os_id = optional(string, "")
vm_os_offer = optional(string)
vm_os_publisher = optional(string)
vm_os_simple = optional(string)
vm_os_sku = optional(string)
vm_os_version = optional(string)
vm_size = string
vtpm_enabled = optional(bool, false)
winrm_listener = optional(list(object({
protocol = string
certificate_url = optional(string)
})))
}))
[] no

Outputs

Name Description
asg_ids List of ASG IDs.
asg_names List of ASG Names.
managed_identities Managed identities of the VMs
nic_private_ipv4_addresses List of NIC Private IPv4 Addresses.
public_ip_ids List of Public IP IDs.
public_ip_names List of Public IP Names.
public_ip_values List of Public IP Addresses.
vm_details_map A map where the key is the VM name and the value is another map containing the VM ID and private IP address.
vm_ids List of VM IDs.
vm_names List of VM Names.