diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7139920 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog +All notable changes to this module will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.0] - 2024-01-11 + +### Added + - Initial code that creates a VM +### Changed + +### Removed + +### Fixed \ No newline at end of file diff --git a/LICENSE b/LICENSE index 1ab1286..b8db84a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 q.beyond AG +Copyright (c) 2024 q.beyond AG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 17bd684..0b6069e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Module -[](https://registry.terraform.io/modules/qbeyond/terraform-module-template/provider/latest) -[](https://github.com/qbeyond/terraform-module-template/blob/main/LICENSE) +# Linux VM +[](https://registry.terraform.io/modules/qbeyond/linux-vm/azurerm/latest) +[](https://github.com/qbeyond/terraform-azurerm-linux-vm/blob/main/LICENSE) ---- -This is a template module. It just showcases how a module should look. This would be a short description of the module. +This module will create a linux virtual machine, a network interface and associates the network interface to the target subnet. Optionally one or more data disks and a public ip can be created and additional network interfaces. ## Usage @@ -12,9 +12,48 @@ This is a template module. It just showcases how a module should look. This woul It's very easy to use! ```hcl provider "azurerm" { - features { + features {} +} +module "virtual_machine" { + source = "../.." + + virtual_machine_config = { + hostname = "CUSTAPP001" + location = azurerm_resource_group.this.location + size = "Standard_B1ms" + os_sku = "22_04-lts-gen2" + os_offer = "0001-com-ubuntu-server-jammy" + os_version = "latest" + os_publisher = "Canonical" + severity_group = "01-second-monday-0300-XCSUFEDTG-reboot" + } + admin_username = "local_admin" + admin_credential = { + admin_password = "H3ll0W0rld!" } + + resource_group_name = azurerm_resource_group.this.name + subnet = azurerm_subnet.this +} + +resource "azurerm_resource_group" "this" { + name = "rg-TestLinuxBasic-tst-01" + location = "westeurope" +} + +resource "azurerm_virtual_network" "this" { + name = "vnet-10-0-0-0-24-${azurerm_resource_group.this.location}" + address_space = ["10.0.0.0/24"] + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name +} + +resource "azurerm_subnet" "this" { + name = "snet-10-0-0-0-24-Test" + resource_group_name = azurerm_resource_group.this.name + virtual_network_name = azurerm_virtual_network.this.name + address_prefixes = ["10.0.0.0/24"] } ``` @@ -22,26 +61,66 @@ provider "azurerm" { | Name | Version | |------|---------| +| [terraform](#requirement\_terraform) | >=1.5.0 | | [azurerm](#requirement\_azurerm) | >= 3.7.0 | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [admin\_credential](#input\_admin\_credential) |
Specify either admin_password or public_key:|
admin_password: Password of the local administrator.
public_key: SSH public key file (e.g. file(id_rsa.pub))
object({| n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group where the resources will be created. | `string` | n/a | yes | +| [subnet](#input\_subnet) | The variable takes the subnet as input and takes the id and the address prefix for further configuration. |
admin_password = optional(string)
public_key = optional(string)
})
object({| n/a | yes | +| [virtual\_machine\_config](#input\_virtual\_machine\_config) |
id = string
address_prefixes = list(string)
})
hostname: Name of system hostname.|
size: The size of the vm. Possible values can be seen here: https://learn.microsoft.com/en-us/azure/virtual-machines/sizes
location: The location of the virtual machine.
os_sku: (Required) The os that will be running on the vm.
os_offer: (Required) Specifies the offer of the image used to create the virtual machines. Changing this forces a new resource to be created.
os_version: (Required) Optionally specify an os version for the chosen sku.
os_publisher: (Required) Specifies the Publisher of the Marketplace Image this Virtual Machine should be created from. Changing this forces a new resource to be created.
os_disk_caching: Optionally change the caching option of the os disk. Defaults to ReadWrite.
os_disk_size_gb: Optionally change the size of the os disk. Defaults to be specified by image.
os_disk_storage_type: Optionally change the os_disk_storage_type. Defaults to StandardSSD_LRS.
zone: Optionally specify an availibility zone for the vm. Values 1, 2 or 3.
availability_set_id: Optionally specify an availibility set for the vm. Not compatible with zone.
os_disk_write_accelerator_enabled: Optionally activate write accelaration for the os disk. Can only
be activated on Premium disks and caching deactivated. Defaults to false.
proximity_placement_group_id: (Optional) The ID of the Proximity Placement Group which the Virtual Machine should be assigned to.
severity_group: (Required) Sets tag 'Severity Group Monthly' to a specific time and date when an update will be done automatically.
update_allowed: Sets tag 'Update allowed' to yes or no to specify if this VM should currently receive updates.
object({| n/a | yes | +| [additional\_network\_interface\_ids](#input\_additional\_network\_interface\_ids) | List of ids for additional azurerm\_network\_interface. | `list(string)` | `[]` | no | +| [admin\_username](#input\_admin\_username) | Optionally choose the admin\_username of the vm. Defaults to loc\_sysadmin. | `string` | `"loc_sysadmin"` | no | +| [data\_disks](#input\_data\_disks) |
hostname = string
size = string
location = string
os_sku = string
os_offer = string
os_version = string
os_publisher = string
os_disk_caching = optional(string, "ReadWrite")
os_disk_size_gb = optional(number)
os_disk_storage_type = optional(string, "Premium_LRS")
os_disk_write_accelerator_enabled = optional(bool, false)
zone = optional(number)
availability_set_id = optional(string)
proximity_placement_group_id = optional(string)
severity_group = string
update_allowed = optional(bool, true)
})
|= {
lun: Number of the lun.
disk_size_gb: The size of the data disk.
storage_account_type: Optionally change the storage_account_type. Defaults to StandardSSD_LRS.
caching: Optionally activate disk caching. Defaults to None.
create_option: Optionally change the create option. Defaults to Empty disk.
write_accelerator_enabled: Optionally activate write accelaration for the data disk. Can only
be activated on Premium disks and caching deactivated. Defaults to false.
on_demand_bursting_enabled: Optionally activate disk bursting. Only for Premium disk. Default false.
}
map(object({| `{}` | no | +| [name\_overrides](#input\_name\_overrides) | Possibility to override names that will be generated according to q.beyond naming convention. |
lun = number
disk_size_gb = number
caching = optional(string, "ReadWrite")
create_option = optional(string, "Empty")
storage_account_type = optional(string, "Premium_LRS")
write_accelerator_enabled = optional(bool, false)
on_demand_bursting_enabled = optional(bool, false)
}))
object({| `{}` | no | +| [nic\_config](#input\_nic\_config) |
nic = optional(string)
nic_ip_config = optional(string)
public_ip = optional(string)
virtual_machine = optional(string)
os_disk = optional(string)
data_disks = optional(map(string), {})
})
private_ip: Optioanlly specify a private ip to use. Otherwise it will be allocated dynamically.|
dns_servers: Optionally specify a list of dns servers for the nic.
enable_accelerated_networking: Enabled Accelerated networking (SR-IOV) on the NIC. The machine SKU must support this feature.
nsg: Although it is discouraged you can optionally assign an NSG to the NIC. Optionally specify a NSG object.
object({| `{}` | no | +| [public\_ip\_config](#input\_public\_ip\_config) |
private_ip = optional(string)
dns_servers = optional(list(string))
enable_accelerated_networking = optional(bool, false)
nsg = optional(object({
id = string
}))
})
allocation_method: The allocation method of the public ip that will be created. Defaults to static.|
stage: The stage of this PIP. Ex: prd, dev, tst, ...
object({| `null` | no | +| [tags](#input\_tags) | A map of tags that will be set on every resource this module creates. | `map(string)` | `{}` | no | ## Outputs -No outputs. -## Resource types +| Name | Description | +|------|-------------| +| [data\_disks](#output\_data\_disks) | n/a | +| [virtual\_machine](#output\_virtual\_machine) | n/a | -No resources. + ## Resource types + | Type | Used | + |------|-------| + | [azurerm_linux_virtual_machine](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine) | 1 | + | [azurerm_managed_disk](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk) | 1 | + | [azurerm_network_interface](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | 1 | + | [azurerm_network_interface_security_group_association](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_security_group_association) | 1 | + | [azurerm_public_ip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | 1 | + | [azurerm_virtual_machine_data_disk_attachment](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | 1 | + **`Used` only includes resource blocks.** `for_each` and `count` meta arguments, as well as resource blocks of modules are not considered. + ## Modules No modules. -## Resources by Files -No resources. + ## Resources by Files + + ### data_disk.tf + + | Name | Type | + |------|------| + | [azurerm_managed_disk.data_disk](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk) | resource | + | [azurerm_virtual_machine_data_disk_attachment.data_disk](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | resource | + + ### main.tf + | Name | Type | + |------|------| + | [azurerm_linux_virtual_machine.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine) | resource | + | [azurerm_network_interface.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource | + | [azurerm_network_interface_security_group_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_security_group_association) | resource | + | [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | + ## Contribute diff --git a/data_disk.tf b/data_disk.tf new file mode 100644 index 0000000..9a976ac --- /dev/null +++ b/data_disk.tf @@ -0,0 +1,29 @@ +resource "azurerm_managed_disk" "data_disk" { + for_each = var.data_disks + name = lookup(var.name_overrides.data_disks, each.key, "disk-${var.virtual_machine_config.hostname}-${each.key}") + location = var.virtual_machine_config.location + resource_group_name = var.resource_group_name + zone = var.virtual_machine_config.zone + storage_account_type = each.value["storage_account_type"] + create_option = each.value["create_option"] + disk_size_gb = each.value["disk_size_gb"] + on_demand_bursting_enabled = each.value["on_demand_bursting_enabled"] + tags = var.tags + + lifecycle { + prevent_destroy = true + } +} + +resource "azurerm_virtual_machine_data_disk_attachment" "data_disk" { + for_each = var.data_disks + managed_disk_id = azurerm_managed_disk.data_disk[each.key].id + virtual_machine_id = azurerm_linux_virtual_machine.this.id + lun = each.value["lun"] + caching = each.value["caching"] + write_accelerator_enabled = each.value["write_accelerator_enabled"] + + lifecycle { + prevent_destroy = true + } +} \ No newline at end of file diff --git a/examples/advanced/.terraform.lock.hcl b/examples/advanced/.terraform.lock.hcl new file mode 100644 index 0000000..ed623db --- /dev/null +++ b/examples/advanced/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.97.1" + constraints = ">= 3.7.0" + hashes = [ + "h1:m5wyoRGjbVfJU2YaGZrN1lfGgjpyuwi7Ykw1uHdwlAg=", + "zh:15171efcc3aa3a37748c502c493cb16ecff603b81ada4499a843574976bac524", + "zh:2ca6c13a4a96f67763ecced0015c7b101ee02d54ea54b28a8df4ae06468071b1", + "zh:2e3c77dbfd8f760132ecef2d6117e939cbea26b96aba5e4d926e7f7f0f7afe72", + "zh:4bc346eece1622be93c73801d8256502b11fd7c2e7f7cea12d048bb9fc9fe900", + "zh:4f1042942ed8d0433680a367527289459d43b0894a51eaba83ac414e80d5187f", + "zh:63e674c31482ae3579ea84daf5b1ba066ce40cb23475f54e17b6b131320a1bec", + "zh:8327148766dcb7a174673729a832c8095d7e137d0e6c7e2a9a01da48b8b73fbe", + "zh:851b3ae417059a80c7813e7f0063298a590a42f056004f2c2558ea14061c207e", + "zh:ac081b48907139c121a422ae9b1f40fc72c6aaaeb05cbdbf848102a6a5f426f4", + "zh:dc1d663df2d95e4ba91070ceb20d3560b6ea5c465d39c57a5979319302643e41", + "zh:ed26457367cbbb94237e935d297cb31b5687f9abf697377da0ee46974480db9b", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/examples/advanced/id_rsa.pub b/examples/advanced/id_rsa.pub new file mode 100644 index 0000000..59bc09e --- /dev/null +++ b/examples/advanced/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDeA/WEEO04H3b9quI2otmtXZ+sJepU4I+WulTSmakwchv0Nt0ebC0uiFRjHtMgtEM2MzrdkeBDuXkTumexcJ5SaauCbdrHKr0gkprSowe/hkTnJL+SZw3cGA6lPXKxdOIvSW2lFzX1z8QlcODdsfEiO/fLfqDNXW5sUBsQbmrBnGsiqVgXtKu53RWEcALQNvKn0qgPF2+nyCQ2yxwa2iCv1w7SgxMN+plf3Wts7zNn8sEzDwT5uWKi5P+9JKPpLRLS0Q8t2f68vaHkgS0LchPc7MhBG7iWQUDi8piY5Y47CBYW3kK59WOtWmRRMjvUecbQTKt+hQXpAv0n5tPYwBJfrqmfaG4X3l1HVdcpDuFls5ooZaIFPwHGNB7gYMqY1+8smoV4PtaEeOTFtfxe6ABP+Q90LmQNBPQHOtuWwkgDOQiPzNjNDffD7Tx/8g4eLaBUuKOVDUgcg6QDasE0pxSFVUO0BU/8/+rJgZ8cUV0wRX3rbqWbIy1VpOrLEeb46aM= diff --git a/examples/advanced/main.tf b/examples/advanced/main.tf new file mode 100644 index 0000000..6e66995 --- /dev/null +++ b/examples/advanced/main.tf @@ -0,0 +1,149 @@ +provider "azurerm" { + features {} +} + +locals { + hostname = "CUSTAPP007" +} + +module "virtual_machine" { + source = "../.." + public_ip_config = { + enabled = true + allocation_method = "Static" + stage = "tst" + } + nic_config = { + private_ip = "10.0.0.16" + enable_accelerated_networking = true + dns_servers = ["10.0.0.10", "10.0.0.11"] + nsg = azurerm_network_security_group.this + } + virtual_machine_config = { + hostname = local.hostname + location = azurerm_resource_group.this.location + size = "Standard_B2s_v2" + zone = null # Could be the default value "1", or "2" or "3". Not compatible with availability_set_id enabled. + os_sku = "22_04-lts-gen2" + os_offer = "0001-com-ubuntu-server-jammy" + os_version = "latest" + os_publisher = "Canonical" + os_disk_caching = "ReadWrite" + os_disk_storage_type = "StandardSSD_LRS" + os_disk_size_gb = 64 + availability_set_id = azurerm_availability_set.this.id # Not compatible with zone. + os_disk_write_accelerator_enabled = false + proximity_placement_group_id = azurerm_proximity_placement_group.this.id + severity_group = "01-second-monday-0300-XCSUFEDTG-reboot" + tags = { + "Environment" = "prd" + } + } + admin_username = "local_admin" + admin_credential = { + public_key = file("${path.root}/id_rsa.pub") + } + + resource_group_name = azurerm_resource_group.this.name + subnet = azurerm_subnet.this + additional_network_interface_ids = [azurerm_network_interface.additional_nic_01.id] + + data_disks = { + shared01 = { + lun = 1 + tier = "P4" + caching = "None" + disk_size_gb = 513 + create_option = "Empty" + storage_account_type = "Premium_LRS" + write_accelerator_enabled = false + on_demand_bursting_enabled = true + } + } + + name_overrides = { + nic = "nic-name-override" + nic_ip_config = "nic-ip-config-override" + public_ip = "pip-name-override" + virtual_machine = "vm-name-override" + os_disk = "vm-os-disk-override" + data_disks = { + shared-01 = "vm-datadisk-override" + } + } +} + +resource "azurerm_resource_group" "this" { + name = "rg-TestLinuxAdvanced-tst-01" + location = "westeurope" +} + +resource "azurerm_virtual_network" "this" { + name = "vnet-10-0-0-0-24-${azurerm_resource_group.this.location}" + address_space = ["10.0.0.0/24"] + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name +} + +resource "azurerm_subnet" "this" { + name = "snet-10-0-0-0-24-Test" + resource_group_name = azurerm_resource_group.this.name + virtual_network_name = azurerm_virtual_network.this.name + address_prefixes = ["10.0.0.0/24"] +} + +resource "azurerm_availability_set" "this" { + name = "avs-example-01" + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name + proximity_placement_group_id = azurerm_proximity_placement_group.this.id +} + +resource "azurerm_proximity_placement_group" "this" { + name = "ppg-Example-test-${azurerm_resource_group.this.location}-01" + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name + + lifecycle { + ignore_changes = [tags] + } +} + +resource "azurerm_network_interface" "additional_nic_01" { + name = "nic-${local.hostname}-${replace(element(azurerm_virtual_network.this.address_space, 0), "/[./]/", "-")}-02" + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name + dns_servers = [] + + ip_configuration { + name = "ip-nic-01" + subnet_id = azurerm_subnet.this.id + private_ip_address_allocation = "Dynamic" + private_ip_address = null + public_ip_address_id = null + } + + lifecycle { + ignore_changes = [ + tags + ] + } +} + +resource "azurerm_network_security_group" "this" { + name = "nsg-${trimprefix(azurerm_network_interface.additional_nic_01.name, "nic-")}-Example-Test" + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name + + security_rule { + name = "example" + priority = 100 + direction = "Outbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } +} diff --git a/examples/basic/.terraform.lock.hcl b/examples/basic/.terraform.lock.hcl new file mode 100644 index 0000000..ed623db --- /dev/null +++ b/examples/basic/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.97.1" + constraints = ">= 3.7.0" + hashes = [ + "h1:m5wyoRGjbVfJU2YaGZrN1lfGgjpyuwi7Ykw1uHdwlAg=", + "zh:15171efcc3aa3a37748c502c493cb16ecff603b81ada4499a843574976bac524", + "zh:2ca6c13a4a96f67763ecced0015c7b101ee02d54ea54b28a8df4ae06468071b1", + "zh:2e3c77dbfd8f760132ecef2d6117e939cbea26b96aba5e4d926e7f7f0f7afe72", + "zh:4bc346eece1622be93c73801d8256502b11fd7c2e7f7cea12d048bb9fc9fe900", + "zh:4f1042942ed8d0433680a367527289459d43b0894a51eaba83ac414e80d5187f", + "zh:63e674c31482ae3579ea84daf5b1ba066ce40cb23475f54e17b6b131320a1bec", + "zh:8327148766dcb7a174673729a832c8095d7e137d0e6c7e2a9a01da48b8b73fbe", + "zh:851b3ae417059a80c7813e7f0063298a590a42f056004f2c2558ea14061c207e", + "zh:ac081b48907139c121a422ae9b1f40fc72c6aaaeb05cbdbf848102a6a5f426f4", + "zh:dc1d663df2d95e4ba91070ceb20d3560b6ea5c465d39c57a5979319302643e41", + "zh:ed26457367cbbb94237e935d297cb31b5687f9abf697377da0ee46974480db9b", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/examples/basic/main.tf b/examples/basic/main.tf index f4a573d..4b62d6e 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -1,5 +1,44 @@ provider "azurerm" { - features { + features {} +} +module "virtual_machine" { + source = "../.." + + virtual_machine_config = { + hostname = "CUSTAPP001" + location = azurerm_resource_group.this.location + size = "Standard_B1ms" + os_sku = "22_04-lts-gen2" + os_offer = "0001-com-ubuntu-server-jammy" + os_version = "latest" + os_publisher = "Canonical" + severity_group = "01-second-monday-0300-XCSUFEDTG-reboot" + } + admin_username = "local_admin" + admin_credential = { + admin_password = "H3ll0W0rld!" } -} \ No newline at end of file + + resource_group_name = azurerm_resource_group.this.name + subnet = azurerm_subnet.this +} + +resource "azurerm_resource_group" "this" { + name = "rg-TestLinuxBasic-tst-01" + location = "westeurope" +} + +resource "azurerm_virtual_network" "this" { + name = "vnet-10-0-0-0-24-${azurerm_resource_group.this.location}" + address_space = ["10.0.0.0/24"] + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name +} + +resource "azurerm_subnet" "this" { + name = "snet-10-0-0-0-24-Test" + resource_group_name = azurerm_resource_group.this.name + virtual_network_name = azurerm_virtual_network.this.name + address_prefixes = ["10.0.0.0/24"] +} diff --git a/examples/write_accelerator/.terraform.lock.hcl b/examples/write_accelerator/.terraform.lock.hcl new file mode 100644 index 0000000..ed623db --- /dev/null +++ b/examples/write_accelerator/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.97.1" + constraints = ">= 3.7.0" + hashes = [ + "h1:m5wyoRGjbVfJU2YaGZrN1lfGgjpyuwi7Ykw1uHdwlAg=", + "zh:15171efcc3aa3a37748c502c493cb16ecff603b81ada4499a843574976bac524", + "zh:2ca6c13a4a96f67763ecced0015c7b101ee02d54ea54b28a8df4ae06468071b1", + "zh:2e3c77dbfd8f760132ecef2d6117e939cbea26b96aba5e4d926e7f7f0f7afe72", + "zh:4bc346eece1622be93c73801d8256502b11fd7c2e7f7cea12d048bb9fc9fe900", + "zh:4f1042942ed8d0433680a367527289459d43b0894a51eaba83ac414e80d5187f", + "zh:63e674c31482ae3579ea84daf5b1ba066ce40cb23475f54e17b6b131320a1bec", + "zh:8327148766dcb7a174673729a832c8095d7e137d0e6c7e2a9a01da48b8b73fbe", + "zh:851b3ae417059a80c7813e7f0063298a590a42f056004f2c2558ea14061c207e", + "zh:ac081b48907139c121a422ae9b1f40fc72c6aaaeb05cbdbf848102a6a5f426f4", + "zh:dc1d663df2d95e4ba91070ceb20d3560b6ea5c465d39c57a5979319302643e41", + "zh:ed26457367cbbb94237e935d297cb31b5687f9abf697377da0ee46974480db9b", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/examples/write_accelerator/main.tf b/examples/write_accelerator/main.tf new file mode 100644 index 0000000..a86e3a3 --- /dev/null +++ b/examples/write_accelerator/main.tf @@ -0,0 +1,76 @@ +provider "azurerm" { + features {} +} + +module "virtual_machine" { + source = "../.." + + virtual_machine_config = { + hostname = "CUSTAPP001" + location = azurerm_resource_group.this.location + size = "Standard_M8ms" + os_sku = "22_04-lts-gen2" + os_offer = "0001-com-ubuntu-server-jammy" + os_version = "latest" + os_publisher = "Canonical" + severity_group = "01-second-monday-0300-XCSUFEDTG-reboot" + } + admin_credential = { + admin_password = "H3ll0W0rld!" + } + stage = "tst" + + data_disks = { + shared01 = { + lun = 1 + tier = "P4" + caching = "ReadOnly" + disk_size_gb = 513 + create_option = "Empty" + storage_account_type = "Premium_LRS" + write_accelerator_enabled = true + on_demand_bursting_enabled = true + } + shared02 = { + lun = 2 + tier = "P4" + caching = "None" + disk_size_gb = 513 + create_option = "Empty" + storage_account_type = "Premium_LRS" + write_accelerator_enabled = true + on_demand_bursting_enabled = true + } + shared03 = { + lun = 3 + tier = "P4" + caching = "ReadWrite" + disk_size_gb = 513 + create_option = "Empty" + storage_account_type = "Premium_LRS" + write_accelerator_enabled = false + on_demand_bursting_enabled = false + } + } + resource_group_name = azurerm_resource_group.this.name + subnet = azurerm_subnet.this +} + +resource "azurerm_resource_group" "this" { + name = "rg-TestLinuxWriteAccelerator-tst-01" + location = "westeurope" +} + +resource "azurerm_virtual_network" "this" { + name = "vnet-10-0-0-0-24-${azurerm_resource_group.this.location}" + address_space = ["10.0.0.0/24"] + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name +} + +resource "azurerm_subnet" "this" { + name = "snet-10-0-0-0-24-Test" + resource_group_name = azurerm_resource_group.this.name + virtual_network_name = azurerm_virtual_network.this.name + address_prefixes = ["10.0.0.0/24"] +} diff --git a/locals.tf b/locals.tf index 66e89d3..5a8ab15 100644 --- a/locals.tf +++ b/locals.tf @@ -1,3 +1,17 @@ locals { - + public_ip = { + name = var.public_ip_config != null ? coalesce(var.name_overrides.public_ip, "pip-${var.public_ip_config.stage}-${var.virtual_machine_config.hostname}-01-${var.virtual_machine_config.location}") : "" + } + + nic = { + name = coalesce(var.name_overrides.nic, "nic-${var.virtual_machine_config.hostname}-${replace(var.subnet.address_prefixes[0], "/[./]/", "-")}") + ip_config_name = coalesce(var.name_overrides.nic_ip_config, "internal") + } + + virtual_machine = { + name = coalesce(var.name_overrides.virtual_machine, "vm-${var.virtual_machine_config.hostname}") + tags = merge(var.tags, { "Severity Group Monthly" = var.virtual_machine_config.severity_group, "Update allowed" = local.update_allowed }) + } + os_disk_name = coalesce(var.name_overrides.os_disk, "disk-${var.virtual_machine_config.hostname}-Os") + update_allowed = var.virtual_machine_config.update_allowed ? "yes" : "no" } \ No newline at end of file diff --git a/main.tf b/main.tf index 8b13789..c4e24c0 100644 --- a/main.tf +++ b/main.tf @@ -1 +1,87 @@ +resource "azurerm_public_ip" "this" { + count = var.public_ip_config != null ? 1 : 0 + name = local.public_ip.name + resource_group_name = var.resource_group_name + location = var.virtual_machine_config.location + allocation_method = var.public_ip_config.allocation_method + tags = var.tags +} +resource "azurerm_network_interface" "this" { + name = local.nic.name + location = var.virtual_machine_config.location + resource_group_name = var.resource_group_name + dns_servers = var.nic_config.dns_servers + enable_accelerated_networking = var.nic_config.enable_accelerated_networking + tags = var.tags + + ip_configuration { + name = local.nic.ip_config_name + subnet_id = var.subnet.id + private_ip_address_allocation = var.nic_config.private_ip == null ? "Dynamic" : "Static" + private_ip_address = var.nic_config.private_ip + public_ip_address_id = var.public_ip_config != null ? azurerm_public_ip.this[0].id : null + } +} + +resource "azurerm_network_interface_security_group_association" "this" { + count = var.nic_config.nsg != null ? 1 : 0 + network_interface_id = azurerm_network_interface.this.id + network_security_group_id = var.nic_config.nsg.id +} + +check "no_nsg_on_nic" { + assert { + condition = length(azurerm_network_interface_security_group_association.this) == 0 + error_message = "Direct NSG associations to the NIC should be avoided. Assign to subnet instead." + } +} + +resource "azurerm_linux_virtual_machine" "this" { + name = local.virtual_machine.name + computer_name = var.virtual_machine_config.hostname + location = var.virtual_machine_config.location + resource_group_name = var.resource_group_name + size = var.virtual_machine_config.size + admin_username = var.admin_username + admin_password = var.admin_credential.admin_password + disable_password_authentication = var.admin_credential.admin_password == null + + + dynamic "admin_ssh_key" { + for_each = var.admin_credential.public_key != null ? [1] : [] + content { + username = var.admin_username + public_key = var.admin_credential.public_key + } + } + + os_disk { + name = local.os_disk_name + caching = var.virtual_machine_config.os_disk_caching + disk_size_gb = var.virtual_machine_config.os_disk_size_gb + storage_account_type = var.virtual_machine_config.os_disk_storage_type + write_accelerator_enabled = var.virtual_machine_config.os_disk_write_accelerator_enabled + } + + source_image_reference { + publisher = var.virtual_machine_config.os_publisher + offer = var.virtual_machine_config.os_offer + sku = var.virtual_machine_config.os_sku + version = var.virtual_machine_config.os_version + } + + proximity_placement_group_id = var.virtual_machine_config.proximity_placement_group_id + network_interface_ids = concat([azurerm_network_interface.this.id], var.additional_network_interface_ids) + availability_set_id = var.virtual_machine_config.availability_set_id + zone = var.virtual_machine_config.zone + tags = local.virtual_machine.tags + + lifecycle { + prevent_destroy = true + ignore_changes = [ + # Ignore policy assigned managed identities + identity + ] + } +} diff --git a/outputs.tf b/outputs.tf index e69de29..8d8ebac 100644 --- a/outputs.tf +++ b/outputs.tf @@ -0,0 +1,7 @@ +output "virtual_machine" { + value = azurerm_linux_virtual_machine.this +} + +output "data_disks" { + value = azurerm_managed_disk.data_disk +} \ No newline at end of file diff --git a/terraform.tf b/terraform.tf index 47f36fb..320f9a4 100644 --- a/terraform.tf +++ b/terraform.tf @@ -1,8 +1,9 @@ terraform { + required_version = ">=1.5.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = ">= 3.7.0" } } -} +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index e69de29..536fd2c 100644 --- a/variables.tf +++ b/variables.tf @@ -0,0 +1,229 @@ +variable "public_ip_config" { + type = object({ + allocation_method = optional(string, "Static") + stage = string + }) + default = null + validation { + condition = var.public_ip_config != null ? contains(["Static", "Dynamic"], var.public_ip_config.allocation_method) : true + error_message = "Allocation method must be Static or Dynamic" + } + description = <<-DOC + ``` + allocation_method: The allocation method of the public ip that will be created. Defaults to static. + stage: The stage of this PIP. Ex: prd, dev, tst, ... + ``` + DOC +} + +# nsg needs to be an object to use the count object in main.tf. +variable "nic_config" { + type = object({ + private_ip = optional(string) + dns_servers = optional(list(string)) + enable_accelerated_networking = optional(bool, false) + nsg = optional(object({ + id = string + })) + }) + default = {} + nullable = false + description = <<-DOC + ``` + private_ip: Optioanlly specify a private ip to use. Otherwise it will be allocated dynamically. + dns_servers: Optionally specify a list of dns servers for the nic. + enable_accelerated_networking: Enabled Accelerated networking (SR-IOV) on the NIC. The machine SKU must support this feature. + nsg: Although it is discouraged you can optionally assign an NSG to the NIC. Optionally specify a NSG object. + ``` + DOC +} + +variable "additional_network_interface_ids" { + type = list(string) + default = [] + nullable = false + description = "List of ids for additional azurerm_network_interface." +} + +variable "subnet" { + type = object({ + id = string + address_prefixes = list(string) + }) + nullable = false + description = "The variable takes the subnet as input and takes the id and the address prefix for further configuration." +} + +variable "admin_username" { + type = string + default = "loc_sysadmin" + nullable = false + description = "Optionally choose the admin_username of the vm. Defaults to loc_sysadmin." +} + +variable "admin_credential" { + type = object({ + admin_password = optional(string) + public_key = optional(string) + }) + + validation { + condition = (var.admin_credential.admin_password != null && var.admin_credential.public_key == null) || (var.admin_credential.admin_password == null && var.admin_credential.public_key != null) + error_message = "Use admin password or public ssh key." + } + sensitive = true + description = <<-DOC + ``` + Specify either admin_password or public_key: + admin_password: Password of the local administrator. + public_key: SSH public key file (e.g. file(id_rsa.pub)) + ``` + DOC +} + +variable "virtual_machine_config" { + type = object({ + hostname = string + size = string + location = string + os_sku = string + os_offer = string + os_version = string + os_publisher = string + os_disk_caching = optional(string, "ReadWrite") + os_disk_size_gb = optional(number) + os_disk_storage_type = optional(string, "Premium_LRS") + os_disk_write_accelerator_enabled = optional(bool, false) + zone = optional(number) + availability_set_id = optional(string) + proximity_placement_group_id = optional(string) + severity_group = string + update_allowed = optional(bool, true) + }) + validation { + condition = contains(["None", "ReadOnly", "ReadWrite"], var.virtual_machine_config.os_disk_caching) + error_message = "Possible values are None, ReadOnly and ReadWrite for os_disk_caching." + } + validation { + condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS", "StandardSSD_ZRS", "Premium_ZRS"], var.virtual_machine_config.os_disk_storage_type) + error_message = "Possible values are Standard_LRS, StandardSSD_LRS, Premium_LRS, StandardSSD_ZRS and Premium_ZRS for os_disk_storage_type." + } + validation { + condition = ( + var.virtual_machine_config.os_disk_write_accelerator_enabled == true && + contains(["Premium_LRS", "Premium_ZRS"], var.virtual_machine_config.os_disk_storage_type) && + contains(["None", "ReadOnly"], var.virtual_machine_config.os_disk_caching) + ) || ( + var.virtual_machine_config.os_disk_write_accelerator_enabled == false + ) + error_message = "os_disk_write_accelerator_enabled can only be activated on Premium disks and caching deactivated." + } + validation { + condition = var.virtual_machine_config.zone == null || var.virtual_machine_config.zone == 1 || var.virtual_machine_config.zone == 2 || var.virtual_machine_config.zone == 3 + error_message = "Zone, can only be empty, 1, 2 or 3." + } + nullable = false + description = <<-DOC + ``` + hostname: Name of system hostname. + size: The size of the vm. Possible values can be seen here: https://learn.microsoft.com/en-us/azure/virtual-machines/sizes + location: The location of the virtual machine. + os_sku: (Required) The os that will be running on the vm. + os_offer: (Required) Specifies the offer of the image used to create the virtual machines. Changing this forces a new resource to be created. + os_version: (Required) Optionally specify an os version for the chosen sku. + os_publisher: (Required) Specifies the Publisher of the Marketplace Image this Virtual Machine should be created from. Changing this forces a new resource to be created. + os_disk_caching: Optionally change the caching option of the os disk. Defaults to ReadWrite. + os_disk_size_gb: Optionally change the size of the os disk. Defaults to be specified by image. + os_disk_storage_type: Optionally change the os_disk_storage_type. Defaults to StandardSSD_LRS. + zone: Optionally specify an availibility zone for the vm. Values 1, 2 or 3. + availability_set_id: Optionally specify an availibility set for the vm. Not compatible with zone. + os_disk_write_accelerator_enabled: Optionally activate write accelaration for the os disk. Can only + be activated on Premium disks and caching deactivated. Defaults to false. + proximity_placement_group_id: (Optional) The ID of the Proximity Placement Group which the Virtual Machine should be assigned to. + severity_group: (Required) Sets tag 'Severity Group Monthly' to a specific time and date when an update will be done automatically. + update_allowed: Sets tag 'Update allowed' to yes or no to specify if this VM should currently receive updates. + ``` + DOC +} + +variable "data_disks" { + type = map(object({ + lun = number + disk_size_gb = number + caching = optional(string, "ReadWrite") + create_option = optional(string, "Empty") + storage_account_type = optional(string, "Premium_LRS") + write_accelerator_enabled = optional(bool, false) + on_demand_bursting_enabled = optional(bool, false) + })) + validation { + condition = length([for v in var.data_disks : v.lun]) == length(distinct([for v in var.data_disks : v.lun])) + error_message = "One or more of the lun parameters in the map are duplicates." + } + validation { + condition = alltrue([for o in var.data_disks : contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS", "StandardSSD_ZRS", "Premium_ZRS"], o.storage_account_type)]) + error_message = "Possible values are Standard_LRS, StandardSSD_LRS, Premium_LRS, StandardSSD_ZRS and Premium_ZRS for storage_account_type" + } + validation { + condition = alltrue([for o in var.data_disks : ( + (o.write_accelerator_enabled == true && contains(["Premium_LRS", "Premium_ZRS"], o.storage_account_type) && contains(["None", "ReadOnly"], o.caching)) || + (o.write_accelerator_enabled == false) + )]) + error_message = "write_accelerator_enabled, can only be activated on Premium disks and caching deactivated." + } + validation { + condition = alltrue([for o in var.data_disks : ( + (o.on_demand_bursting_enabled == true && contains(["Premium_LRS", "Premium_ZRS"], o.storage_account_type)) || + (o.on_demand_bursting_enabled == false) + )]) + error_message = "If enable on demand bursting, possible storage_account_type values are Premium_LRS and Premium_ZRS." + } + validation { + condition = alltrue([for k, v in var.data_disks : !strcontains(k, "-")]) + error_message = "Logical Name can't contain a '-'" + } + + default = {} + nullable = false + description = <<-DOC + ``` +
allocation_method = optional(string, "Static")
stage = string
})