diff --git a/registry/coder/modules/digitalocean-region/README.md b/registry/coder/modules/digitalocean-region/README.md new file mode 100644 index 00000000..57a66d73 --- /dev/null +++ b/registry/coder/modules/digitalocean-region/README.md @@ -0,0 +1,155 @@ +--- +display_name: DigitalOcean Region +description: Provides a region selection parameter for DigitalOcean resources +icon: ../../../../.icons/digitalocean.svg +maintainer_github: coder +verified: true +tags: [helper, digitalocean, region] +--- + +# DigitalOcean Region + +This module provides a standardized region selection parameter for DigitalOcean resources. It includes all volume-supporting regions with user-friendly names and country flag icons. + +## Features + +- **Volume Support**: Only includes regions that support DigitalOcean volumes +- **User-Friendly**: Display names with country/city information +- **Visual Icons**: Country flag emojis for each region +- **Configurable**: Customizable default region and parameter details +- **Consistent**: Standardized region selection across templates + +## Usage + +### Basic Usage + +```tf +module "region" { + source = "registry.coder.com/coder/digitalocean-region/coder" + version = "1.0.0" +} + +resource "digitalocean_volume" "home_volume" { + region = module.region.value + # ... other configuration +} + +resource "digitalocean_droplet" "workspace" { + region = module.region.value + # ... other configuration +} +``` + +### Customized Configuration + +```tf +module "region" { + source = "registry.coder.com/coder/digitalocean-region/coder" + version = "1.0.0" + + default = "sfo3" + mutable = true + display_name = "Datacenter Location" + description = "Select the datacenter region for your development environment" +} + +resource "digitalocean_droplet" "workspace" { + region = module.region.value + # ... other configuration +} +``` + +## Available Regions + +The module includes the following DigitalOcean regions: + +| Region Code | Location | Notes | +| ----------- | ------------------------------ | ------------ | +| `tor1` | Canada (Toronto) | 🇨🇦 | +| `fra1` | Germany (Frankfurt) | 🇩🇪 | +| `blr1` | India (Bangalore) | 🇮🇳 | +| `ams3` | Netherlands (Amsterdam) | 🇳🇱 (Default) | +| `sgp1` | Singapore | 🇸🇬 | +| `lon1` | United Kingdom (London) | 🇬🇧 | +| `sfo2` | United States (California - 2) | 🇺🇸 | +| `sfo3` | United States (California - 3) | 🇺🇸 | +| `nyc1` | United States (New York - 1) | 🇺🇸 | +| `nyc3` | United States (New York - 3) | 🇺🇸 | + +> **Note**: Some regions (nyc1, sfo1, ams2) are excluded because they do not support volumes, which are commonly used for persistent data storage. + +## Variables + +| Variable | Type | Default | Description | +| -------------- | -------- | ------------------------------------------------------------ | ---------------------------------------------------------- | +| `default` | `string` | `"ams3"` | The default region to select | +| `mutable` | `bool` | `false` | Whether the region can be changed after workspace creation | +| `name` | `string` | `"region"` | The name of the parameter | +| `display_name` | `string` | `"Region"` | The display name of the parameter | +| `description` | `string` | `"This is the region where your workspace will be created."` | The description of the parameter | +| `icon` | `string` | `"/emojis/1f30e.png"` | The icon to display for the parameter | + +## Outputs + +| Output | Type | Description | +| -------------- | -------- | ---------------------------------------- | +| `value` | `string` | The selected region value (e.g., "ams3") | +| `name` | `string` | The parameter name | +| `display_name` | `string` | The parameter display name | + +## Examples + +### With Custom Default Region + +```tf +module "region" { + source = "registry.coder.com/coder/digitalocean-region/coder" + version = "1.0.0" + + default = "sfo3" # Default to San Francisco +} +``` + +### With Mutable Region Selection + +```tf +module "region" { + source = "registry.coder.com/coder/digitalocean-region/coder" + version = "1.0.0" + + mutable = true # Allow changing region after workspace creation +} +``` + +### Integration with DigitalOcean Resources + +```tf +module "region" { + source = "registry.coder.com/coder/digitalocean-region/coder" + version = "1.0.0" +} + +resource "digitalocean_volume" "home_volume" { + region = module.region.value + name = "coder-${data.coder_workspace.me.id}-home" + size = 20 + initial_filesystem_type = "ext4" +} + +resource "digitalocean_droplet" "workspace" { + region = module.region.value + name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}" + image = "ubuntu-22-04-x64" + size = "s-2vcpu-4gb" + + volume_ids = [digitalocean_volume.home_volume.id] +} +``` + +## Benefits + +1. **Standardization**: Consistent region selection across all DigitalOcean templates +2. **Maintenance**: Single place to update region options +3. **User Experience**: Better UX with descriptive names and icons +4. **Reliability**: Only includes regions that support required features +5. **Flexibility**: Customizable for different use cases diff --git a/registry/coder/modules/digitalocean-region/main.test.ts b/registry/coder/modules/digitalocean-region/main.test.ts new file mode 100644 index 00000000..85f1f7f8 --- /dev/null +++ b/registry/coder/modules/digitalocean-region/main.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, it } from "bun:test"; +import { + findResourceInstance, + runTerraformInit, + runTerraformApply, + testRequiredVariables, +} from "~test"; + +describe("digitalocean-region", async () => { + type TestVariables = { + default?: string; + mutable?: boolean; + name?: string; + display_name?: string; + description?: string; + icon?: string; + }; + + await runTerraformInit(import.meta.dir); + + it("can run apply with no variables", async () => { + const state = await runTerraformApply(import.meta.dir, {}); + const parameter = findResourceInstance(state, "coder_parameter"); + expect(parameter.name).toBe("region"); + expect(parameter.display_name).toBe("Region"); + expect(parameter.default).toBe("ams3"); + expect(parameter.mutable).toBe(false); + expect(parameter.type).toBe("string"); + expect(parameter.icon).toBe("/emojis/1f30e.png"); + }); + + it("can customize default region", async () => { + const state = await runTerraformApply(import.meta.dir, { + default: "sfo3", + }); + const parameter = findResourceInstance(state, "coder_parameter"); + expect(parameter.default).toBe("sfo3"); + }); + + it("can make region mutable", async () => { + const state = await runTerraformApply(import.meta.dir, { + mutable: true, + }); + const parameter = findResourceInstance(state, "coder_parameter"); + expect(parameter.mutable).toBe(true); + }); + + it("can customize parameter details", async () => { + const state = await runTerraformApply(import.meta.dir, { + name: "datacenter", + display_name: "Datacenter Location", + description: "Select your preferred datacenter", + icon: "/emojis/1f4cd.png", + }); + const parameter = findResourceInstance(state, "coder_parameter", "region"); + expect(parameter.name).toBe("datacenter"); + expect(parameter.display_name).toBe("Datacenter Location"); + expect(parameter.description).toBe("Select your preferred datacenter"); + expect(parameter.icon).toBe("/emojis/1f4cd.png"); + }); + + it("includes all expected region options", async () => { + const state = await runTerraformApply(import.meta.dir, {}); + const parameter = findResourceInstance(state, "coder_parameter"); + + const expectedRegions = [ + { value: "tor1", name: "Canada (Toronto)", icon: "/emojis/1f1e8-1f1e6.png", description: "" }, + { value: "fra1", name: "Germany (Frankfurt)", icon: "/emojis/1f1e9-1f1ea.png", description: "" }, + { value: "blr1", name: "India (Bangalore)", icon: "/emojis/1f1ee-1f1f3.png", description: "" }, + { value: "ams3", name: "Netherlands (Amsterdam)", icon: "/emojis/1f1f3-1f1f1.png", description: "" }, + { value: "sgp1", name: "Singapore", icon: "/emojis/1f1f8-1f1ec.png", description: "" }, + { value: "lon1", name: "United Kingdom (London)", icon: "/emojis/1f1ec-1f1e7.png", description: "" }, + { value: "sfo2", name: "United States (California - 2)", icon: "/emojis/1f1fa-1f1f8.png", description: "" }, + { value: "sfo3", name: "United States (California - 3)", icon: "/emojis/1f1fa-1f1f8.png", description: "" }, + { value: "nyc1", name: "United States (New York - 1)", icon: "/emojis/1f1fa-1f1f8.png", description: "" }, + { value: "nyc3", name: "United States (New York - 3)", icon: "/emojis/1f1fa-1f1f8.png", description: "" }, + ]; + + expect(parameter.option).toHaveLength(expectedRegions.length); + + expectedRegions.forEach((expectedRegion, index) => { + expect(parameter.option[index]).toEqual(expectedRegion); + }); + }); + + it("outputs the correct values", async () => { + const state = await runTerraformApply(import.meta.dir, { + default: "sfo3", + name: "test_region", + display_name: "Test Region", + }); + + expect(state.outputs.value.value).toBe("sfo3"); + expect(state.outputs.name.value).toBe("test_region"); + expect(state.outputs.display_name.value).toBe("Test Region"); + }); +}); \ No newline at end of file diff --git a/registry/coder/modules/digitalocean-region/main.tf b/registry/coder/modules/digitalocean-region/main.tf new file mode 100644 index 00000000..e6b671f9 --- /dev/null +++ b/registry/coder/modules/digitalocean-region/main.tf @@ -0,0 +1,122 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.23" + } + } +} + +variable "default" { + type = string + description = "The default region to select" + default = "ams3" +} + +variable "mutable" { + type = bool + description = "Whether the region can be changed after workspace creation" + default = false +} + +variable "name" { + type = string + description = "The name of the parameter" + default = "region" +} + +variable "display_name" { + type = string + description = "The display name of the parameter" + default = "Region" +} + +variable "description" { + type = string + description = "The description of the parameter" + default = "This is the region where your workspace will be created." +} + +variable "icon" { + type = string + description = "The icon to display for the parameter" + default = "/emojis/1f30e.png" +} + +data "coder_parameter" "region" { + name = var.name + display_name = var.display_name + description = var.description + icon = var.icon + type = "string" + default = var.default + mutable = var.mutable + # 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" + } +} + +output "value" { + description = "The selected region value" + value = data.coder_parameter.region.value +} + +output "name" { + description = "The selected region name" + value = data.coder_parameter.region.name +} + +output "display_name" { + description = "The selected region display name" + value = data.coder_parameter.region.display_name +} \ No newline at end of file diff --git a/registry/coder/templates/digitalocean-linux/main.tf b/registry/coder/templates/digitalocean-linux/main.tf index 4daf4b8b..92ab107b 100644 --- a/registry/coder/templates/digitalocean-linux/main.tf +++ b/registry/coder/templates/digitalocean-linux/main.tf @@ -167,65 +167,8 @@ data "coder_parameter" "home_volume_size" { } } -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" - } +module "region" { + source = "../../modules/digitalocean-region" } # Configure the DigitalOcean Provider @@ -297,7 +240,7 @@ module "jetbrains_gateway" { } resource "digitalocean_volume" "home_volume" { - region = data.coder_parameter.region.value + region = module.region.value name = "coder-${data.coder_workspace.me.id}-home" size = data.coder_parameter.home_volume_size.value initial_filesystem_type = "ext4" @@ -309,7 +252,7 @@ resource "digitalocean_volume" "home_volume" { } resource "digitalocean_droplet" "workspace" { - region = data.coder_parameter.region.value + region = module.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