mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add base templates for all major cloud providers (#26634)
Add 6 new base templates to the template builder, covering all major cloud providers and platforms: - **scratch**: Minimal starter template with only `coder_agent` and metadata - **aws-windows**: AWS EC2 Windows instances with PowerShell user_data - **azure-linux**: Azure VMs with cloud-init and managed disk persistence - **gcp-linux**: Google Compute Engine Linux instances with persistent disk - **gcp-windows**: Google Compute Engine Windows instances - **digitalocean-linux**: DigitalOcean Linux droplets with persistent volumes Each base includes `base.json`, `main.tf.tmpl`, `README.md` (with prerequisite markers), and any static files (cloud-init configs). Tests verify all 9 bases load, render without error, and produce valid single-agent declarations. `azure-windows` is deferred; it needs to be registered in the `examples` package first. Depends on #26633 > [!NOTE] > This PR was authored by Coder Agents on behalf of @jeremyruppel. --- NB: This is very much an agent-generated PR and draws completely from base templates that exist in `examples/templates/`. The base.json files are new, so review those, but don't spend any brain tokens on the correctness of the terraform and supporting files: any issues there are issues with the upstream example template
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Last updated 2023-03-14
|
||||
# aws ec2 describe-regions | jq -r '[.Regions[].RegionName] | sort'
|
||||
data "coder_parameter" "region" {
|
||||
name = "region"
|
||||
display_name = "Region"
|
||||
description = "The region to deploy the workspace in."
|
||||
default = "us-east-1"
|
||||
mutable = false
|
||||
option {
|
||||
name = "Asia Pacific (Tokyo)"
|
||||
value = "ap-northeast-1"
|
||||
icon = "/emojis/1f1ef-1f1f5.png"
|
||||
}
|
||||
option {
|
||||
name = "Asia Pacific (Seoul)"
|
||||
value = "ap-northeast-2"
|
||||
icon = "/emojis/1f1f0-1f1f7.png"
|
||||
}
|
||||
option {
|
||||
name = "Asia Pacific (Osaka-Local)"
|
||||
value = "ap-northeast-3"
|
||||
icon = "/emojis/1f1f0-1f1f7.png"
|
||||
}
|
||||
option {
|
||||
name = "Asia Pacific (Mumbai)"
|
||||
value = "ap-south-1"
|
||||
icon = "/emojis/1f1f0-1f1f7.png"
|
||||
}
|
||||
option {
|
||||
name = "Asia Pacific (Singapore)"
|
||||
value = "ap-southeast-1"
|
||||
icon = "/emojis/1f1f0-1f1f7.png"
|
||||
}
|
||||
option {
|
||||
name = "Asia Pacific (Sydney)"
|
||||
value = "ap-southeast-2"
|
||||
icon = "/emojis/1f1f0-1f1f7.png"
|
||||
}
|
||||
option {
|
||||
name = "Canada (Central)"
|
||||
value = "ca-central-1"
|
||||
icon = "/emojis/1f1e8-1f1e6.png"
|
||||
}
|
||||
option {
|
||||
name = "EU (Frankfurt)"
|
||||
value = "eu-central-1"
|
||||
icon = "/emojis/1f1ea-1f1fa.png"
|
||||
}
|
||||
option {
|
||||
name = "EU (Stockholm)"
|
||||
value = "eu-north-1"
|
||||
icon = "/emojis/1f1ea-1f1fa.png"
|
||||
}
|
||||
option {
|
||||
name = "EU (Ireland)"
|
||||
value = "eu-west-1"
|
||||
icon = "/emojis/1f1ea-1f1fa.png"
|
||||
}
|
||||
option {
|
||||
name = "EU (London)"
|
||||
value = "eu-west-2"
|
||||
icon = "/emojis/1f1ea-1f1fa.png"
|
||||
}
|
||||
option {
|
||||
name = "EU (Paris)"
|
||||
value = "eu-west-3"
|
||||
icon = "/emojis/1f1ea-1f1fa.png"
|
||||
}
|
||||
option {
|
||||
name = "South America (São Paulo)"
|
||||
value = "sa-east-1"
|
||||
icon = "/emojis/1f1e7-1f1f7.png"
|
||||
}
|
||||
option {
|
||||
name = "US East (N. Virginia)"
|
||||
value = "us-east-1"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
option {
|
||||
name = "US East (Ohio)"
|
||||
value = "us-east-2"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
option {
|
||||
name = "US West (N. California)"
|
||||
value = "us-west-1"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
option {
|
||||
name = "US West (Oregon)"
|
||||
value = "us-west-2"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "instance_type" {
|
||||
name = "instance_type"
|
||||
display_name = "Instance type"
|
||||
description = "What instance type should your workspace use?"
|
||||
default = "t3.micro"
|
||||
mutable = false
|
||||
option {
|
||||
name = "2 vCPU, 1 GiB RAM"
|
||||
value = "t3.micro"
|
||||
}
|
||||
option {
|
||||
name = "2 vCPU, 2 GiB RAM"
|
||||
value = "t3.small"
|
||||
}
|
||||
option {
|
||||
name = "2 vCPU, 4 GiB RAM"
|
||||
value = "t3.medium"
|
||||
}
|
||||
option {
|
||||
name = "2 vCPU, 8 GiB RAM"
|
||||
value = "t3.large"
|
||||
}
|
||||
option {
|
||||
name = "4 vCPU, 16 GiB RAM"
|
||||
value = "t3.xlarge"
|
||||
}
|
||||
option {
|
||||
name = "8 vCPU, 32 GiB RAM"
|
||||
value = "t3.2xlarge"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = data.coder_parameter.region.value
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {
|
||||
}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
data "aws_ami" "windows" {
|
||||
most_recent = true
|
||||
owners = ["amazon"]
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["Windows_Server-2019-English-Full-Base-*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = "amd64"
|
||||
auth = "aws-instance-identity"
|
||||
os = "windows"
|
||||
}
|
||||
|
||||
locals {
|
||||
|
||||
# User data is used to stop/start AWS instances. See:
|
||||
# https://github.com/hashicorp/terraform-provider-aws/issues/22
|
||||
|
||||
user_data_start = <<EOT
|
||||
<powershell>
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
${coder_agent.main.init_script}
|
||||
</powershell>
|
||||
<persist>true</persist>
|
||||
EOT
|
||||
|
||||
user_data_end = <<EOT
|
||||
<powershell>
|
||||
shutdown /s
|
||||
</powershell>
|
||||
<persist>true</persist>
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "aws_instance" "dev" {
|
||||
ami = data.aws_ami.windows.id
|
||||
availability_zone = "${data.coder_parameter.region.value}a"
|
||||
instance_type = data.coder_parameter.instance_type.value
|
||||
|
||||
user_data = data.coder_workspace.me.transition == "start" ? local.user_data_start : local.user_data_end
|
||||
tags = {
|
||||
Name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
|
||||
# Required if you are using our example policy, see template README
|
||||
Coder_Provisioned = "true"
|
||||
}
|
||||
lifecycle {
|
||||
ignore_changes = [ami]
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "workspace_info" {
|
||||
resource_id = aws_instance.dev.id
|
||||
item {
|
||||
key = "region"
|
||||
value = data.coder_parameter.region.value
|
||||
}
|
||||
item {
|
||||
key = "instance type"
|
||||
value = aws_instance.dev.instance_type
|
||||
}
|
||||
item {
|
||||
key = "disk"
|
||||
value = "${aws_instance.dev.root_block_device[0].volume_size} GiB"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
}
|
||||
cloudinit = {
|
||||
source = "hashicorp/cloudinit"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/azure-region
|
||||
module "azure_region" {
|
||||
source = "registry.coder.com/coder/azure-region/coder"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
default = "eastus"
|
||||
}
|
||||
|
||||
data "coder_parameter" "instance_type" {
|
||||
name = "instance_type"
|
||||
display_name = "Instance type"
|
||||
description = "What instance type should your workspace use?"
|
||||
default = "Standard_B4ms"
|
||||
icon = "/icon/azure.png"
|
||||
mutable = false
|
||||
option {
|
||||
name = "Standard_B1ms (1 vCPU, 2 GiB RAM)"
|
||||
value = "Standard_B1ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B2ms (2 vCPU, 8 GiB RAM)"
|
||||
value = "Standard_B2ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B4ms (4 vCPU, 16 GiB RAM)"
|
||||
value = "Standard_B4ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B8ms (8 vCPU, 32 GiB RAM)"
|
||||
value = "Standard_B8ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B12ms (12 vCPU, 48 GiB RAM)"
|
||||
value = "Standard_B12ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B16ms (16 vCPU, 64 GiB RAM)"
|
||||
value = "Standard_B16ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D2as_v5 (2 vCPU, 8 GiB RAM)"
|
||||
value = "Standard_D2as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D4as_v5 (4 vCPU, 16 GiB RAM)"
|
||||
value = "Standard_D4as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D8as_v5 (8 vCPU, 32 GiB RAM)"
|
||||
value = "Standard_D8as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D16as_v5 (16 vCPU, 64 GiB RAM)"
|
||||
value = "Standard_D16as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D32as_v5 (32 vCPU, 128 GiB RAM)"
|
||||
value = "Standard_D32as_v5"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "home_size" {
|
||||
name = "home_size"
|
||||
display_name = "Home volume size"
|
||||
description = "How large would you like your home volume to be (in GB)?"
|
||||
default = 20
|
||||
type = "number"
|
||||
icon = "/icon/azure.png"
|
||||
mutable = false
|
||||
validation {
|
||||
min = 1
|
||||
max = 1024
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = "amd64"
|
||||
os = "linux"
|
||||
auth = "azure-instance-identity"
|
||||
|
||||
metadata {
|
||||
key = "cpu"
|
||||
display_name = "CPU Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "%"}'
|
||||
EOT
|
||||
}
|
||||
metadata {
|
||||
key = "memory"
|
||||
display_name = "Memory Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
free -m | awk 'NR==2{printf "%.2f%%\t", $3*100/$2 }'
|
||||
EOT
|
||||
}
|
||||
metadata {
|
||||
key = "disk"
|
||||
display_name = "Disk Usage"
|
||||
interval = 600 # every 10 minutes
|
||||
timeout = 30 # df can take a while on large filesystems
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
df /home/coder | awk '$NF=="/"{printf "%s", $5}'
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
prefix = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
|
||||
}
|
||||
|
||||
data "cloudinit_config" "user_data" {
|
||||
gzip = false
|
||||
base64_encode = true
|
||||
|
||||
boundary = "//"
|
||||
|
||||
part {
|
||||
filename = "cloud-config.yaml"
|
||||
content_type = "text/cloud-config"
|
||||
|
||||
content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
|
||||
username = "coder" # Ensure this user/group does not exist in your VM image
|
||||
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 = module.azure_region.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"
|
||||
}
|
||||
}
|
||||
|
||||
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 = data.coder_parameter.home_size.value
|
||||
}
|
||||
|
||||
// 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.start_count
|
||||
name = "vm"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = azurerm_resource_group.main.location
|
||||
size = data.coder_parameter.instance_type.value
|
||||
// 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 = data.cloudinit_config.user_data.rendered
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
resource "coder_metadata" "workspace_info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = azurerm_linux_virtual_machine.main[0].id
|
||||
|
||||
item {
|
||||
key = "type"
|
||||
value = azurerm_linux_virtual_machine.main[0].size
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "home_info" {
|
||||
resource_id = azurerm_managed_disk.home.id
|
||||
|
||||
item {
|
||||
key = "size"
|
||||
value = "${data.coder_parameter.home_size.value} GiB"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "coder" {}
|
||||
|
||||
variable "project_uuid" {
|
||||
type = string
|
||||
description = <<-EOF
|
||||
DigitalOcean project ID
|
||||
|
||||
$ doctl projects list
|
||||
EOF
|
||||
sensitive = true
|
||||
|
||||
validation {
|
||||
# make sure length of alphanumeric string is 36 (UUIDv4 size)
|
||||
condition = length(var.project_uuid) == 36
|
||||
error_message = "Invalid Digital Ocean Project ID."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
variable "ssh_key_id" {
|
||||
type = number
|
||||
description = <<-EOF
|
||||
DigitalOcean SSH key ID (some Droplet images require an SSH key to be set):
|
||||
|
||||
Can be set to "0" for no key.
|
||||
|
||||
Note: Setting this to zero will break Fedora images and notify root passwords via email.
|
||||
|
||||
$ doctl compute ssh-key list
|
||||
EOF
|
||||
sensitive = true
|
||||
default = 0
|
||||
|
||||
validation {
|
||||
condition = var.ssh_key_id >= 0
|
||||
error_message = "Invalid Digital Ocean SSH key ID, a number is required."
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "droplet_image" {
|
||||
name = "droplet_image"
|
||||
display_name = "Droplet image"
|
||||
description = "Which Droplet image would you like to use?"
|
||||
default = "ubuntu-22-04-x64"
|
||||
type = "string"
|
||||
mutable = false
|
||||
option {
|
||||
name = "AlmaLinux 9"
|
||||
value = "almalinux-9-x64"
|
||||
icon = "/icon/almalinux.svg"
|
||||
}
|
||||
option {
|
||||
name = "AlmaLinux 8"
|
||||
value = "almalinux-8-x64"
|
||||
icon = "/icon/almalinux.svg"
|
||||
}
|
||||
option {
|
||||
name = "Fedora 39"
|
||||
value = "fedora-39-x64"
|
||||
icon = "/icon/fedora.svg"
|
||||
}
|
||||
option {
|
||||
name = "Fedora 38"
|
||||
value = "fedora-38-x64"
|
||||
icon = "/icon/fedora.svg"
|
||||
}
|
||||
option {
|
||||
name = "CentOS Stream 9"
|
||||
value = "centos-stream-9-x64"
|
||||
icon = "/icon/centos.svg"
|
||||
}
|
||||
option {
|
||||
name = "CentOS Stream 8"
|
||||
value = "centos-stream-8-x64"
|
||||
icon = "/icon/centos.svg"
|
||||
}
|
||||
option {
|
||||
name = "Debian 12"
|
||||
value = "debian-12-x64"
|
||||
icon = "/icon/debian.svg"
|
||||
}
|
||||
option {
|
||||
name = "Debian 11"
|
||||
value = "debian-11-x64"
|
||||
icon = "/icon/debian.svg"
|
||||
}
|
||||
option {
|
||||
name = "Debian 10"
|
||||
value = "debian-10-x64"
|
||||
icon = "/icon/debian.svg"
|
||||
}
|
||||
option {
|
||||
name = "Rocky Linux 9"
|
||||
value = "rockylinux-9-x64"
|
||||
icon = "/icon/rockylinux.svg"
|
||||
}
|
||||
option {
|
||||
name = "Rocky Linux 8"
|
||||
value = "rockylinux-8-x64"
|
||||
icon = "/icon/rockylinux.svg"
|
||||
}
|
||||
option {
|
||||
name = "Ubuntu 22.04 (LTS)"
|
||||
value = "ubuntu-22-04-x64"
|
||||
icon = "/icon/ubuntu.svg"
|
||||
}
|
||||
option {
|
||||
name = "Ubuntu 20.04 (LTS)"
|
||||
value = "ubuntu-20-04-x64"
|
||||
icon = "/icon/ubuntu.svg"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "droplet_size" {
|
||||
name = "droplet_size"
|
||||
display_name = "Droplet size"
|
||||
description = "Which Droplet configuration would you like to use?"
|
||||
default = "s-1vcpu-1gb"
|
||||
type = "string"
|
||||
icon = "/icon/memory.svg"
|
||||
mutable = false
|
||||
# s-1vcpu-512mb-10gb is unsupported in tor1, blr1, lon1, sfo2, and nyc3 regions
|
||||
# s-8vcpu-16gb access requires a support ticket with Digital Ocean
|
||||
option {
|
||||
name = "1 vCPU, 1 GB RAM"
|
||||
value = "s-1vcpu-1gb"
|
||||
}
|
||||
option {
|
||||
name = "1 vCPU, 2 GB RAM"
|
||||
value = "s-1vcpu-2gb"
|
||||
}
|
||||
option {
|
||||
name = "2 vCPU, 2 GB RAM"
|
||||
value = "s-2vcpu-2gb"
|
||||
}
|
||||
option {
|
||||
name = "2 vCPU, 4 GB RAM"
|
||||
value = "s-2vcpu-4gb"
|
||||
}
|
||||
option {
|
||||
name = "4 vCPU, 8 GB RAM"
|
||||
value = "s-4vcpu-8gb"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "home_volume_size" {
|
||||
name = "home_volume_size"
|
||||
display_name = "Home volume size"
|
||||
description = "How large would you like your home volume to be (in GB)?"
|
||||
type = "number"
|
||||
default = "20"
|
||||
mutable = false
|
||||
validation {
|
||||
min = 1
|
||||
max = 100 # Sizes larger than 100 GB require a support ticket with Digital Ocean
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "region" {
|
||||
name = "region"
|
||||
display_name = "Region"
|
||||
description = "This is the region where your workspace will be created."
|
||||
icon = "/emojis/1f30e.png"
|
||||
type = "string"
|
||||
default = "ams3"
|
||||
mutable = false
|
||||
# nyc1, sfo1, and ams2 regions were excluded because they do not support volumes, which are used to persist data while decreasing cost
|
||||
option {
|
||||
name = "Canada (Toronto)"
|
||||
value = "tor1"
|
||||
icon = "/emojis/1f1e8-1f1e6.png"
|
||||
}
|
||||
option {
|
||||
name = "Germany (Frankfurt)"
|
||||
value = "fra1"
|
||||
icon = "/emojis/1f1e9-1f1ea.png"
|
||||
}
|
||||
option {
|
||||
name = "India (Bangalore)"
|
||||
value = "blr1"
|
||||
icon = "/emojis/1f1ee-1f1f3.png"
|
||||
}
|
||||
option {
|
||||
name = "Netherlands (Amsterdam)"
|
||||
value = "ams3"
|
||||
icon = "/emojis/1f1f3-1f1f1.png"
|
||||
}
|
||||
option {
|
||||
name = "Singapore"
|
||||
value = "sgp1"
|
||||
icon = "/emojis/1f1f8-1f1ec.png"
|
||||
}
|
||||
option {
|
||||
name = "United Kingdom (London)"
|
||||
value = "lon1"
|
||||
icon = "/emojis/1f1ec-1f1e7.png"
|
||||
}
|
||||
option {
|
||||
name = "United States (California - 2)"
|
||||
value = "sfo2"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
option {
|
||||
name = "United States (California - 3)"
|
||||
value = "sfo3"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
option {
|
||||
name = "United States (New York - 1)"
|
||||
value = "nyc1"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
option {
|
||||
name = "United States (New York - 3)"
|
||||
value = "nyc3"
|
||||
icon = "/emojis/1f1fa-1f1f8.png"
|
||||
}
|
||||
}
|
||||
|
||||
# Configure the DigitalOcean Provider
|
||||
provider "digitalocean" {
|
||||
# Recommended: use environment variable DIGITALOCEAN_TOKEN with your personal access token when starting coderd
|
||||
# alternatively, you can pass the token via a variable.
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
os = "linux"
|
||||
arch = "amd64"
|
||||
|
||||
metadata {
|
||||
key = "cpu"
|
||||
display_name = "CPU Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = "coder stat cpu"
|
||||
}
|
||||
metadata {
|
||||
key = "memory"
|
||||
display_name = "Memory Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = "coder stat mem"
|
||||
}
|
||||
metadata {
|
||||
key = "home"
|
||||
display_name = "Home Usage"
|
||||
interval = 600 # every 10 minutes
|
||||
timeout = 30 # df can take a while on large filesystems
|
||||
script = "coder stat disk --path /home/${lower(data.coder_workspace_owner.me.name)}"
|
||||
}
|
||||
}
|
||||
|
||||
resource "digitalocean_volume" "home_volume" {
|
||||
region = data.coder_parameter.region.value
|
||||
name = "coder-${data.coder_workspace.me.id}-home"
|
||||
size = data.coder_parameter.home_volume_size.value
|
||||
initial_filesystem_type = "ext4"
|
||||
initial_filesystem_label = "coder-home"
|
||||
# Protect the volume from being deleted due to changes in attributes.
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
}
|
||||
|
||||
resource "digitalocean_droplet" "workspace" {
|
||||
region = data.coder_parameter.region.value
|
||||
count = data.coder_workspace.me.start_count
|
||||
name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
|
||||
image = data.coder_parameter.droplet_image.value
|
||||
size = data.coder_parameter.droplet_size.value
|
||||
|
||||
volume_ids = [digitalocean_volume.home_volume.id]
|
||||
user_data = templatefile("cloud-config.yaml.tftpl", {
|
||||
username = lower(data.coder_workspace_owner.me.name)
|
||||
home_volume_label = digitalocean_volume.home_volume.initial_filesystem_label
|
||||
init_script = base64encode(coder_agent.main.init_script)
|
||||
coder_agent_token = coder_agent.main.token
|
||||
})
|
||||
# Required to provision Fedora.
|
||||
ssh_keys = var.ssh_key_id > 0 ? [var.ssh_key_id] : []
|
||||
}
|
||||
|
||||
resource "digitalocean_project_resources" "project" {
|
||||
project = var.project_uuid
|
||||
# Workaround for terraform plan when using count.
|
||||
resources = length(digitalocean_droplet.workspace) > 0 ? [
|
||||
digitalocean_volume.home_volume.urn,
|
||||
digitalocean_droplet.workspace[0].urn
|
||||
] : [
|
||||
digitalocean_volume.home_volume.urn
|
||||
]
|
||||
}
|
||||
|
||||
resource "coder_metadata" "workspace-info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = digitalocean_droplet.workspace[0].id
|
||||
|
||||
item {
|
||||
key = "region"
|
||||
value = digitalocean_droplet.workspace[0].region
|
||||
}
|
||||
item {
|
||||
key = "image"
|
||||
value = digitalocean_droplet.workspace[0].image
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "volume-info" {
|
||||
resource_id = digitalocean_volume.home_volume.id
|
||||
|
||||
item {
|
||||
key = "size"
|
||||
value = "${digitalocean_volume.home_volume.size} GiB"
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "coder" {}
|
||||
|
||||
variable "project_id" {
|
||||
description = "Which Google Compute Project should your workspace live in?"
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/gcp-region
|
||||
module "gcp_region" {
|
||||
source = "registry.coder.com/coder/gcp-region/coder"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
regions = ["us", "europe"]
|
||||
default = "us-central1-a"
|
||||
}
|
||||
|
||||
provider "google" {
|
||||
zone = module.gcp_region.value
|
||||
project = var.project_id
|
||||
}
|
||||
|
||||
data "google_compute_default_service_account" "default" {}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "google_compute_disk" "root" {
|
||||
name = "coder-${data.coder_workspace.me.id}-root"
|
||||
type = "pd-ssd"
|
||||
zone = module.gcp_region.value
|
||||
image = "debian-cloud/debian-11"
|
||||
lifecycle {
|
||||
ignore_changes = [name, image]
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
auth = "google-instance-identity"
|
||||
arch = "amd64"
|
||||
os = "linux"
|
||||
startup_script = <<-EOT
|
||||
set -e
|
||||
|
||||
# Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
|
||||
EOT
|
||||
|
||||
metadata {
|
||||
key = "cpu"
|
||||
display_name = "CPU Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "%"}'
|
||||
EOT
|
||||
}
|
||||
metadata {
|
||||
key = "memory"
|
||||
display_name = "Memory Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
free -m | awk 'NR==2{printf "%.2f%%\t", $3*100/$2 }'
|
||||
EOT
|
||||
}
|
||||
metadata {
|
||||
key = "disk"
|
||||
display_name = "Disk Usage"
|
||||
interval = 600 # every 10 minutes
|
||||
timeout = 30 # df can take a while on large filesystems
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
df /home/coder | awk '$NF=="/"{printf "%s", $5}'
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance" "dev" {
|
||||
zone = module.gcp_region.value
|
||||
count = data.coder_workspace.me.start_count
|
||||
name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-root"
|
||||
machine_type = "e2-medium"
|
||||
network_interface {
|
||||
network = "default"
|
||||
access_config {
|
||||
// Ephemeral public IP
|
||||
}
|
||||
}
|
||||
boot_disk {
|
||||
auto_delete = false
|
||||
source = google_compute_disk.root.name
|
||||
}
|
||||
service_account {
|
||||
email = data.google_compute_default_service_account.default.email
|
||||
scopes = ["cloud-platform"]
|
||||
}
|
||||
# The startup script runs as root with no $HOME environment set up, so instead of directly
|
||||
# running the agent init script, create a user (with a homedir, default shell and sudo
|
||||
# permissions) and execute the init script as that user.
|
||||
metadata_startup_script = <<EOMETA
|
||||
#!/usr/bin/env sh
|
||||
set -eux
|
||||
|
||||
# If user does not exist, create it and set up passwordless sudo
|
||||
if ! id -u "${local.linux_user}" >/dev/null 2>&1; then
|
||||
useradd -m -s /bin/bash "${local.linux_user}"
|
||||
echo "${local.linux_user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder-user
|
||||
fi
|
||||
|
||||
exec sudo -u "${local.linux_user}" sh -c '${coder_agent.main.init_script}'
|
||||
EOMETA
|
||||
}
|
||||
|
||||
locals {
|
||||
# Ensure Coder username is a valid Linux username
|
||||
linux_user = lower(substr(data.coder_workspace_owner.me.name, 0, 32))
|
||||
}
|
||||
|
||||
resource "coder_metadata" "workspace_info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = google_compute_instance.dev[0].id
|
||||
|
||||
item {
|
||||
key = "type"
|
||||
value = google_compute_instance.dev[0].machine_type
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "home_info" {
|
||||
resource_id = google_compute_disk.root.id
|
||||
|
||||
item {
|
||||
key = "size"
|
||||
value = "${google_compute_disk.root.size} GiB"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "coder" {}
|
||||
|
||||
variable "project_id" {
|
||||
description = "Which Google Compute Project should your workspace live in?"
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/gcp-region
|
||||
module "gcp_region" {
|
||||
source = "registry.coder.com/coder/gcp-region/coder"
|
||||
|
||||
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
|
||||
version = "~> 1.0"
|
||||
|
||||
regions = ["us", "europe"]
|
||||
default = "us-central1-a"
|
||||
}
|
||||
|
||||
provider "google" {
|
||||
zone = module.gcp_region.value
|
||||
project = var.project_id
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
data "google_compute_default_service_account" "default" {}
|
||||
|
||||
resource "google_compute_disk" "root" {
|
||||
name = "coder-${data.coder_workspace.me.id}-root"
|
||||
type = "pd-ssd"
|
||||
zone = module.gcp_region.value
|
||||
image = "projects/windows-cloud/global/images/windows-server-2022-dc-core-v20220215"
|
||||
lifecycle {
|
||||
ignore_changes = [name, image]
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
auth = "google-instance-identity"
|
||||
arch = "amd64"
|
||||
os = "windows"
|
||||
}
|
||||
|
||||
resource "google_compute_instance" "dev" {
|
||||
zone = module.gcp_region.value
|
||||
count = data.coder_workspace.me.start_count
|
||||
name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
|
||||
machine_type = "e2-medium"
|
||||
network_interface {
|
||||
network = "default"
|
||||
access_config {
|
||||
// Ephemeral public IP
|
||||
}
|
||||
}
|
||||
boot_disk {
|
||||
auto_delete = false
|
||||
source = google_compute_disk.root.name
|
||||
}
|
||||
service_account {
|
||||
email = data.google_compute_default_service_account.default.email
|
||||
scopes = ["cloud-platform"]
|
||||
}
|
||||
metadata = {
|
||||
windows-startup-script-ps1 = coder_agent.main.init_script
|
||||
serial-port-enable = "TRUE"
|
||||
}
|
||||
}
|
||||
resource "coder_metadata" "workspace_info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = google_compute_instance.dev[0].id
|
||||
|
||||
item {
|
||||
key = "type"
|
||||
value = google_compute_instance.dev[0].machine_type
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "home_info" {
|
||||
resource_id = google_compute_disk.root.id
|
||||
|
||||
item {
|
||||
key = "size"
|
||||
value = "${google_compute_disk.root.size} GiB"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_provisioner" "me" {}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = data.coder_provisioner.me.arch
|
||||
os = data.coder_provisioner.me.os
|
||||
|
||||
metadata {
|
||||
display_name = "CPU Usage"
|
||||
key = "0_cpu_usage"
|
||||
script = "coder stat cpu"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "RAM Usage"
|
||||
key = "1_ram_usage"
|
||||
script = "coder stat mem"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
}
|
||||
|
||||
# Use this to set environment variables in your workspace
|
||||
# details: https://registry.terraform.io/providers/coder/coder/latest/docs/resources/env
|
||||
resource "coder_env" "welcome_message" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "WELCOME_MESSAGE"
|
||||
value = "Welcome to your Coder workspace!"
|
||||
}
|
||||
Reference in New Issue
Block a user