Skip to content

create DigitalOcean region module #221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions registry/coder/modules/digitalocean-region/README.md
Original file line number Diff line number Diff line change
@@ -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
97 changes: 97 additions & 0 deletions registry/coder/modules/digitalocean-region/main.test.ts
Original file line number Diff line number Diff line change
@@ -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<TestVariables>(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<TestVariables>(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<TestVariables>(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<TestVariables>(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<TestVariables>(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<TestVariables>(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");
});
});
122 changes: 122 additions & 0 deletions registry/coder/modules/digitalocean-region/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
Loading