From df23374e34509c1cc0dd6ee241c8bba183be3b7a Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:24:50 +0000 Subject: [PATCH 01/11] feat(registry/coder/modules/aws-region): store regions in regions.json and expose availability_zone --- registry/coder/modules/aws-region/README.md | 51 +++++- .../coder/modules/aws-region/main.test.ts | 33 ---- registry/coder/modules/aws-region/main.tf | 145 +++------------ .../coder/modules/aws-region/main.tftest.hcl | 114 ++++++++++++ .../coder/modules/aws-region/regions.json | 170 ++++++++++++++++++ 5 files changed, 348 insertions(+), 165 deletions(-) delete mode 100644 registry/coder/modules/aws-region/main.test.ts create mode 100644 registry/coder/modules/aws-region/main.tftest.hcl create mode 100644 registry/coder/modules/aws-region/regions.json diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index c2527a306..48559873c 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -14,15 +14,15 @@ the region closest to them. Customize the preselected parameter value: ```tf -module "aws-region" { +module "aws_region" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/aws-region/coder" - version = "1.0.31" + version = "1.1.0" default = "us-east-1" } provider "aws" { - region = module.aws_region.value + region = module.aws_region[0].value } ``` @@ -30,15 +30,40 @@ provider "aws" { ## Examples +### Provision in the selected region's availability zone + +The `availability_zone` output resolves the selected region to a concrete zone +(for example `us-east-1a`), so templates no longer have to guess it by appending +a letter to the region ID: + +```tf +module "aws_region" { + source = "registry.coder.com/coder/aws-region/coder" + version = "1.1.0" + default = "us-east-1" +} + +provider "aws" { + region = module.aws_region.value +} + +resource "aws_instance" "dev" { + ami = data.aws_ami.ubuntu.id + instance_type = "t3.micro" + availability_zone = module.aws_region.availability_zone + # ... +} +``` + ### Customize regions Change the display name and icon for a region using the corresponding maps: ```tf -module "aws-region" { +module "aws_region" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/aws-region/coder" - version = "1.0.31" + version = "1.1.0" default = "ap-south-1" custom_names = { @@ -51,7 +76,7 @@ module "aws-region" { } provider "aws" { - region = module.aws_region.value + region = module.aws_region[0].value } ``` @@ -62,20 +87,28 @@ provider "aws" { Hide the Asia Pacific regions Seoul and Osaka: ```tf -module "aws-region" { +module "aws_region" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/aws-region/coder" - version = "1.0.31" + version = "1.1.0" exclude = ["ap-northeast-2", "ap-northeast-3"] } provider "aws" { - region = module.aws_region.value + region = module.aws_region[0].value } ``` ![AWS Exclude](../../.images/aws-exclude.png) +## Outputs + +| Output | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------- | +| `value` | The ID of the selected region, e.g. `us-east-1`. | +| `availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | +| `regions` | Every region keyed by ID, each with its `name`, `icon`, and `availability_zone`. Sourced from `regions.json`. | + ## Related templates For a complete AWS EC2 template, see the following examples in the [Coder Registry](https://registry.coder.com/). diff --git a/registry/coder/modules/aws-region/main.test.ts b/registry/coder/modules/aws-region/main.test.ts deleted file mode 100644 index b7768cf2e..000000000 --- a/registry/coder/modules/aws-region/main.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { - runTerraformApply, - runTerraformInit, - testRequiredVariables, -} from "~test"; - -describe("aws-region", async () => { - await runTerraformInit(import.meta.dir); - - testRequiredVariables(import.meta.dir, {}); - - it("default output", async () => { - const state = await runTerraformApply(import.meta.dir, {}); - expect(state.outputs.value.value).toBe(""); - }); - - it("customized default", async () => { - const state = await runTerraformApply(import.meta.dir, { - default: "us-west-2", - }); - expect(state.outputs.value.value).toBe("us-west-2"); - }); - - it("set custom order for coder_parameter", async () => { - const order = 99; - const state = await runTerraformApply(import.meta.dir, { - coder_parameter_order: order.toString(), - }); - expect(state.resources).toHaveLength(1); - expect(state.resources[0].instances[0].attributes.order).toBe(order); - }); -}); diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 12a01fe76..88a8d6a0a 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -58,123 +58,11 @@ variable "coder_parameter_order" { } locals { - # This is a static list because the regions don't change _that_ - # frequently and including the `aws_regions` data source requires - # the provider, which requires a region. - regions = { - "af-south-1" = { - name = "Africa (Cape Town)" - icon = "/emojis/1f1ff-1f1e6.png" - } - "ap-east-1" = { - name = "Asia Pacific (Hong Kong)" - icon = "/emojis/1f1ed-1f1f0.png" - } - "ap-northeast-1" = { - name = "Asia Pacific (Tokyo)" - icon = "/emojis/1f1ef-1f1f5.png" - } - "ap-northeast-2" = { - name = "Asia Pacific (Seoul)" - icon = "/emojis/1f1f0-1f1f7.png" - } - "ap-northeast-3" = { - name = "Asia Pacific (Osaka)" - icon = "/emojis/1f1ef-1f1f5.png" - } - "ap-south-1" = { - name = "Asia Pacific (Mumbai)" - icon = "/emojis/1f1ee-1f1f3.png" - } - "ap-south-2" = { - name = "Asia Pacific (Hyderabad)" - icon = "/emojis/1f1ee-1f1f3.png" - } - "ap-southeast-1" = { - name = "Asia Pacific (Singapore)" - icon = "/emojis/1f1f8-1f1ec.png" - } - "ap-southeast-2" = { - name = "Asia Pacific (Sydney)" - icon = "/emojis/1f1e6-1f1fa.png" - } - "ap-southeast-3" = { - name = "Asia Pacific (Jakarta)" - icon = "/emojis/1f1ee-1f1e9.png" - } - "ap-southeast-4" = { - name = "Asia Pacific (Melbourne)" - icon = "/emojis/1f1e6-1f1fa.png" - } - "ca-central-1" = { - name = "Canada (Central)" - icon = "/emojis/1f1e8-1f1e6.png" - } - "ca-west-1" = { - name = "Canada West (Calgary)" - icon = "/emojis/1f1e8-1f1e6.png" - } - "eu-central-1" = { - name = "EU (Frankfurt)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-central-2" = { - name = "Europe (Zurich)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-north-1" = { - name = "EU (Stockholm)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-south-1" = { - name = "Europe (Milan)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-south-2" = { - name = "Europe (Spain)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-west-1" = { - name = "EU (Ireland)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-west-2" = { - name = "EU (London)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "eu-west-3" = { - name = "EU (Paris)" - icon = "/emojis/1f1ea-1f1fa.png" - } - "il-central-1" = { - name = "Israel (Tel Aviv)" - icon = "/emojis/1f1ee-1f1f1.png" - } - "me-south-1" = { - name = "Middle East (Bahrain)" - icon = "/emojis/1f1e7-1f1ed.png" - } - "sa-east-1" = { - name = "South America (São Paulo)" - icon = "/emojis/1f1e7-1f1f7.png" - } - "us-east-1" = { - name = "US East (N. Virginia)" - icon = "/emojis/1f1fa-1f1f8.png" - } - "us-east-2" = { - name = "US East (Ohio)" - icon = "/emojis/1f1fa-1f1f8.png" - } - "us-west-1" = { - name = "US West (N. California)" - icon = "/emojis/1f1fa-1f1f8.png" - } - "us-west-2" = { - name = "US West (Oregon)" - icon = "/emojis/1f1fa-1f1f8.png" - } - } + # Static catalog (see regions.json) so the module needs no AWS provider or + # credentials at plan time. The regions don't change frequently, and the + # aws_regions data source would require a provider, which requires a region. + regions = jsondecode(file("${path.module}/regions.json")) + regions_by_id = { for region in local.regions : region.value => region } } data "coder_parameter" "region" { @@ -185,15 +73,26 @@ data "coder_parameter" "region" { order = var.coder_parameter_order mutable = var.mutable dynamic "option" { - for_each = { for k, v in local.regions : k => v if !(contains(var.exclude, k)) } + for_each = [for region in local.regions : region if !contains(var.exclude, region.value)] content { - name = try(var.custom_names[option.key], option.value.name) - icon = try(var.custom_icons[option.key], option.value.icon) - value = option.key + name = try(var.custom_names[option.value.value], option.value.name) + icon = try(var.custom_icons[option.value.value], option.value.icon) + value = option.value.value } } } output "value" { - value = data.coder_parameter.region.value -} \ No newline at end of file + description = "The ID of the selected AWS region, e.g. \"us-east-1\"." + value = data.coder_parameter.region.value +} + +output "availability_zone" { + description = "The default availability zone for the selected region, e.g. \"us-east-1a\". Empty when no region is selected." + value = try(local.regions_by_id[data.coder_parameter.region.value].availability_zone, "") +} + +output "regions" { + description = "All AWS regions keyed by region ID, each with name, icon, and availability_zone." + value = local.regions_by_id +} diff --git a/registry/coder/modules/aws-region/main.tftest.hcl b/registry/coder/modules/aws-region/main.tftest.hcl new file mode 100644 index 000000000..51bb45900 --- /dev/null +++ b/registry/coder/modules/aws-region/main.tftest.hcl @@ -0,0 +1,114 @@ +run "parameter_name" { + command = plan + + assert { + condition = data.coder_parameter.region.name == "aws_region" + error_message = "Parameter name should be aws_region" + } +} + +run "custom_order" { + command = plan + + variables { + coder_parameter_order = 99 + } + + assert { + condition = data.coder_parameter.region.order == 99 + error_message = "coder_parameter_order should propagate to the parameter order" + } +} + +run "default_output" { + command = apply + + assert { + condition = output.value == "" + error_message = "Default output should be empty when no default is set" + } +} + +run "empty_default_has_no_availability_zone" { + command = apply + + assert { + condition = output.availability_zone == "" + error_message = "availability_zone should be empty when no region is selected" + } +} + +run "custom_default" { + command = apply + + variables { + default = "us-west-2" + } + + assert { + condition = output.value == "us-west-2" + error_message = "Output should match the configured default" + } +} + +run "availability_zone_for_selected_region" { + command = apply + + variables { + default = "us-west-2" + } + + assert { + condition = output.availability_zone == "us-west-2a" + error_message = "availability_zone should be the selected region's default zone" + } +} + +run "regions_output_exposes_full_catalog" { + command = plan + + assert { + condition = length(output.regions) == length(local.regions) + error_message = "The regions output should expose every catalog entry keyed by ID" + } +} + +run "regions_output_includes_metadata" { + command = plan + + assert { + condition = output.regions["us-east-1"].name == "US East (N. Virginia)" && output.regions["us-east-1"].availability_zone == "us-east-1a" + error_message = "regions output should expose name and availability_zone per region" + } +} + +run "exclude_removes_option" { + command = apply + + variables { + exclude = ["ap-northeast-2", "ap-northeast-3"] + } + + assert { + condition = !contains([for o in data.coder_parameter.region.option : o.value], "ap-northeast-2") && !contains([for o in data.coder_parameter.region.option : o.value], "ap-northeast-3") + error_message = "Excluded regions should not appear as options" + } +} + +run "custom_names_and_icons_override" { + command = apply + + variables { + custom_names = { + "ap-south-1" = "Awesome Mumbai!" + } + custom_icons = { + "ap-south-1" = "/emojis/1f33a.png" + } + } + + assert { + condition = length([for o in data.coder_parameter.region.option : o if o.value == "ap-south-1" && o.name == "Awesome Mumbai!" && o.icon == "/emojis/1f33a.png"]) == 1 + error_message = "custom_names and custom_icons should override the defaults for a region" + } +} diff --git a/registry/coder/modules/aws-region/regions.json b/registry/coder/modules/aws-region/regions.json new file mode 100644 index 000000000..c8922b5bf --- /dev/null +++ b/registry/coder/modules/aws-region/regions.json @@ -0,0 +1,170 @@ +[ + { + "value": "af-south-1", + "name": "Africa (Cape Town)", + "icon": "/emojis/1f1ff-1f1e6.png", + "availability_zone": "af-south-1a" + }, + { + "value": "ap-east-1", + "name": "Asia Pacific (Hong Kong)", + "icon": "/emojis/1f1ed-1f1f0.png", + "availability_zone": "ap-east-1a" + }, + { + "value": "ap-northeast-1", + "name": "Asia Pacific (Tokyo)", + "icon": "/emojis/1f1ef-1f1f5.png", + "availability_zone": "ap-northeast-1a" + }, + { + "value": "ap-northeast-2", + "name": "Asia Pacific (Seoul)", + "icon": "/emojis/1f1f0-1f1f7.png", + "availability_zone": "ap-northeast-2a" + }, + { + "value": "ap-northeast-3", + "name": "Asia Pacific (Osaka)", + "icon": "/emojis/1f1ef-1f1f5.png", + "availability_zone": "ap-northeast-3a" + }, + { + "value": "ap-south-1", + "name": "Asia Pacific (Mumbai)", + "icon": "/emojis/1f1ee-1f1f3.png", + "availability_zone": "ap-south-1a" + }, + { + "value": "ap-south-2", + "name": "Asia Pacific (Hyderabad)", + "icon": "/emojis/1f1ee-1f1f3.png", + "availability_zone": "ap-south-2a" + }, + { + "value": "ap-southeast-1", + "name": "Asia Pacific (Singapore)", + "icon": "/emojis/1f1f8-1f1ec.png", + "availability_zone": "ap-southeast-1a" + }, + { + "value": "ap-southeast-2", + "name": "Asia Pacific (Sydney)", + "icon": "/emojis/1f1e6-1f1fa.png", + "availability_zone": "ap-southeast-2a" + }, + { + "value": "ap-southeast-3", + "name": "Asia Pacific (Jakarta)", + "icon": "/emojis/1f1ee-1f1e9.png", + "availability_zone": "ap-southeast-3a" + }, + { + "value": "ap-southeast-4", + "name": "Asia Pacific (Melbourne)", + "icon": "/emojis/1f1e6-1f1fa.png", + "availability_zone": "ap-southeast-4a" + }, + { + "value": "ca-central-1", + "name": "Canada (Central)", + "icon": "/emojis/1f1e8-1f1e6.png", + "availability_zone": "ca-central-1a" + }, + { + "value": "ca-west-1", + "name": "Canada West (Calgary)", + "icon": "/emojis/1f1e8-1f1e6.png", + "availability_zone": "ca-west-1a" + }, + { + "value": "eu-central-1", + "name": "EU (Frankfurt)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-central-1a" + }, + { + "value": "eu-central-2", + "name": "Europe (Zurich)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-central-2a" + }, + { + "value": "eu-north-1", + "name": "EU (Stockholm)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-north-1a" + }, + { + "value": "eu-south-1", + "name": "Europe (Milan)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-south-1a" + }, + { + "value": "eu-south-2", + "name": "Europe (Spain)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-south-2a" + }, + { + "value": "eu-west-1", + "name": "EU (Ireland)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-west-1a" + }, + { + "value": "eu-west-2", + "name": "EU (London)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-west-2a" + }, + { + "value": "eu-west-3", + "name": "EU (Paris)", + "icon": "/emojis/1f1ea-1f1fa.png", + "availability_zone": "eu-west-3a" + }, + { + "value": "il-central-1", + "name": "Israel (Tel Aviv)", + "icon": "/emojis/1f1ee-1f1f1.png", + "availability_zone": "il-central-1a" + }, + { + "value": "me-south-1", + "name": "Middle East (Bahrain)", + "icon": "/emojis/1f1e7-1f1ed.png", + "availability_zone": "me-south-1a" + }, + { + "value": "sa-east-1", + "name": "South America (São Paulo)", + "icon": "/emojis/1f1e7-1f1f7.png", + "availability_zone": "sa-east-1a" + }, + { + "value": "us-east-1", + "name": "US East (N. Virginia)", + "icon": "/emojis/1f1fa-1f1f8.png", + "availability_zone": "us-east-1a" + }, + { + "value": "us-east-2", + "name": "US East (Ohio)", + "icon": "/emojis/1f1fa-1f1f8.png", + "availability_zone": "us-east-2a" + }, + { + "value": "us-west-1", + "name": "US West (N. California)", + "icon": "/emojis/1f1fa-1f1f8.png", + "availability_zone": "us-west-1a" + }, + { + "value": "us-west-2", + "name": "US West (Oregon)", + "icon": "/emojis/1f1fa-1f1f8.png", + "availability_zone": "us-west-2a" + } +] From 4c5c12fd6ab3b5d1f2c8014a890b90d179f42089 Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:16:45 +0000 Subject: [PATCH 02/11] feat(registry/coder/modules/aws-region): optional parameter, in-module flags, rename AZ output - Move the flag emoji mapping into main.tf (flags map keyed by country); regions.json now stores value/name/country only. - Rename output availability_zone -> default_availability_zone. - Add create_parameter (default true); when false the picker is skipped and outputs fall back to var.default so admins can use the outputs only. - Remove the terraform test file. - Document how to regenerate regions.json via the AWS CLI. --- registry/coder/modules/aws-region/README.md | 69 +++++++++-- registry/coder/modules/aws-region/main.tf | 61 ++++++++-- .../coder/modules/aws-region/main.tftest.hcl | 114 ------------------ .../coder/modules/aws-region/regions.json | 84 +++++-------- 4 files changed, 137 insertions(+), 191 deletions(-) delete mode 100644 registry/coder/modules/aws-region/main.tftest.hcl diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index 48559873c..217a02740 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -32,9 +32,9 @@ provider "aws" { ### Provision in the selected region's availability zone -The `availability_zone` output resolves the selected region to a concrete zone -(for example `us-east-1a`), so templates no longer have to guess it by appending -a letter to the region ID: +The `default_availability_zone` output resolves the selected region to a +concrete zone (for example `us-east-1a`), so templates no longer have to guess +it by appending a letter to the region ID: ```tf module "aws_region" { @@ -50,11 +50,33 @@ provider "aws" { resource "aws_instance" "dev" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro" - availability_zone = module.aws_region.availability_zone + availability_zone = module.aws_region.default_availability_zone # ... } ``` +### Use the outputs without a parameter + +Set `create_parameter = false` to skip the region picker and pin a region +yourself, while still using the module's outputs (for example +`default_availability_zone` or the full `regions` catalog): + +```tf +module "aws_region" { + source = "registry.coder.com/coder/aws-region/coder" + version = "1.1.0" + create_parameter = false + default = "us-east-1" +} + +provider "aws" { + region = module.aws_region.value # "us-east-1" +} + +# module.aws_region.default_availability_zone => "us-east-1a" +# module.aws_region.regions => full catalog keyed by region ID +``` + ### Customize regions Change the display name and icon for a region using the corresponding maps: @@ -103,11 +125,40 @@ provider "aws" { ## Outputs -| Output | Description | -| ------------------- | ------------------------------------------------------------------------------------------------------------- | -| `value` | The ID of the selected region, e.g. `us-east-1`. | -| `availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | -| `regions` | Every region keyed by ID, each with its `name`, `icon`, and `availability_zone`. Sourced from `regions.json`. | +| Output | Description | +| --------------------------- | ----------------------------------------------------------------------------------------------- | +| `value` | The ID of the selected region, e.g. `us-east-1`. | +| `default_availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | +| `regions` | Every region keyed by ID, each with `name`, `country`, `icon`, and `default_availability_zone`. | + +## Updating regions.json + +`regions.json` is a static catalog of region IDs and display names, so the +module needs no AWS provider or credentials at plan time. Flag icons are not +stored in the JSON: each entry carries a `country` code that maps to a flag in +the `flags` map in `main.tf`. + +To refresh the list from AWS, use the AWS CLI. Region codes come from +`ec2:DescribeRegions`, and the human-readable names come from the public +`global-infrastructure` SSM parameters (hosted in `us-east-1`): + +```bash +for region in $(aws ec2 describe-regions --all-regions \ + --query 'Regions[].RegionName' --output text); do + name=$(aws ssm get-parameter --region us-east-1 \ + --name "/aws/service/global-infrastructure/regions/$region/longName" \ + --query 'Parameter.Value' --output text) + printf '%s\t%s\n' "$region" "$name" +done +``` + +For each region, add or update an entry in `regions.json` with: + +- `value`: the region code, e.g. `us-east-1`. +- `name`: the display name (the `longName` above, or a custom label). +- `country`: the key of the flag to show, from the `flags` map in `main.tf`. Add + a new `country = "/emojis/....png"` entry there if the region needs a flag that + is not already listed. ## Related templates diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 88a8d6a0a..ce6510415 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -23,7 +23,7 @@ variable "description" { variable "default" { default = "" - description = "The default region to use if no region is specified." + description = "The default region to preselect. Also used as the selected region when create_parameter is false." type = string } @@ -57,15 +57,52 @@ variable "coder_parameter_order" { default = null } +variable "create_parameter" { + type = bool + description = "Whether to create the coder_parameter. Set to false to skip the region picker and use the module only for its outputs, e.g. the regions catalog or a default_availability_zone for a fixed default region." + default = true +} + locals { - # Static catalog (see regions.json) so the module needs no AWS provider or - # credentials at plan time. The regions don't change frequently, and the - # aws_regions data source would require a provider, which requires a region. - regions = jsondecode(file("${path.module}/regions.json")) - regions_by_id = { for region in local.regions : region.value => region } + # Flag emoji per country/area code. Region rows in regions.json reference + # these by their "country" field, so the icon paths live here in one place + # instead of being repeated for every region in the JSON. + flags = { + au = "/emojis/1f1e6-1f1fa.png" + bh = "/emojis/1f1e7-1f1ed.png" + br = "/emojis/1f1e7-1f1f7.png" + ca = "/emojis/1f1e8-1f1e6.png" + eu = "/emojis/1f1ea-1f1fa.png" + hk = "/emojis/1f1ed-1f1f0.png" + id = "/emojis/1f1ee-1f1e9.png" + il = "/emojis/1f1ee-1f1f1.png" + in = "/emojis/1f1ee-1f1f3.png" + jp = "/emojis/1f1ef-1f1f5.png" + kr = "/emojis/1f1f0-1f1f7.png" + sg = "/emojis/1f1f8-1f1ec.png" + us = "/emojis/1f1fa-1f1f8.png" + za = "/emojis/1f1ff-1f1e6.png" + } + + # Region catalog (see regions.json). Kept as static data so the module needs + # no AWS provider or credentials at plan time. Each region resolves its flag + # from local.flags and a default availability zone of "a". + regions = jsondecode(file("${path.module}/regions.json")) + regions_by_id = { + for region in local.regions : region.value => { + value = region.value + name = region.name + country = region.country + icon = local.flags[region.country] + default_availability_zone = "${region.value}a" + } + } + + selected_region = var.create_parameter ? one(data.coder_parameter.region[*].value) : var.default } data "coder_parameter" "region" { + count = var.create_parameter ? 1 : 0 name = "aws_region" display_name = var.display_name description = var.description @@ -76,23 +113,23 @@ data "coder_parameter" "region" { for_each = [for region in local.regions : region if !contains(var.exclude, region.value)] content { name = try(var.custom_names[option.value.value], option.value.name) - icon = try(var.custom_icons[option.value.value], option.value.icon) + icon = try(var.custom_icons[option.value.value], local.flags[option.value.country]) value = option.value.value } } } output "value" { - description = "The ID of the selected AWS region, e.g. \"us-east-1\"." - value = data.coder_parameter.region.value + description = "The ID of the selected AWS region, e.g. \"us-east-1\". Falls back to var.default when create_parameter is false." + value = local.selected_region } -output "availability_zone" { +output "default_availability_zone" { description = "The default availability zone for the selected region, e.g. \"us-east-1a\". Empty when no region is selected." - value = try(local.regions_by_id[data.coder_parameter.region.value].availability_zone, "") + value = try(local.regions_by_id[local.selected_region].default_availability_zone, "") } output "regions" { - description = "All AWS regions keyed by region ID, each with name, icon, and availability_zone." + description = "All AWS regions keyed by region ID, each with name, country, icon, and default_availability_zone." value = local.regions_by_id } diff --git a/registry/coder/modules/aws-region/main.tftest.hcl b/registry/coder/modules/aws-region/main.tftest.hcl deleted file mode 100644 index 51bb45900..000000000 --- a/registry/coder/modules/aws-region/main.tftest.hcl +++ /dev/null @@ -1,114 +0,0 @@ -run "parameter_name" { - command = plan - - assert { - condition = data.coder_parameter.region.name == "aws_region" - error_message = "Parameter name should be aws_region" - } -} - -run "custom_order" { - command = plan - - variables { - coder_parameter_order = 99 - } - - assert { - condition = data.coder_parameter.region.order == 99 - error_message = "coder_parameter_order should propagate to the parameter order" - } -} - -run "default_output" { - command = apply - - assert { - condition = output.value == "" - error_message = "Default output should be empty when no default is set" - } -} - -run "empty_default_has_no_availability_zone" { - command = apply - - assert { - condition = output.availability_zone == "" - error_message = "availability_zone should be empty when no region is selected" - } -} - -run "custom_default" { - command = apply - - variables { - default = "us-west-2" - } - - assert { - condition = output.value == "us-west-2" - error_message = "Output should match the configured default" - } -} - -run "availability_zone_for_selected_region" { - command = apply - - variables { - default = "us-west-2" - } - - assert { - condition = output.availability_zone == "us-west-2a" - error_message = "availability_zone should be the selected region's default zone" - } -} - -run "regions_output_exposes_full_catalog" { - command = plan - - assert { - condition = length(output.regions) == length(local.regions) - error_message = "The regions output should expose every catalog entry keyed by ID" - } -} - -run "regions_output_includes_metadata" { - command = plan - - assert { - condition = output.regions["us-east-1"].name == "US East (N. Virginia)" && output.regions["us-east-1"].availability_zone == "us-east-1a" - error_message = "regions output should expose name and availability_zone per region" - } -} - -run "exclude_removes_option" { - command = apply - - variables { - exclude = ["ap-northeast-2", "ap-northeast-3"] - } - - assert { - condition = !contains([for o in data.coder_parameter.region.option : o.value], "ap-northeast-2") && !contains([for o in data.coder_parameter.region.option : o.value], "ap-northeast-3") - error_message = "Excluded regions should not appear as options" - } -} - -run "custom_names_and_icons_override" { - command = apply - - variables { - custom_names = { - "ap-south-1" = "Awesome Mumbai!" - } - custom_icons = { - "ap-south-1" = "/emojis/1f33a.png" - } - } - - assert { - condition = length([for o in data.coder_parameter.region.option : o if o.value == "ap-south-1" && o.name == "Awesome Mumbai!" && o.icon == "/emojis/1f33a.png"]) == 1 - error_message = "custom_names and custom_icons should override the defaults for a region" - } -} diff --git a/registry/coder/modules/aws-region/regions.json b/registry/coder/modules/aws-region/regions.json index c8922b5bf..30e681542 100644 --- a/registry/coder/modules/aws-region/regions.json +++ b/registry/coder/modules/aws-region/regions.json @@ -2,169 +2,141 @@ { "value": "af-south-1", "name": "Africa (Cape Town)", - "icon": "/emojis/1f1ff-1f1e6.png", - "availability_zone": "af-south-1a" + "country": "za" }, { "value": "ap-east-1", "name": "Asia Pacific (Hong Kong)", - "icon": "/emojis/1f1ed-1f1f0.png", - "availability_zone": "ap-east-1a" + "country": "hk" }, { "value": "ap-northeast-1", "name": "Asia Pacific (Tokyo)", - "icon": "/emojis/1f1ef-1f1f5.png", - "availability_zone": "ap-northeast-1a" + "country": "jp" }, { "value": "ap-northeast-2", "name": "Asia Pacific (Seoul)", - "icon": "/emojis/1f1f0-1f1f7.png", - "availability_zone": "ap-northeast-2a" + "country": "kr" }, { "value": "ap-northeast-3", "name": "Asia Pacific (Osaka)", - "icon": "/emojis/1f1ef-1f1f5.png", - "availability_zone": "ap-northeast-3a" + "country": "jp" }, { "value": "ap-south-1", "name": "Asia Pacific (Mumbai)", - "icon": "/emojis/1f1ee-1f1f3.png", - "availability_zone": "ap-south-1a" + "country": "in" }, { "value": "ap-south-2", "name": "Asia Pacific (Hyderabad)", - "icon": "/emojis/1f1ee-1f1f3.png", - "availability_zone": "ap-south-2a" + "country": "in" }, { "value": "ap-southeast-1", "name": "Asia Pacific (Singapore)", - "icon": "/emojis/1f1f8-1f1ec.png", - "availability_zone": "ap-southeast-1a" + "country": "sg" }, { "value": "ap-southeast-2", "name": "Asia Pacific (Sydney)", - "icon": "/emojis/1f1e6-1f1fa.png", - "availability_zone": "ap-southeast-2a" + "country": "au" }, { "value": "ap-southeast-3", "name": "Asia Pacific (Jakarta)", - "icon": "/emojis/1f1ee-1f1e9.png", - "availability_zone": "ap-southeast-3a" + "country": "id" }, { "value": "ap-southeast-4", "name": "Asia Pacific (Melbourne)", - "icon": "/emojis/1f1e6-1f1fa.png", - "availability_zone": "ap-southeast-4a" + "country": "au" }, { "value": "ca-central-1", "name": "Canada (Central)", - "icon": "/emojis/1f1e8-1f1e6.png", - "availability_zone": "ca-central-1a" + "country": "ca" }, { "value": "ca-west-1", "name": "Canada West (Calgary)", - "icon": "/emojis/1f1e8-1f1e6.png", - "availability_zone": "ca-west-1a" + "country": "ca" }, { "value": "eu-central-1", "name": "EU (Frankfurt)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-central-1a" + "country": "eu" }, { "value": "eu-central-2", "name": "Europe (Zurich)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-central-2a" + "country": "eu" }, { "value": "eu-north-1", "name": "EU (Stockholm)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-north-1a" + "country": "eu" }, { "value": "eu-south-1", "name": "Europe (Milan)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-south-1a" + "country": "eu" }, { "value": "eu-south-2", "name": "Europe (Spain)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-south-2a" + "country": "eu" }, { "value": "eu-west-1", "name": "EU (Ireland)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-west-1a" + "country": "eu" }, { "value": "eu-west-2", "name": "EU (London)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-west-2a" + "country": "eu" }, { "value": "eu-west-3", "name": "EU (Paris)", - "icon": "/emojis/1f1ea-1f1fa.png", - "availability_zone": "eu-west-3a" + "country": "eu" }, { "value": "il-central-1", "name": "Israel (Tel Aviv)", - "icon": "/emojis/1f1ee-1f1f1.png", - "availability_zone": "il-central-1a" + "country": "il" }, { "value": "me-south-1", "name": "Middle East (Bahrain)", - "icon": "/emojis/1f1e7-1f1ed.png", - "availability_zone": "me-south-1a" + "country": "bh" }, { "value": "sa-east-1", "name": "South America (São Paulo)", - "icon": "/emojis/1f1e7-1f1f7.png", - "availability_zone": "sa-east-1a" + "country": "br" }, { "value": "us-east-1", "name": "US East (N. Virginia)", - "icon": "/emojis/1f1fa-1f1f8.png", - "availability_zone": "us-east-1a" + "country": "us" }, { "value": "us-east-2", "name": "US East (Ohio)", - "icon": "/emojis/1f1fa-1f1f8.png", - "availability_zone": "us-east-2a" + "country": "us" }, { "value": "us-west-1", "name": "US West (N. California)", - "icon": "/emojis/1f1fa-1f1f8.png", - "availability_zone": "us-west-1a" + "country": "us" }, { "value": "us-west-2", "name": "US West (Oregon)", - "icon": "/emojis/1f1fa-1f1f8.png", - "availability_zone": "us-west-2a" + "country": "us" } ] From 952331cdc7547e8a55acff8e7f51889a6c6b868e Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:55:58 +0000 Subject: [PATCH 03/11] fix(registry/coder/modules/aws-region): render options under dynamic parameters Coder's dynamic parameters preview resolves file() relative to the module's own directory, while Terraform resolves it relative to the root module. The previous file("${path.module}/regions.json") therefore returned nothing under preview, so the parameter rendered as a free-text input with no options. Read the catalog with try(file("${path.module}/regions.json"), file("regions.json")) so it loads under both engines. Verified with coder/preview (radio, 28 options) and terraform apply. --- registry/coder/modules/aws-region/main.tf | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index ce6510415..6eb5580a9 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -87,7 +87,14 @@ locals { # Region catalog (see regions.json). Kept as static data so the module needs # no AWS provider or credentials at plan time. Each region resolves its flag # from local.flags and a default availability zone of "a". - regions = jsondecode(file("${path.module}/regions.json")) + # + # The try() reads regions.json under both Terraform and Coder's dynamic + # parameters preview: Terraform resolves file() relative to the root module + # (so path.module is required), while the preview evaluator resolves it + # relative to this module's own directory (so path.module points one level too + # deep). Without the fallback the parameter renders with no options under + # dynamic parameters. + regions = jsondecode(try(file("${path.module}/regions.json"), file("regions.json"))) regions_by_id = { for region in local.regions : region.value => { value = region.value From 814134b4767458129f1865140eff60c9aa49d173 Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:44:11 +0000 Subject: [PATCH 04/11] test(registry/coder/modules/aws-region): restore tests and add input validation Recover the module scorecard Engineering Quality points lost when the tests were removed: - Restore main.tftest.hcl covering options rendering, outputs, exclude, custom names/icons, and the create_parameter toggle (9 runs). - Add self-contained validation to default and exclude (region ID format), compatible with terraform >= 1.0. --- registry/coder/modules/aws-region/main.tf | 10 ++ .../coder/modules/aws-region/main.tftest.hcl | 124 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 registry/coder/modules/aws-region/main.tftest.hcl diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 6eb5580a9..53127c0dd 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -25,6 +25,11 @@ variable "default" { default = "" description = "The default region to preselect. Also used as the selected region when create_parameter is false." type = string + + validation { + condition = var.default == "" || can(regex("^[a-z]{2}-[a-z]+-[0-9]+$", var.default)) + error_message = "default must be empty or a valid AWS region ID, e.g. \"us-east-1\"." + } } variable "mutable" { @@ -49,6 +54,11 @@ variable "exclude" { default = [] description = "A list of region IDs to exclude." type = list(string) + + validation { + condition = alltrue([for region in var.exclude : can(regex("^[a-z]{2}-[a-z]+-[0-9]+$", region))]) + error_message = "exclude must contain valid AWS region IDs, e.g. \"ap-northeast-2\"." + } } variable "coder_parameter_order" { diff --git a/registry/coder/modules/aws-region/main.tftest.hcl b/registry/coder/modules/aws-region/main.tftest.hcl new file mode 100644 index 000000000..b976003c8 --- /dev/null +++ b/registry/coder/modules/aws-region/main.tftest.hcl @@ -0,0 +1,124 @@ +run "parameter_name" { + command = plan + + assert { + condition = data.coder_parameter.region[0].name == "aws_region" + error_message = "Parameter name should be aws_region" + } +} + +run "custom_order" { + command = plan + + variables { + coder_parameter_order = 99 + } + + assert { + condition = data.coder_parameter.region[0].order == 99 + error_message = "coder_parameter_order should propagate to the parameter order" + } +} + +run "all_regions_are_options" { + command = plan + + assert { + condition = length(data.coder_parameter.region[0].option) == length(local.regions) + error_message = "Every catalog region should be rendered as a selectable option" + } +} + +run "default_output_empty" { + command = apply + + assert { + condition = output.value == "" && output.default_availability_zone == "" + error_message = "With no default and no selection, value and default_availability_zone should be empty" + } +} + +run "custom_default" { + command = apply + + variables { + default = "us-west-2" + } + + assert { + condition = output.value == "us-west-2" && output.default_availability_zone == "us-west-2a" + error_message = "value and default_availability_zone should follow the configured default" + } +} + +run "regions_output_exposes_catalog" { + command = plan + + assert { + condition = length(output.regions) == length(local.regions) + error_message = "regions output should expose every catalog entry keyed by ID" + } + + assert { + condition = ( + output.regions["ap-northeast-1"].name == "Asia Pacific (Tokyo)" && + output.regions["ap-northeast-1"].country == "jp" && + output.regions["ap-northeast-1"].icon == "/emojis/1f1ef-1f1f5.png" && + output.regions["ap-northeast-1"].default_availability_zone == "ap-northeast-1a" + ) + error_message = "regions entries should expose name, country, icon, and default_availability_zone" + } +} + +run "exclude_removes_option" { + command = apply + + variables { + exclude = ["ap-northeast-2", "ap-northeast-3"] + } + + assert { + condition = ( + !contains([for o in data.coder_parameter.region[0].option : o.value], "ap-northeast-2") && + !contains([for o in data.coder_parameter.region[0].option : o.value], "ap-northeast-3") + ) + error_message = "Excluded regions should not appear as options" + } +} + +run "custom_names_and_icons_override" { + command = apply + + variables { + custom_names = { + "ap-south-1" = "Awesome Mumbai!" + } + custom_icons = { + "ap-south-1" = "/emojis/1f33a.png" + } + } + + assert { + condition = length([for o in data.coder_parameter.region[0].option : o if o.value == "ap-south-1" && o.name == "Awesome Mumbai!" && o.icon == "/emojis/1f33a.png"]) == 1 + error_message = "custom_names and custom_icons should override the defaults for a region" + } +} + +run "outputs_without_parameter" { + command = apply + + variables { + create_parameter = false + default = "eu-west-1" + } + + assert { + condition = length(data.coder_parameter.region) == 0 + error_message = "create_parameter = false should not create the coder_parameter" + } + + assert { + condition = output.value == "eu-west-1" && output.default_availability_zone == "eu-west-1a" + error_message = "With create_parameter = false, outputs should fall back to var.default" + } +} From 03ba5e468da85128db5eef55dc3dceadb3b5bf81 Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:51:52 +0000 Subject: [PATCH 05/11] refactor(registry/coder/modules/aws-region): default variable to null Use null instead of an empty-string sentinel for the default region: - var.default now defaults to null and validates null or a region ID. - coder_parameter reads default = var.default directly (no "" -> null shim). - Add a test for create_parameter = false with no default (value null). --- registry/coder/modules/aws-region/main.tf | 10 +++++----- registry/coder/modules/aws-region/main.tftest.hcl | 13 +++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 53127c0dd..553a45717 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -22,13 +22,13 @@ variable "description" { } variable "default" { - default = "" - description = "The default region to preselect. Also used as the selected region when create_parameter is false." + default = null + description = "The default region to preselect, e.g. \"us-east-1\". Leave unset for no preselection; also used as the selected region when create_parameter is false." type = string validation { - condition = var.default == "" || can(regex("^[a-z]{2}-[a-z]+-[0-9]+$", var.default)) - error_message = "default must be empty or a valid AWS region ID, e.g. \"us-east-1\"." + condition = var.default == null || can(regex("^[a-z]{2}-[a-z]+-[0-9]+$", var.default)) + error_message = "default must be null or a valid AWS region ID, e.g. \"us-east-1\"." } } @@ -123,7 +123,7 @@ data "coder_parameter" "region" { name = "aws_region" display_name = var.display_name description = var.description - default = var.default == "" ? null : var.default + default = var.default order = var.coder_parameter_order mutable = var.mutable dynamic "option" { diff --git a/registry/coder/modules/aws-region/main.tftest.hcl b/registry/coder/modules/aws-region/main.tftest.hcl index b976003c8..9291a36fd 100644 --- a/registry/coder/modules/aws-region/main.tftest.hcl +++ b/registry/coder/modules/aws-region/main.tftest.hcl @@ -122,3 +122,16 @@ run "outputs_without_parameter" { error_message = "With create_parameter = false, outputs should fall back to var.default" } } + +run "no_parameter_and_no_default" { + command = apply + + variables { + create_parameter = false + } + + assert { + condition = output.value == null && output.default_availability_zone == "" + error_message = "With no parameter and no default, value is null and default_availability_zone is empty" + } +} From e53d5f8014e63c080a4ad8aa5a5cd141b14a6231 Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:01:01 +0000 Subject: [PATCH 06/11] fix(registry/coder/modules/aws-region): use consistent Europe region names The catalog mixed "EU (...)" and "Europe (...)" display names. Align every European region to AWS's longName form ("Europe (...)"), which is what the documented AWS CLI regeneration returns, so the names stay consistent and reproducible. --- registry/coder/modules/aws-region/README.md | 2 +- registry/coder/modules/aws-region/regions.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index 217a02740..642dba344 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -155,7 +155,7 @@ done For each region, add or update an entry in `regions.json` with: - `value`: the region code, e.g. `us-east-1`. -- `name`: the display name (the `longName` above, or a custom label). +- `name`: the display name. Use the `longName` verbatim so names stay consistent; AWS returns `Europe (...)` for every European region, `US East (...)`, and so on. - `country`: the key of the flag to show, from the `flags` map in `main.tf`. Add a new `country = "/emojis/....png"` entry there if the region needs a flag that is not already listed. diff --git a/registry/coder/modules/aws-region/regions.json b/registry/coder/modules/aws-region/regions.json index 30e681542..85ae23b28 100644 --- a/registry/coder/modules/aws-region/regions.json +++ b/registry/coder/modules/aws-region/regions.json @@ -66,7 +66,7 @@ }, { "value": "eu-central-1", - "name": "EU (Frankfurt)", + "name": "Europe (Frankfurt)", "country": "eu" }, { @@ -76,7 +76,7 @@ }, { "value": "eu-north-1", - "name": "EU (Stockholm)", + "name": "Europe (Stockholm)", "country": "eu" }, { @@ -91,17 +91,17 @@ }, { "value": "eu-west-1", - "name": "EU (Ireland)", + "name": "Europe (Ireland)", "country": "eu" }, { "value": "eu-west-2", - "name": "EU (London)", + "name": "Europe (London)", "country": "eu" }, { "value": "eu-west-3", - "name": "EU (Paris)", + "name": "Europe (Paris)", "country": "eu" }, { From 7b2d9103337fbbcc4e33cc6b030240741a6dbacd Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:07:57 +0000 Subject: [PATCH 07/11] refactor(registry/coder/modules/aws-region): rename region field to flag The per-region field was named "country", but its value is a flag code such as "eu" for the shared European flag, which is not a country. Rename it to "flag" across regions.json, main.tf, the tests, and the README. --- registry/coder/modules/aws-region/README.md | 17 +++--- registry/coder/modules/aws-region/main.tf | 15 ++--- .../coder/modules/aws-region/main.tftest.hcl | 4 +- .../coder/modules/aws-region/regions.json | 56 +++++++++---------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index 642dba344..23ec5de36 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -125,17 +125,17 @@ provider "aws" { ## Outputs -| Output | Description | -| --------------------------- | ----------------------------------------------------------------------------------------------- | -| `value` | The ID of the selected region, e.g. `us-east-1`. | -| `default_availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | -| `regions` | Every region keyed by ID, each with `name`, `country`, `icon`, and `default_availability_zone`. | +| Output | Description | +| --------------------------- | -------------------------------------------------------------------------------------------- | +| `value` | The ID of the selected region, e.g. `us-east-1`. | +| `default_availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | +| `regions` | Every region keyed by ID, each with `name`, `flag`, `icon`, and `default_availability_zone`. | ## Updating regions.json `regions.json` is a static catalog of region IDs and display names, so the module needs no AWS provider or credentials at plan time. Flag icons are not -stored in the JSON: each entry carries a `country` code that maps to a flag in +stored in the JSON: each entry carries a `flag` code that maps to an emoji in the `flags` map in `main.tf`. To refresh the list from AWS, use the AWS CLI. Region codes come from @@ -156,9 +156,8 @@ For each region, add or update an entry in `regions.json` with: - `value`: the region code, e.g. `us-east-1`. - `name`: the display name. Use the `longName` verbatim so names stay consistent; AWS returns `Europe (...)` for every European region, `US East (...)`, and so on. -- `country`: the key of the flag to show, from the `flags` map in `main.tf`. Add - a new `country = "/emojis/....png"` entry there if the region needs a flag that - is not already listed. +- `flag`: the key of the flag to show, from the `flags` map in `main.tf`. Add a + new entry to that map if the region needs a flag that is not already listed. ## Related templates diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 553a45717..ca8b9abc1 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -74,9 +74,10 @@ variable "create_parameter" { } locals { - # Flag emoji per country/area code. Region rows in regions.json reference - # these by their "country" field, so the icon paths live here in one place - # instead of being repeated for every region in the JSON. + # Flag emoji per flag code. Region rows in regions.json reference these by + # their "flag" field, so the icon paths live in one place here instead of + # being repeated for every region in the JSON. Codes are the two-letter emoji + # code (for example "us"), or "eu" for the shared European flag. flags = { au = "/emojis/1f1e6-1f1fa.png" bh = "/emojis/1f1e7-1f1ed.png" @@ -109,8 +110,8 @@ locals { for region in local.regions : region.value => { value = region.value name = region.name - country = region.country - icon = local.flags[region.country] + flag = region.flag + icon = local.flags[region.flag] default_availability_zone = "${region.value}a" } } @@ -130,7 +131,7 @@ data "coder_parameter" "region" { for_each = [for region in local.regions : region if !contains(var.exclude, region.value)] content { name = try(var.custom_names[option.value.value], option.value.name) - icon = try(var.custom_icons[option.value.value], local.flags[option.value.country]) + icon = try(var.custom_icons[option.value.value], local.flags[option.value.flag]) value = option.value.value } } @@ -147,6 +148,6 @@ output "default_availability_zone" { } output "regions" { - description = "All AWS regions keyed by region ID, each with name, country, icon, and default_availability_zone." + description = "All AWS regions keyed by region ID, each with name, flag, icon, and default_availability_zone." value = local.regions_by_id } diff --git a/registry/coder/modules/aws-region/main.tftest.hcl b/registry/coder/modules/aws-region/main.tftest.hcl index 9291a36fd..9a17928d6 100644 --- a/registry/coder/modules/aws-region/main.tftest.hcl +++ b/registry/coder/modules/aws-region/main.tftest.hcl @@ -62,11 +62,11 @@ run "regions_output_exposes_catalog" { assert { condition = ( output.regions["ap-northeast-1"].name == "Asia Pacific (Tokyo)" && - output.regions["ap-northeast-1"].country == "jp" && + output.regions["ap-northeast-1"].flag == "jp" && output.regions["ap-northeast-1"].icon == "/emojis/1f1ef-1f1f5.png" && output.regions["ap-northeast-1"].default_availability_zone == "ap-northeast-1a" ) - error_message = "regions entries should expose name, country, icon, and default_availability_zone" + error_message = "regions entries should expose name, flag, icon, and default_availability_zone" } } diff --git a/registry/coder/modules/aws-region/regions.json b/registry/coder/modules/aws-region/regions.json index 85ae23b28..a1bc512c6 100644 --- a/registry/coder/modules/aws-region/regions.json +++ b/registry/coder/modules/aws-region/regions.json @@ -2,141 +2,141 @@ { "value": "af-south-1", "name": "Africa (Cape Town)", - "country": "za" + "flag": "za" }, { "value": "ap-east-1", "name": "Asia Pacific (Hong Kong)", - "country": "hk" + "flag": "hk" }, { "value": "ap-northeast-1", "name": "Asia Pacific (Tokyo)", - "country": "jp" + "flag": "jp" }, { "value": "ap-northeast-2", "name": "Asia Pacific (Seoul)", - "country": "kr" + "flag": "kr" }, { "value": "ap-northeast-3", "name": "Asia Pacific (Osaka)", - "country": "jp" + "flag": "jp" }, { "value": "ap-south-1", "name": "Asia Pacific (Mumbai)", - "country": "in" + "flag": "in" }, { "value": "ap-south-2", "name": "Asia Pacific (Hyderabad)", - "country": "in" + "flag": "in" }, { "value": "ap-southeast-1", "name": "Asia Pacific (Singapore)", - "country": "sg" + "flag": "sg" }, { "value": "ap-southeast-2", "name": "Asia Pacific (Sydney)", - "country": "au" + "flag": "au" }, { "value": "ap-southeast-3", "name": "Asia Pacific (Jakarta)", - "country": "id" + "flag": "id" }, { "value": "ap-southeast-4", "name": "Asia Pacific (Melbourne)", - "country": "au" + "flag": "au" }, { "value": "ca-central-1", "name": "Canada (Central)", - "country": "ca" + "flag": "ca" }, { "value": "ca-west-1", "name": "Canada West (Calgary)", - "country": "ca" + "flag": "ca" }, { "value": "eu-central-1", "name": "Europe (Frankfurt)", - "country": "eu" + "flag": "eu" }, { "value": "eu-central-2", "name": "Europe (Zurich)", - "country": "eu" + "flag": "eu" }, { "value": "eu-north-1", "name": "Europe (Stockholm)", - "country": "eu" + "flag": "eu" }, { "value": "eu-south-1", "name": "Europe (Milan)", - "country": "eu" + "flag": "eu" }, { "value": "eu-south-2", "name": "Europe (Spain)", - "country": "eu" + "flag": "eu" }, { "value": "eu-west-1", "name": "Europe (Ireland)", - "country": "eu" + "flag": "eu" }, { "value": "eu-west-2", "name": "Europe (London)", - "country": "eu" + "flag": "eu" }, { "value": "eu-west-3", "name": "Europe (Paris)", - "country": "eu" + "flag": "eu" }, { "value": "il-central-1", "name": "Israel (Tel Aviv)", - "country": "il" + "flag": "il" }, { "value": "me-south-1", "name": "Middle East (Bahrain)", - "country": "bh" + "flag": "bh" }, { "value": "sa-east-1", "name": "South America (São Paulo)", - "country": "br" + "flag": "br" }, { "value": "us-east-1", "name": "US East (N. Virginia)", - "country": "us" + "flag": "us" }, { "value": "us-east-2", "name": "US East (Ohio)", - "country": "us" + "flag": "us" }, { "value": "us-west-1", "name": "US West (N. California)", - "country": "us" + "flag": "us" }, { "value": "us-west-2", "name": "US West (Oregon)", - "country": "us" + "flag": "us" } ] From 7408129c098cbba6f896234c5bd41df6d34799f8 Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:13:14 +0000 Subject: [PATCH 08/11] docs(registry/coder/modules/aws-region): make regions.json update a single script Replace the print-and-hand-edit steps with one script that writes regions.json end to end: region names from the global-infrastructure longName SSM parameters, and flag from the lowercased geolocationCountry (eu-* regions share the eu flag). --- registry/coder/modules/aws-region/README.md | 41 +++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index 23ec5de36..8d71645e8 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -138,26 +138,37 @@ module needs no AWS provider or credentials at plan time. Flag icons are not stored in the JSON: each entry carries a `flag` code that maps to an emoji in the `flags` map in `main.tf`. -To refresh the list from AWS, use the AWS CLI. Region codes come from -`ec2:DescribeRegions`, and the human-readable names come from the public -`global-infrastructure` SSM parameters (hosted in `us-east-1`): +Regenerate the whole file from AWS with the AWS CLI (any credentials) and `jq`, +run from this module's directory. Region names come from the public +`global-infrastructure` SSM parameters in `us-east-1`, and each `flag` is the +region's lowercased `geolocationCountry`, except European (`eu-*`) regions, which +share the `eu` flag: ```bash +#!/usr/bin/env bash +set -euo pipefail + +# Read a global-infrastructure attribute for a region, e.g. longName. +get() { + aws ssm get-parameter --region us-east-1 \ + --name "/aws/service/global-infrastructure/regions/$1/$2" \ + --query Parameter.Value --output text +} + for region in $(aws ec2 describe-regions --all-regions \ - --query 'Regions[].RegionName' --output text); do - name=$(aws ssm get-parameter --region us-east-1 \ - --name "/aws/service/global-infrastructure/regions/$region/longName" \ - --query 'Parameter.Value' --output text) - printf '%s\t%s\n' "$region" "$name" -done + --query 'Regions[].RegionName' --output text | tr '\t' '\n' | sort); do + name=$(get "$region" longName) + if [ "${region%%-*}" = "eu" ]; then + flag=eu + else + flag=$(get "$region" geolocationCountry | tr '[:upper:]' '[:lower:]') + fi + jq -n --arg value "$region" --arg name "$name" --arg flag "$flag" '{$value, $name, $flag}' +done | jq -s '.' > regions.json ``` -For each region, add or update an entry in `regions.json` with: - -- `value`: the region code, e.g. `us-east-1`. -- `name`: the display name. Use the `longName` verbatim so names stay consistent; AWS returns `Europe (...)` for every European region, `US East (...)`, and so on. -- `flag`: the key of the flag to show, from the `flags` map in `main.tf`. Add a - new entry to that map if the region needs a flag that is not already listed. +If a new region uses a `flag` code that isn't in the `flags` map in `main.tf`, +add a matching ` = "/emojis/.png"` entry there too. ## Related templates From e24846c822b0772cb9786d8f3af7c2f66b4aadfa Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:27:44 +0000 Subject: [PATCH 09/11] refactor(registry/coder/modules/aws-region): derive flag in terraform regions.json now stores only region ID and display name; the regeneration script emits just those. main.tf derives each region's flag from its ID: a region uses its geographic prefix's flag (us-*, eu-*, ...) with explicit entries for the multi-country Asia Pacific and Middle East prefixes, then resolves the emoji from flag_emojis. Verified options and icons render under coder/preview and terraform apply. --- registry/coder/modules/aws-region/README.md | 35 +++----- registry/coder/modules/aws-region/main.tf | 52 +++++++++--- .../coder/modules/aws-region/regions.json | 84 +++++++------------ 3 files changed, 82 insertions(+), 89 deletions(-) diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index 8d71645e8..aee715cea 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -134,41 +134,30 @@ provider "aws" { ## Updating regions.json `regions.json` is a static catalog of region IDs and display names, so the -module needs no AWS provider or credentials at plan time. Flag icons are not -stored in the JSON: each entry carries a `flag` code that maps to an emoji in -the `flags` map in `main.tf`. +module needs no AWS provider or credentials at plan time. Flags are not stored +here: `main.tf` derives each region's flag emoji from its ID (see the +`prefix_flags` and `region_flags` maps). Regenerate the whole file from AWS with the AWS CLI (any credentials) and `jq`, -run from this module's directory. Region names come from the public -`global-infrastructure` SSM parameters in `us-east-1`, and each `flag` is the -region's lowercased `geolocationCountry`, except European (`eu-*`) regions, which -share the `eu` flag: +run from this module's directory. Names come from the public +`global-infrastructure` SSM parameters in `us-east-1`: ```bash #!/usr/bin/env bash set -euo pipefail -# Read a global-infrastructure attribute for a region, e.g. longName. -get() { - aws ssm get-parameter --region us-east-1 \ - --name "/aws/service/global-infrastructure/regions/$1/$2" \ - --query Parameter.Value --output text -} - for region in $(aws ec2 describe-regions --all-regions \ --query 'Regions[].RegionName' --output text | tr '\t' '\n' | sort); do - name=$(get "$region" longName) - if [ "${region%%-*}" = "eu" ]; then - flag=eu - else - flag=$(get "$region" geolocationCountry | tr '[:upper:]' '[:lower:]') - fi - jq -n --arg value "$region" --arg name "$name" --arg flag "$flag" '{$value, $name, $flag}' + name=$(aws ssm get-parameter --region us-east-1 \ + --name "/aws/service/global-infrastructure/regions/$region/longName" \ + --query Parameter.Value --output text) + jq -n --arg value "$region" --arg name "$name" '{$value, $name}' done | jq -s '.' > regions.json ``` -If a new region uses a `flag` code that isn't in the `flags` map in `main.tf`, -add a matching ` = "/emojis/.png"` entry there too. +A region in a multi-country area (`ap-*` or `me-*`) or one that introduces a new +country needs an entry in `region_flags`/`prefix_flags` and `flag_emojis` in +`main.tf`. ## Related templates diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index ca8b9abc1..52b2498f5 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -74,11 +74,9 @@ variable "create_parameter" { } locals { - # Flag emoji per flag code. Region rows in regions.json reference these by - # their "flag" field, so the icon paths live in one place here instead of - # being repeated for every region in the JSON. Codes are the two-letter emoji - # code (for example "us"), or "eu" for the shared European flag. - flags = { + # Flag emoji per two-letter flag code. Defined here so the icon paths live in + # one place; "eu" is the shared European flag. + flag_emojis = { au = "/emojis/1f1e6-1f1fa.png" bh = "/emojis/1f1e7-1f1ed.png" br = "/emojis/1f1e7-1f1f7.png" @@ -95,9 +93,35 @@ locals { za = "/emojis/1f1ff-1f1e6.png" } + # Flag per region, derived from the region ID so regions.json only needs the + # region ID and display name. A region defaults to its geographic prefix's + # flag (every us-* region uses "us", every eu-* region shares "eu", and so on); + # the Asia Pacific and Middle East prefixes span several countries, so those + # regions are mapped individually. + prefix_flags = { + af = "za" + ca = "ca" + eu = "eu" + il = "il" + sa = "br" + us = "us" + } + region_flags = { + "ap-east-1" = "hk" + "ap-northeast-1" = "jp" + "ap-northeast-2" = "kr" + "ap-northeast-3" = "jp" + "ap-south-1" = "in" + "ap-south-2" = "in" + "ap-southeast-1" = "sg" + "ap-southeast-2" = "au" + "ap-southeast-3" = "id" + "ap-southeast-4" = "au" + "me-south-1" = "bh" + } + # Region catalog (see regions.json). Kept as static data so the module needs - # no AWS provider or credentials at plan time. Each region resolves its flag - # from local.flags and a default availability zone of "a". + # no AWS provider or credentials at plan time. # # The try() reads regions.json under both Terraform and Coder's dynamic # parameters preview: Terraform resolves file() relative to the root module @@ -106,12 +130,20 @@ locals { # deep). Without the fallback the parameter renders with no options under # dynamic parameters. regions = jsondecode(try(file("${path.module}/regions.json"), file("regions.json"))) + + # Resolve each region's flag code once: an explicit entry wins, otherwise the + # geographic prefix supplies it. + region_flag = { + for region in local.regions : region.value => + try(local.region_flags[region.value], local.prefix_flags[split("-", region.value)[0]]) + } + regions_by_id = { for region in local.regions : region.value => { value = region.value name = region.name - flag = region.flag - icon = local.flags[region.flag] + flag = local.region_flag[region.value] + icon = local.flag_emojis[local.region_flag[region.value]] default_availability_zone = "${region.value}a" } } @@ -131,7 +163,7 @@ data "coder_parameter" "region" { for_each = [for region in local.regions : region if !contains(var.exclude, region.value)] content { name = try(var.custom_names[option.value.value], option.value.name) - icon = try(var.custom_icons[option.value.value], local.flags[option.value.flag]) + icon = try(var.custom_icons[option.value.value], local.flag_emojis[local.region_flag[option.value.value]]) value = option.value.value } } diff --git a/registry/coder/modules/aws-region/regions.json b/registry/coder/modules/aws-region/regions.json index a1bc512c6..78d827185 100644 --- a/registry/coder/modules/aws-region/regions.json +++ b/registry/coder/modules/aws-region/regions.json @@ -1,142 +1,114 @@ [ { "value": "af-south-1", - "name": "Africa (Cape Town)", - "flag": "za" + "name": "Africa (Cape Town)" }, { "value": "ap-east-1", - "name": "Asia Pacific (Hong Kong)", - "flag": "hk" + "name": "Asia Pacific (Hong Kong)" }, { "value": "ap-northeast-1", - "name": "Asia Pacific (Tokyo)", - "flag": "jp" + "name": "Asia Pacific (Tokyo)" }, { "value": "ap-northeast-2", - "name": "Asia Pacific (Seoul)", - "flag": "kr" + "name": "Asia Pacific (Seoul)" }, { "value": "ap-northeast-3", - "name": "Asia Pacific (Osaka)", - "flag": "jp" + "name": "Asia Pacific (Osaka)" }, { "value": "ap-south-1", - "name": "Asia Pacific (Mumbai)", - "flag": "in" + "name": "Asia Pacific (Mumbai)" }, { "value": "ap-south-2", - "name": "Asia Pacific (Hyderabad)", - "flag": "in" + "name": "Asia Pacific (Hyderabad)" }, { "value": "ap-southeast-1", - "name": "Asia Pacific (Singapore)", - "flag": "sg" + "name": "Asia Pacific (Singapore)" }, { "value": "ap-southeast-2", - "name": "Asia Pacific (Sydney)", - "flag": "au" + "name": "Asia Pacific (Sydney)" }, { "value": "ap-southeast-3", - "name": "Asia Pacific (Jakarta)", - "flag": "id" + "name": "Asia Pacific (Jakarta)" }, { "value": "ap-southeast-4", - "name": "Asia Pacific (Melbourne)", - "flag": "au" + "name": "Asia Pacific (Melbourne)" }, { "value": "ca-central-1", - "name": "Canada (Central)", - "flag": "ca" + "name": "Canada (Central)" }, { "value": "ca-west-1", - "name": "Canada West (Calgary)", - "flag": "ca" + "name": "Canada West (Calgary)" }, { "value": "eu-central-1", - "name": "Europe (Frankfurt)", - "flag": "eu" + "name": "Europe (Frankfurt)" }, { "value": "eu-central-2", - "name": "Europe (Zurich)", - "flag": "eu" + "name": "Europe (Zurich)" }, { "value": "eu-north-1", - "name": "Europe (Stockholm)", - "flag": "eu" + "name": "Europe (Stockholm)" }, { "value": "eu-south-1", - "name": "Europe (Milan)", - "flag": "eu" + "name": "Europe (Milan)" }, { "value": "eu-south-2", - "name": "Europe (Spain)", - "flag": "eu" + "name": "Europe (Spain)" }, { "value": "eu-west-1", - "name": "Europe (Ireland)", - "flag": "eu" + "name": "Europe (Ireland)" }, { "value": "eu-west-2", - "name": "Europe (London)", - "flag": "eu" + "name": "Europe (London)" }, { "value": "eu-west-3", - "name": "Europe (Paris)", - "flag": "eu" + "name": "Europe (Paris)" }, { "value": "il-central-1", - "name": "Israel (Tel Aviv)", - "flag": "il" + "name": "Israel (Tel Aviv)" }, { "value": "me-south-1", - "name": "Middle East (Bahrain)", - "flag": "bh" + "name": "Middle East (Bahrain)" }, { "value": "sa-east-1", - "name": "South America (São Paulo)", - "flag": "br" + "name": "South America (São Paulo)" }, { "value": "us-east-1", - "name": "US East (N. Virginia)", - "flag": "us" + "name": "US East (N. Virginia)" }, { "value": "us-east-2", - "name": "US East (Ohio)", - "flag": "us" + "name": "US East (Ohio)" }, { "value": "us-west-1", - "name": "US West (N. California)", - "flag": "us" + "name": "US West (N. California)" }, { "value": "us-west-2", - "name": "US West (Oregon)", - "flag": "us" + "name": "US West (Oregon)" } ] From 91b47ba5dc55e88964d650f86be0deb4b960d73f Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:36:42 +0000 Subject: [PATCH 10/11] refactor(registry/coder/modules/aws-region): simplify flags to one map Replace the flag_emojis + prefix_flags + region_flags + derived region_flag machinery with a single region_icons map (region ID -> emoji) and a direct lookup. Drops the redundant flag code field from the regions output; the icon is the flag. --- registry/coder/modules/aws-region/README.md | 17 ++- registry/coder/modules/aws-region/main.tf | 100 +++++++----------- .../coder/modules/aws-region/main.tftest.hcl | 3 +- 3 files changed, 44 insertions(+), 76 deletions(-) diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index aee715cea..8c74ba28d 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -125,18 +125,17 @@ provider "aws" { ## Outputs -| Output | Description | -| --------------------------- | -------------------------------------------------------------------------------------------- | -| `value` | The ID of the selected region, e.g. `us-east-1`. | -| `default_availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | -| `regions` | Every region keyed by ID, each with `name`, `flag`, `icon`, and `default_availability_zone`. | +| Output | Description | +| --------------------------- | ------------------------------------------------------------------------------------ | +| `value` | The ID of the selected region, e.g. `us-east-1`. | +| `default_availability_zone` | The default availability zone for the selected region, e.g. `us-east-1a`. | +| `regions` | Every region keyed by ID, each with `name`, `icon`, and `default_availability_zone`. | ## Updating regions.json `regions.json` is a static catalog of region IDs and display names, so the module needs no AWS provider or credentials at plan time. Flags are not stored -here: `main.tf` derives each region's flag emoji from its ID (see the -`prefix_flags` and `region_flags` maps). +here: each region's flag emoji comes from the `region_icons` map in `main.tf`. Regenerate the whole file from AWS with the AWS CLI (any credentials) and `jq`, run from this module's directory. Names come from the public @@ -155,9 +154,7 @@ for region in $(aws ec2 describe-regions --all-regions \ done | jq -s '.' > regions.json ``` -A region in a multi-country area (`ap-*` or `me-*`) or one that introduces a new -country needs an entry in `region_flags`/`prefix_flags` and `flag_emojis` in -`main.tf`. +A new region also needs an entry in the `region_icons` map in `main.tf`. ## Related templates diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 52b2498f5..62c73c728 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -74,76 +74,48 @@ variable "create_parameter" { } locals { - # Flag emoji per two-letter flag code. Defined here so the icon paths live in - # one place; "eu" is the shared European flag. - flag_emojis = { - au = "/emojis/1f1e6-1f1fa.png" - bh = "/emojis/1f1e7-1f1ed.png" - br = "/emojis/1f1e7-1f1f7.png" - ca = "/emojis/1f1e8-1f1e6.png" - eu = "/emojis/1f1ea-1f1fa.png" - hk = "/emojis/1f1ed-1f1f0.png" - id = "/emojis/1f1ee-1f1e9.png" - il = "/emojis/1f1ee-1f1f1.png" - in = "/emojis/1f1ee-1f1f3.png" - jp = "/emojis/1f1ef-1f1f5.png" - kr = "/emojis/1f1f0-1f1f7.png" - sg = "/emojis/1f1f8-1f1ec.png" - us = "/emojis/1f1fa-1f1f8.png" - za = "/emojis/1f1ff-1f1e6.png" - } - - # Flag per region, derived from the region ID so regions.json only needs the - # region ID and display name. A region defaults to its geographic prefix's - # flag (every us-* region uses "us", every eu-* region shares "eu", and so on); - # the Asia Pacific and Middle East prefixes span several countries, so those - # regions are mapped individually. - prefix_flags = { - af = "za" - ca = "ca" - eu = "eu" - il = "il" - sa = "br" - us = "us" - } - region_flags = { - "ap-east-1" = "hk" - "ap-northeast-1" = "jp" - "ap-northeast-2" = "kr" - "ap-northeast-3" = "jp" - "ap-south-1" = "in" - "ap-south-2" = "in" - "ap-southeast-1" = "sg" - "ap-southeast-2" = "au" - "ap-southeast-3" = "id" - "ap-southeast-4" = "au" - "me-south-1" = "bh" - } - - # Region catalog (see regions.json). Kept as static data so the module needs - # no AWS provider or credentials at plan time. - # - # The try() reads regions.json under both Terraform and Coder's dynamic - # parameters preview: Terraform resolves file() relative to the root module - # (so path.module is required), while the preview evaluator resolves it - # relative to this module's own directory (so path.module points one level too - # deep). Without the fallback the parameter renders with no options under - # dynamic parameters. + # Read regions.json with a fallback: Terraform resolves file() from the root + # module, but Coder's dynamic parameters preview resolves it from this module's + # directory, so neither path works on its own. regions = jsondecode(try(file("${path.module}/regions.json"), file("regions.json"))) - # Resolve each region's flag code once: an explicit entry wins, otherwise the - # geographic prefix supplies it. - region_flag = { - for region in local.regions : region.value => - try(local.region_flags[region.value], local.prefix_flags[split("-", region.value)[0]]) + # Flag emoji shown for each region. European regions share the EU flag. + region_icons = { + "af-south-1" = "/emojis/1f1ff-1f1e6.png" + "ap-east-1" = "/emojis/1f1ed-1f1f0.png" + "ap-northeast-1" = "/emojis/1f1ef-1f1f5.png" + "ap-northeast-2" = "/emojis/1f1f0-1f1f7.png" + "ap-northeast-3" = "/emojis/1f1ef-1f1f5.png" + "ap-south-1" = "/emojis/1f1ee-1f1f3.png" + "ap-south-2" = "/emojis/1f1ee-1f1f3.png" + "ap-southeast-1" = "/emojis/1f1f8-1f1ec.png" + "ap-southeast-2" = "/emojis/1f1e6-1f1fa.png" + "ap-southeast-3" = "/emojis/1f1ee-1f1e9.png" + "ap-southeast-4" = "/emojis/1f1e6-1f1fa.png" + "ca-central-1" = "/emojis/1f1e8-1f1e6.png" + "ca-west-1" = "/emojis/1f1e8-1f1e6.png" + "eu-central-1" = "/emojis/1f1ea-1f1fa.png" + "eu-central-2" = "/emojis/1f1ea-1f1fa.png" + "eu-north-1" = "/emojis/1f1ea-1f1fa.png" + "eu-south-1" = "/emojis/1f1ea-1f1fa.png" + "eu-south-2" = "/emojis/1f1ea-1f1fa.png" + "eu-west-1" = "/emojis/1f1ea-1f1fa.png" + "eu-west-2" = "/emojis/1f1ea-1f1fa.png" + "eu-west-3" = "/emojis/1f1ea-1f1fa.png" + "il-central-1" = "/emojis/1f1ee-1f1f1.png" + "me-south-1" = "/emojis/1f1e7-1f1ed.png" + "sa-east-1" = "/emojis/1f1e7-1f1f7.png" + "us-east-1" = "/emojis/1f1fa-1f1f8.png" + "us-east-2" = "/emojis/1f1fa-1f1f8.png" + "us-west-1" = "/emojis/1f1fa-1f1f8.png" + "us-west-2" = "/emojis/1f1fa-1f1f8.png" } regions_by_id = { for region in local.regions : region.value => { value = region.value name = region.name - flag = local.region_flag[region.value] - icon = local.flag_emojis[local.region_flag[region.value]] + icon = local.region_icons[region.value] default_availability_zone = "${region.value}a" } } @@ -163,7 +135,7 @@ data "coder_parameter" "region" { for_each = [for region in local.regions : region if !contains(var.exclude, region.value)] content { name = try(var.custom_names[option.value.value], option.value.name) - icon = try(var.custom_icons[option.value.value], local.flag_emojis[local.region_flag[option.value.value]]) + icon = try(var.custom_icons[option.value.value], local.region_icons[option.value.value]) value = option.value.value } } @@ -180,6 +152,6 @@ output "default_availability_zone" { } output "regions" { - description = "All AWS regions keyed by region ID, each with name, flag, icon, and default_availability_zone." + description = "All AWS regions keyed by region ID, each with name, icon, and default_availability_zone." value = local.regions_by_id } diff --git a/registry/coder/modules/aws-region/main.tftest.hcl b/registry/coder/modules/aws-region/main.tftest.hcl index 9a17928d6..781439a65 100644 --- a/registry/coder/modules/aws-region/main.tftest.hcl +++ b/registry/coder/modules/aws-region/main.tftest.hcl @@ -62,11 +62,10 @@ run "regions_output_exposes_catalog" { assert { condition = ( output.regions["ap-northeast-1"].name == "Asia Pacific (Tokyo)" && - output.regions["ap-northeast-1"].flag == "jp" && output.regions["ap-northeast-1"].icon == "/emojis/1f1ef-1f1f5.png" && output.regions["ap-northeast-1"].default_availability_zone == "ap-northeast-1a" ) - error_message = "regions entries should expose name, flag, icon, and default_availability_zone" + error_message = "regions entries should expose name, icon, and default_availability_zone" } } From 0fdb972ba6081e231f3bb0b669d86662d1914254 Mon Sep 17 00:00:00 2001 From: phorcys420 <57866459+phorcys420@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:51:19 +0000 Subject: [PATCH 11/11] refactor(registry/coder/modules/aws-region): move region icons into regions.json --- registry/coder/modules/aws-region/README.md | 29 +++++-- registry/coder/modules/aws-region/main.tf | 36 +------- .../coder/modules/aws-region/regions.json | 84 ++++++++++++------- 3 files changed, 80 insertions(+), 69 deletions(-) diff --git a/registry/coder/modules/aws-region/README.md b/registry/coder/modules/aws-region/README.md index 8c74ba28d..ceb82f291 100644 --- a/registry/coder/modules/aws-region/README.md +++ b/registry/coder/modules/aws-region/README.md @@ -133,29 +133,44 @@ provider "aws" { ## Updating regions.json -`regions.json` is a static catalog of region IDs and display names, so the -module needs no AWS provider or credentials at plan time. Flags are not stored -here: each region's flag emoji comes from the `region_icons` map in `main.tf`. +`regions.json` is a generated catalog of region IDs, display names, and flag +icons, so the module needs no AWS provider or credentials at plan time. +Terraform only reads the file; all the flag logic lives in the script below. Regenerate the whole file from AWS with the AWS CLI (any credentials) and `jq`, -run from this module's directory. Names come from the public +run from this module's directory. Names and country codes come from the public `global-infrastructure` SSM parameters in `us-east-1`: ```bash #!/usr/bin/env bash set -euo pipefail +# Two-letter country code -> Coder flag emoji asset (regional indicator pair). +icon() { + local a b + a=$(printf '%x' $((0x1f1e6 + $(printf '%d' "'${1:0:1}") - 0x61))) + b=$(printf '%x' $((0x1f1e6 + $(printf '%d' "'${1:1:1}") - 0x61))) + printf '/emojis/%s-%s.png' "$a" "$b" +} + for region in $(aws ec2 describe-regions --all-regions \ --query 'Regions[].RegionName' --output text | tr '\t' '\n' | sort); do name=$(aws ssm get-parameter --region us-east-1 \ --name "/aws/service/global-infrastructure/regions/$region/longName" \ --query Parameter.Value --output text) - jq -n --arg value "$region" --arg name "$name" '{$value, $name}' + # European regions share the EU flag; every other region uses its country flag. + if [[ $region == eu-* ]]; then + country=eu + else + country=$(aws ssm get-parameter --region us-east-1 \ + --name "/aws/service/global-infrastructure/regions/$region/geolocationCountry" \ + --query Parameter.Value --output text | tr '[:upper:]' '[:lower:]') + fi + jq -n --arg value "$region" --arg name "$name" --arg icon "$(icon "$country")" \ + '{$value, $name, $icon}' done | jq -s '.' > regions.json ``` -A new region also needs an entry in the `region_icons` map in `main.tf`. - ## Related templates For a complete AWS EC2 template, see the following examples in the [Coder Registry](https://registry.coder.com/). diff --git a/registry/coder/modules/aws-region/main.tf b/registry/coder/modules/aws-region/main.tf index 62c73c728..3edd76134 100644 --- a/registry/coder/modules/aws-region/main.tf +++ b/registry/coder/modules/aws-region/main.tf @@ -79,43 +79,11 @@ locals { # directory, so neither path works on its own. regions = jsondecode(try(file("${path.module}/regions.json"), file("regions.json"))) - # Flag emoji shown for each region. European regions share the EU flag. - region_icons = { - "af-south-1" = "/emojis/1f1ff-1f1e6.png" - "ap-east-1" = "/emojis/1f1ed-1f1f0.png" - "ap-northeast-1" = "/emojis/1f1ef-1f1f5.png" - "ap-northeast-2" = "/emojis/1f1f0-1f1f7.png" - "ap-northeast-3" = "/emojis/1f1ef-1f1f5.png" - "ap-south-1" = "/emojis/1f1ee-1f1f3.png" - "ap-south-2" = "/emojis/1f1ee-1f1f3.png" - "ap-southeast-1" = "/emojis/1f1f8-1f1ec.png" - "ap-southeast-2" = "/emojis/1f1e6-1f1fa.png" - "ap-southeast-3" = "/emojis/1f1ee-1f1e9.png" - "ap-southeast-4" = "/emojis/1f1e6-1f1fa.png" - "ca-central-1" = "/emojis/1f1e8-1f1e6.png" - "ca-west-1" = "/emojis/1f1e8-1f1e6.png" - "eu-central-1" = "/emojis/1f1ea-1f1fa.png" - "eu-central-2" = "/emojis/1f1ea-1f1fa.png" - "eu-north-1" = "/emojis/1f1ea-1f1fa.png" - "eu-south-1" = "/emojis/1f1ea-1f1fa.png" - "eu-south-2" = "/emojis/1f1ea-1f1fa.png" - "eu-west-1" = "/emojis/1f1ea-1f1fa.png" - "eu-west-2" = "/emojis/1f1ea-1f1fa.png" - "eu-west-3" = "/emojis/1f1ea-1f1fa.png" - "il-central-1" = "/emojis/1f1ee-1f1f1.png" - "me-south-1" = "/emojis/1f1e7-1f1ed.png" - "sa-east-1" = "/emojis/1f1e7-1f1f7.png" - "us-east-1" = "/emojis/1f1fa-1f1f8.png" - "us-east-2" = "/emojis/1f1fa-1f1f8.png" - "us-west-1" = "/emojis/1f1fa-1f1f8.png" - "us-west-2" = "/emojis/1f1fa-1f1f8.png" - } - regions_by_id = { for region in local.regions : region.value => { value = region.value name = region.name - icon = local.region_icons[region.value] + icon = region.icon default_availability_zone = "${region.value}a" } } @@ -135,7 +103,7 @@ data "coder_parameter" "region" { for_each = [for region in local.regions : region if !contains(var.exclude, region.value)] content { name = try(var.custom_names[option.value.value], option.value.name) - icon = try(var.custom_icons[option.value.value], local.region_icons[option.value.value]) + icon = try(var.custom_icons[option.value.value], option.value.icon) value = option.value.value } } diff --git a/registry/coder/modules/aws-region/regions.json b/registry/coder/modules/aws-region/regions.json index 78d827185..7ae421da6 100644 --- a/registry/coder/modules/aws-region/regions.json +++ b/registry/coder/modules/aws-region/regions.json @@ -1,114 +1,142 @@ [ { "value": "af-south-1", - "name": "Africa (Cape Town)" + "name": "Africa (Cape Town)", + "icon": "/emojis/1f1ff-1f1e6.png" }, { "value": "ap-east-1", - "name": "Asia Pacific (Hong Kong)" + "name": "Asia Pacific (Hong Kong)", + "icon": "/emojis/1f1ed-1f1f0.png" }, { "value": "ap-northeast-1", - "name": "Asia Pacific (Tokyo)" + "name": "Asia Pacific (Tokyo)", + "icon": "/emojis/1f1ef-1f1f5.png" }, { "value": "ap-northeast-2", - "name": "Asia Pacific (Seoul)" + "name": "Asia Pacific (Seoul)", + "icon": "/emojis/1f1f0-1f1f7.png" }, { "value": "ap-northeast-3", - "name": "Asia Pacific (Osaka)" + "name": "Asia Pacific (Osaka)", + "icon": "/emojis/1f1ef-1f1f5.png" }, { "value": "ap-south-1", - "name": "Asia Pacific (Mumbai)" + "name": "Asia Pacific (Mumbai)", + "icon": "/emojis/1f1ee-1f1f3.png" }, { "value": "ap-south-2", - "name": "Asia Pacific (Hyderabad)" + "name": "Asia Pacific (Hyderabad)", + "icon": "/emojis/1f1ee-1f1f3.png" }, { "value": "ap-southeast-1", - "name": "Asia Pacific (Singapore)" + "name": "Asia Pacific (Singapore)", + "icon": "/emojis/1f1f8-1f1ec.png" }, { "value": "ap-southeast-2", - "name": "Asia Pacific (Sydney)" + "name": "Asia Pacific (Sydney)", + "icon": "/emojis/1f1e6-1f1fa.png" }, { "value": "ap-southeast-3", - "name": "Asia Pacific (Jakarta)" + "name": "Asia Pacific (Jakarta)", + "icon": "/emojis/1f1ee-1f1e9.png" }, { "value": "ap-southeast-4", - "name": "Asia Pacific (Melbourne)" + "name": "Asia Pacific (Melbourne)", + "icon": "/emojis/1f1e6-1f1fa.png" }, { "value": "ca-central-1", - "name": "Canada (Central)" + "name": "Canada (Central)", + "icon": "/emojis/1f1e8-1f1e6.png" }, { "value": "ca-west-1", - "name": "Canada West (Calgary)" + "name": "Canada West (Calgary)", + "icon": "/emojis/1f1e8-1f1e6.png" }, { "value": "eu-central-1", - "name": "Europe (Frankfurt)" + "name": "Europe (Frankfurt)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-central-2", - "name": "Europe (Zurich)" + "name": "Europe (Zurich)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-north-1", - "name": "Europe (Stockholm)" + "name": "Europe (Stockholm)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-south-1", - "name": "Europe (Milan)" + "name": "Europe (Milan)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-south-2", - "name": "Europe (Spain)" + "name": "Europe (Spain)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-west-1", - "name": "Europe (Ireland)" + "name": "Europe (Ireland)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-west-2", - "name": "Europe (London)" + "name": "Europe (London)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "eu-west-3", - "name": "Europe (Paris)" + "name": "Europe (Paris)", + "icon": "/emojis/1f1ea-1f1fa.png" }, { "value": "il-central-1", - "name": "Israel (Tel Aviv)" + "name": "Israel (Tel Aviv)", + "icon": "/emojis/1f1ee-1f1f1.png" }, { "value": "me-south-1", - "name": "Middle East (Bahrain)" + "name": "Middle East (Bahrain)", + "icon": "/emojis/1f1e7-1f1ed.png" }, { "value": "sa-east-1", - "name": "South America (São Paulo)" + "name": "South America (São Paulo)", + "icon": "/emojis/1f1e7-1f1f7.png" }, { "value": "us-east-1", - "name": "US East (N. Virginia)" + "name": "US East (N. Virginia)", + "icon": "/emojis/1f1fa-1f1f8.png" }, { "value": "us-east-2", - "name": "US East (Ohio)" + "name": "US East (Ohio)", + "icon": "/emojis/1f1fa-1f1f8.png" }, { "value": "us-west-1", - "name": "US West (N. California)" + "name": "US West (N. California)", + "icon": "/emojis/1f1fa-1f1f8.png" }, { "value": "us-west-2", - "name": "US West (Oregon)" + "name": "US West (Oregon)", + "icon": "/emojis/1f1fa-1f1f8.png" } ]