mirror of https://github.com/coder/coder.git
feat: Windows on Azure example template (#7469)
* WIP Azure template for windows machine Signed-off-by: Spike Curtis <spike@coder.com> * WIP windows uses data disk Signed-off-by: Spike Curtis <spike@coder.com> * Data drive working Signed-off-by: Spike Curtis <spike@coder.com> * Add az cli commands to start & stop Signed-off-by: Spike Curtis <spike@coder.com> * Remove commented line Signed-off-by: Spike Curtis <spike@coder.com> * Prettierify Signed-off-by: Spike Curtis <spike@coder.com> --------- Signed-off-by: Spike Curtis <spike@coder.com>
This commit is contained in:
parent
b5ad628460
commit
d35a458767
|
@ -0,0 +1,12 @@
|
|||
<FirstLogonCommands>
|
||||
<SynchronousCommand>
|
||||
<CommandLine>cmd /c "copy C:\AzureData\CustomData.bin C:\AzureData\Initialize.ps1"</CommandLine>
|
||||
<Description>Copy Initialize.ps1 to file from CustomData</Description>
|
||||
<Order>3</Order>
|
||||
</SynchronousCommand>
|
||||
<SynchronousCommand>
|
||||
<CommandLine>powershell.exe -sta -ExecutionPolicy Unrestricted -Command "C:\AzureData\Initialize.ps1 *> C:\AzureData\Initialize.log"</CommandLine>
|
||||
<Description>Execute Initialize.ps1 script</Description>
|
||||
<Order>4</Order>
|
||||
</SynchronousCommand>
|
||||
</FirstLogonCommands>
|
|
@ -0,0 +1,73 @@
|
|||
# This script gets run once when the VM is first created.
|
||||
|
||||
# Initialize the data disk & home directory.
|
||||
$disk = Get-Disk -Number 2
|
||||
if ($disk.PartitionStyle -Eq 'RAW')
|
||||
{
|
||||
"Initializing data disk"
|
||||
$disk | Initialize-Disk
|
||||
} else {
|
||||
"data disk already initialized"
|
||||
}
|
||||
|
||||
$partitions = Get-Partition -DiskNumber $disk.Number | Where-Object Type -Ne 'Reserved'
|
||||
if ($partitions.Count -Eq 0) {
|
||||
"Creating partition on data disk"
|
||||
$partition = New-Partition -DiskNumber $disk.Number -UseMaximumSize
|
||||
} else {
|
||||
$partition = $partitions[0]
|
||||
$s = "data disk already has partition of size {0:n1} GiB" -f ($partition.Size / 1073741824)
|
||||
Write-Output $s
|
||||
}
|
||||
|
||||
$volume = Get-Volume -Partition $partition
|
||||
if ($volume.FileSystemType -Eq 'Unknown')
|
||||
{
|
||||
"Formatting data disk"
|
||||
Format-Volume -InputObject $volume -FileSystem NTFS -Confirm:$false
|
||||
} else {
|
||||
"data disk is already formatted"
|
||||
}
|
||||
|
||||
# Mount the partition
|
||||
Add-PartitionAccessPath -InputObject $partition -AccessPath "F:"
|
||||
|
||||
# Enable RDP
|
||||
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 0
|
||||
# Enable RDP through Windows Firewall
|
||||
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
|
||||
# Disable Network Level Authentication (NLA)
|
||||
# Clients will connect via Coder's tunnel
|
||||
(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -ComputerName $env:COMPUTERNAME -Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0)
|
||||
|
||||
# Install Chocolatey package manager
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
# Reload path so sessions include "choco" and "refreshenv"
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
|
||||
# Install Git and reload path
|
||||
choco install -y git
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
|
||||
# Set protocol to TLS1.2 for agent download
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
# Set Coder Agent to run immediately, and on each restart
|
||||
$init_script = @'
|
||||
${init_script}
|
||||
'@
|
||||
Out-File -FilePath "C:\AzureData\CoderAgent.ps1" -InputObject $init_script
|
||||
$task = @{
|
||||
TaskName = 'CoderAgent'
|
||||
Action = (New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-sta -ExecutionPolicy Unrestricted -Command "C:\AzureData\CoderAgent.ps1 *>> C:\AzureData\CoderAgent.log"')
|
||||
Trigger = (New-ScheduledTaskTrigger -AtStartup), (New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(15))
|
||||
Settings = (New-ScheduledTaskSettingsSet -DontStopOnIdleEnd -ExecutionTimeLimit ([TimeSpan]::FromDays(3650)) -Compatibility Win8)
|
||||
Principal = (New-ScheduledTaskPrincipal -UserId 'vm\coder' -RunLevel Highest -LogonType S4U)
|
||||
}
|
||||
Register-ScheduledTask @task -Force
|
||||
|
||||
# Additional Chocolatey package installs (optional, uncomment to enable)
|
||||
# choco feature enable -n=allowGlobalConfirmation
|
||||
# choco install visualstudio2022community --package-parameters "--add=Microsoft.VisualStudio.Workload.ManagedDesktop;includeRecommended --passive --locale en-US"
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
name: Develop in Windows on Azure
|
||||
description: Get started with Windows development on Microsoft Azure.
|
||||
tags: [cloud, azure, windows]
|
||||
icon: /icon/azure.png
|
||||
---
|
||||
|
||||
# azure-windows
|
||||
|
||||
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=<id>`
|
||||
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).
|
||||
|
||||
## Dependencies
|
||||
|
||||
This template depends on the Azure CLI tool (`az`) to start and stop the Windows VM. Ensure this
|
||||
tool is installed and available in the path on the machine that runs coderd.
|
|
@ -0,0 +1,231 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.7.0"
|
||||
}
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "=3.52.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
provider "coder" {
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
|
||||
data "coder_parameter" "location" {
|
||||
description = "What location should your workspace live in?"
|
||||
display_name = "Location"
|
||||
name = "location"
|
||||
default = "eastus"
|
||||
mutable = false
|
||||
option {
|
||||
value = "eastus"
|
||||
name = "East US"
|
||||
}
|
||||
option {
|
||||
value = "centralus"
|
||||
name = "Central US"
|
||||
}
|
||||
option {
|
||||
value = "southcentralus"
|
||||
name = "South Central US"
|
||||
}
|
||||
option {
|
||||
value = "westus2"
|
||||
name = "West US 2"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "data_disk_size" {
|
||||
description = "Size of your data (F:) drive in GB"
|
||||
display_name = "Data disk size"
|
||||
name = "data_disk_size"
|
||||
default = 20
|
||||
mutable = "false"
|
||||
type = "number"
|
||||
validation {
|
||||
min = 5
|
||||
max = 5000
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = "amd64"
|
||||
auth = "azure-instance-identity"
|
||||
os = "windows"
|
||||
login_before_ready = false
|
||||
}
|
||||
|
||||
resource "random_password" "admin_password" {
|
||||
length = 16
|
||||
special = true
|
||||
# https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/password-must-meet-complexity-requirements#reference
|
||||
# we remove characters that require special handling in XML, as this is how we pass it to the VM
|
||||
# namely: <>&'"
|
||||
override_special = "~!@#$%^*_-+=`|\\(){}[]:;,.?/"
|
||||
}
|
||||
|
||||
locals {
|
||||
prefix = "coder-win"
|
||||
admin_username = "coder"
|
||||
}
|
||||
|
||||
resource "azurerm_resource_group" "main" {
|
||||
name = "${local.prefix}-${data.coder_workspace.me.id}"
|
||||
location = data.coder_parameter.location.value
|
||||
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"
|
||||
}
|
||||
}
|
||||
# Create storage account for boot diagnostics
|
||||
resource "azurerm_storage_account" "my_storage_account" {
|
||||
name = "diag${random_id.storage_id.hex}"
|
||||
location = azurerm_resource_group.main.location
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
}
|
||||
# Generate random text for a unique storage account name
|
||||
resource "random_id" "storage_id" {
|
||||
keepers = {
|
||||
# Generate a new ID only when a new resource group is defined
|
||||
resource_group = azurerm_resource_group.main.name
|
||||
}
|
||||
byte_length = 8
|
||||
}
|
||||
|
||||
resource "azurerm_managed_disk" "data" {
|
||||
name = "data_disk"
|
||||
location = azurerm_resource_group.main.location
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
storage_account_type = "Standard_LRS"
|
||||
create_option = "Empty"
|
||||
disk_size_gb = data.coder_parameter.data_disk_size.value
|
||||
}
|
||||
|
||||
# Create virtual machine
|
||||
resource "azurerm_windows_virtual_machine" "main" {
|
||||
name = "vm"
|
||||
admin_username = local.admin_username
|
||||
admin_password = random_password.admin_password.result
|
||||
location = azurerm_resource_group.main.location
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
network_interface_ids = [azurerm_network_interface.main.id]
|
||||
size = "Standard_DS1_v2"
|
||||
custom_data = base64encode(
|
||||
templatefile("${path.module}/Initialize.ps1.tftpl", { init_script = coder_agent.main.init_script })
|
||||
)
|
||||
os_disk {
|
||||
name = "myOsDisk"
|
||||
caching = "ReadWrite"
|
||||
storage_account_type = "Premium_LRS"
|
||||
}
|
||||
source_image_reference {
|
||||
publisher = "MicrosoftWindowsServer"
|
||||
offer = "WindowsServer"
|
||||
sku = "2022-datacenter-azure-edition"
|
||||
version = "latest"
|
||||
}
|
||||
additional_unattend_content {
|
||||
content = "<AutoLogon><Password><Value>${random_password.admin_password.result}</Value></Password><Enabled>true</Enabled><LogonCount>1</LogonCount><Username>${local.admin_username}</Username></AutoLogon>"
|
||||
setting = "AutoLogon"
|
||||
}
|
||||
additional_unattend_content {
|
||||
content = file("${path.module}/FirstLogonCommands.xml")
|
||||
setting = "FirstLogonCommands"
|
||||
}
|
||||
boot_diagnostics {
|
||||
storage_account_uri = azurerm_storage_account.my_storage_account.primary_blob_endpoint
|
||||
}
|
||||
tags = {
|
||||
Coder_Provisioned = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "rdp_login" {
|
||||
resource_id = azurerm_windows_virtual_machine.main.id
|
||||
item {
|
||||
key = "Username"
|
||||
value = local.admin_username
|
||||
}
|
||||
item {
|
||||
key = "Password"
|
||||
value = random_password.admin_password.result
|
||||
sensitive = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_virtual_machine_data_disk_attachment" "main_data" {
|
||||
managed_disk_id = azurerm_managed_disk.data.id
|
||||
virtual_machine_id = azurerm_windows_virtual_machine.main.id
|
||||
lun = "10"
|
||||
caching = "ReadWrite"
|
||||
}
|
||||
|
||||
# Stop the VM
|
||||
resource "null_resource" "stop_vm" {
|
||||
count = data.coder_workspace.me.transition == "stop" ? 1 : 0
|
||||
depends_on = [azurerm_windows_virtual_machine.main]
|
||||
provisioner "local-exec" {
|
||||
# Use deallocate so the VM is not charged
|
||||
command = "az vm deallocate --ids ${azurerm_windows_virtual_machine.main.id}"
|
||||
}
|
||||
}
|
||||
|
||||
# Start the VM
|
||||
resource "null_resource" "start" {
|
||||
count = data.coder_workspace.me.transition == "start" ? 1 : 0
|
||||
depends_on = [azurerm_windows_virtual_machine.main]
|
||||
provisioner "local-exec" {
|
||||
command = "az vm start --ids ${azurerm_windows_virtual_machine.main.id}"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue