diff --git a/examples/templates/azure-linux/README.md b/examples/templates/azure-linux/README.md new file mode 100644 index 0000000000000..ce84b5e9a2e59 --- /dev/null +++ b/examples/templates/azure-linux/README.md @@ -0,0 +1,17 @@ +--- +name: Develop in Linux on Azure +description: Get started with Linux development on Microsoft Azure. +tags: [cloud, azure, linux] +--- + +# azure-linux + +To get started, run `coder templates init`. When prompted, select this template. +Follow the on-screen instructions to proceed. + +## Authentication + +This template assumes that coderd is run in an environment that is authenticated +with Azure. For example, run `az login` then `az account set --subscription=` +to import credentials on the system and user running coderd. For other ways to +authenticate [consult the Terraform docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure). diff --git a/examples/templates/azure-linux/cloud-config.yaml.tftpl b/examples/templates/azure-linux/cloud-config.yaml.tftpl new file mode 100644 index 0000000000000..75006778331b8 --- /dev/null +++ b/examples/templates/azure-linux/cloud-config.yaml.tftpl @@ -0,0 +1,56 @@ +#cloud-config +cloud_final_modules: +- [scripts-user, always] +bootcmd: + # work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117 + - until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done +device_aliases: + homedir: /dev/disk/azure/scsi1/lun10 +disk_setup: + homedir: + table_type: gpt + layout: true +fs_setup: + - label: coder_home + filesystem: ext4 + device: homedir.1 +mounts: + - ["LABEL=coder_home", "/home/${username}"] +hostname: ${hostname} +users: + - name: ${username} + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + groups: sudo + shell: /bin/bash +packages: + - git +write_files: + - path: /opt/coder/init + permissions: "0755" + encoding: b64 + content: ${init_script} + - path: /etc/systemd/system/coder-agent.service + permissions: "0644" + content: | + [Unit] + Description=Coder Agent + After=network-online.target + Wants=network-online.target + + [Service] + User=${username} + ExecStart=/opt/coder/init + Restart=always + RestartSec=10 + TimeoutStopSec=90 + KillMode=process + + OOMScoreAdjust=-900 + SyslogIdentifier=coder-agent + + [Install] + WantedBy=multi-user.target +runcmd: + - chown ${username}:${username} /home/${username} + - systemctl enable coder-agent + - systemctl start coder-agent diff --git a/examples/templates/azure-linux/main.tf b/examples/templates/azure-linux/main.tf new file mode 100644 index 0000000000000..2406d6a5901bb --- /dev/null +++ b/examples/templates/azure-linux/main.tf @@ -0,0 +1,211 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "0.4.3" + } + azurerm = { + source = "hashicorp/azurerm" + version = "=3.0.0" + } + } +} + +variable "location" { + description = "What location should your workspace live in?" + default = "eastus" + validation { + condition = contains([ + "eastus", + "southcentralus", + "westus2", + "australiaeast", + "southeastasia", + "northeurope", + "westeurope", + "centralindia", + "eastasia", + "japaneast", + "brazilsouth", + "asia", + "asiapacific", + "australia", + "brazil", + "india", + "japan", + "southafrica", + "switzerland", + "uae", + ], var.location) + error_message = "Invalid location!" + } +} + +variable "instance_type" { + description = "What instance type should your workspace use?" + default = "Standard_B4ms" + validation { + condition = contains([ + "Standard_B1ms", + "Standard_B2ms", + "Standard_B4ms", + "Standard_B8ms", + "Standard_B12ms", + "Standard_B16ms", + "Standard_D2as_v5", + "Standard_D4as_v5", + "Standard_D8as_v5", + "Standard_D16as_v5", + "Standard_D32as_v5", + ], var.instance_type) + error_message = "Invalid instance type!" + } +} + +variable "home_size" { + type = number + description = "How large would you like your home volume to be (in GB)?" + default = 20 + validation { + condition = var.home_size >= 1 + error_message = "Value must be greater than or equal to 1." + } +} + +provider "azurerm" { + features {} +} + +data "coder_workspace" "me" { +} + +resource "coder_agent" "main" { + arch = "amd64" + os = "linux" + auth = "azure-instance-identity" +} + +locals { + prefix = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}" + + userdata = templatefile("cloud-config.yaml.tftpl", { + username = lower(substr(data.coder_workspace.me.owner, 0, 32)) + init_script = base64encode(coder_agent.main.init_script) + hostname = lower(data.coder_workspace.me.name) + }) +} + +resource "azurerm_resource_group" "main" { + name = "${local.prefix}-resources" + location = var.location + + tags = { + Coder_Provisioned = "true" + } +} + +// Uncomment here and in the azurerm_network_interface resource to obtain a public IP +#resource "azurerm_public_ip" "main" { +# name = "publicip" +# resource_group_name = azurerm_resource_group.main.name +# location = azurerm_resource_group.main.location +# allocation_method = "Static" +# +# tags = { +# Coder_Provisioned = "true" +# } +#} + +resource "azurerm_virtual_network" "main" { + name = "network" + address_space = ["10.0.0.0/24"] + location = azurerm_resource_group.main.location + resource_group_name = azurerm_resource_group.main.name + + tags = { + Coder_Provisioned = "true" + } +} + +resource "azurerm_subnet" "internal" { + name = "internal" + resource_group_name = azurerm_resource_group.main.name + virtual_network_name = azurerm_virtual_network.main.name + address_prefixes = ["10.0.0.0/29"] +} + +resource "azurerm_network_interface" "main" { + name = "nic" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.internal.id + private_ip_address_allocation = "Dynamic" + // Uncomment for public IP address as well as azurerm_public_ip resource above + //public_ip_address_id = azurerm_public_ip.main.id + } + + tags = { + Coder_Provisioned = "true" + } +} + +resource "azurerm_managed_disk" "home" { + create_option = "Empty" + location = azurerm_resource_group.main.location + name = "home" + resource_group_name = azurerm_resource_group.main.name + storage_account_type = "StandardSSD_LRS" + disk_size_gb = var.home_size +} + +// azurerm requires an SSH key (or password) for an admin user or it won't start a VM. However, +// cloud-init overwrites this anyway, so we'll just use a dummy SSH key. +resource "tls_private_key" "dummy" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "azurerm_linux_virtual_machine" "main" { + count = data.coder_workspace.me.transition == "start" ? 1 : 0 + name = "vm" + resource_group_name = azurerm_resource_group.main.name + location = azurerm_resource_group.main.location + size = var.instance_type + // cloud-init overwrites this, so the value here doesn't matter + admin_username = "adminuser" + admin_ssh_key { + public_key = tls_private_key.dummy.public_key_openssh + username = "adminuser" + } + + network_interface_ids = [ + azurerm_network_interface.main.id, + ] + computer_name = lower(data.coder_workspace.me.name) + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-focal" + sku = "20_04-lts-gen2" + version = "latest" + } + user_data = base64encode(local.userdata) + + tags = { + Coder_Provisioned = "true" + } +} + +resource "azurerm_virtual_machine_data_disk_attachment" "home" { + count = data.coder_workspace.me.transition == "start" ? 1 : 0 + managed_disk_id = azurerm_managed_disk.home.id + virtual_machine_id = azurerm_linux_virtual_machine.main[0].id + lun = "10" + caching = "ReadWrite" +}