diff --git a/.icons/exoscale.svg b/.icons/exoscale.svg new file mode 100644 index 0000000..c56a615 --- /dev/null +++ b/.icons/exoscale.svg @@ -0,0 +1 @@ +Artboard 1 \ No newline at end of file diff --git a/.images/exoscale-custom.png b/.images/exoscale-custom.png new file mode 100644 index 0000000..3646c8c Binary files /dev/null and b/.images/exoscale-custom.png differ diff --git a/.images/exoscale-exclude.png b/.images/exoscale-exclude.png new file mode 100644 index 0000000..40683c1 Binary files /dev/null and b/.images/exoscale-exclude.png differ diff --git a/.images/exoscale-instance-custom.png b/.images/exoscale-instance-custom.png new file mode 100644 index 0000000..04373e3 Binary files /dev/null and b/.images/exoscale-instance-custom.png differ diff --git a/.images/exoscale-instance-exclude.png b/.images/exoscale-instance-exclude.png new file mode 100644 index 0000000..1261b24 Binary files /dev/null and b/.images/exoscale-instance-exclude.png differ diff --git a/.images/exoscale-instance-types.png b/.images/exoscale-instance-types.png new file mode 100644 index 0000000..9158830 Binary files /dev/null and b/.images/exoscale-instance-types.png differ diff --git a/.images/exoscale-zones.png b/.images/exoscale-zones.png new file mode 100644 index 0000000..b78cd01 Binary files /dev/null and b/.images/exoscale-zones.png differ diff --git a/code-server/README.md b/code-server/README.md index 302a1ea..ca076df 100644 --- a/code-server/README.md +++ b/code-server/README.md @@ -63,6 +63,18 @@ module "settings" { } ``` +### Install multiple extensions + +Just run code-server in the background, don't fetch it from GitHub: + +```hcl +module "settings" { + source = "https://registry.coder.com/modules/code-server" + agent_id = coder_agent.example.id + extensions = [ "dracula-theme.theme-dracula", "ms-azuretools.vscode-docker" ] +} +``` + ### Offline Mode Just run code-server in the background, don't fetch it from GitHub: diff --git a/code-server/main.tf b/code-server/main.tf index 837f0db..86afdd0 100644 --- a/code-server/main.tf +++ b/code-server/main.tf @@ -62,6 +62,15 @@ variable "install_version" { default = "" } +variable "share" { + type = string + default = "owner" + validation { + condition = var.share == "owner" || var.share == "authenticated" || var.share == "public" + error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'." + } +} + resource "coder_script" "code-server" { agent_id = var.agent_id display_name = "code-server" @@ -85,7 +94,7 @@ resource "coder_app" "code-server" { url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}" icon = "/icon/code.svg" subdomain = false - share = "owner" + share = var.share healthcheck { url = "http://localhost:${var.port}/healthz" diff --git a/code-server/run.sh b/code-server/run.sh index 6676aaa..552ced1 100755 --- a/code-server/run.sh +++ b/code-server/run.sh @@ -25,7 +25,8 @@ printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n" CODE_SERVER="${INSTALL_PREFIX}/bin/code-server" # Install each extension... -for extension in "$${EXTENSIONS[@]}"; do +IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}" +for extension in "$${EXTENSIONLIST[@]}"; do if [ -z "$extension" ]; then continue fi @@ -46,4 +47,4 @@ fi echo "👷 Running code-server in the background..." echo "Check logs at ${LOG_PATH}!" -$CODE_SERVER --auth none --port ${PORT} >${LOG_PATH} 2>&1 & +$CODE_SERVER --auth none --port ${PORT} >${LOG_PATH} 2>&1 & \ No newline at end of file diff --git a/exoscale-instance-type/README.md b/exoscale-instance-type/README.md new file mode 100644 index 0000000..e1031e4 --- /dev/null +++ b/exoscale-instance-type/README.md @@ -0,0 +1,109 @@ +--- +display_name: exoscale-instance-type +description: A parameter with human readable exoscale instance names +icon: ../.icons/exoscale.svg +maintainer_github: WhizUs +verified: false +tags: [helper, parameter, instances, exoscale] +--- + +# exoscale-instance-type + +A parameter with all Exoscale instance types. This allows developers to select +their desired virtuell machine for the workspace. + +Customize the preselected parameter value: + +```hcl +module "exoscale-instance-type" { + source = "https://registry.coder.com/modules/exoscale-instance-type" + default = "standard.medium" +} + +resource "exoscale_compute_instance" "instance" { + type = module.exoscale-instance-type.value + ... +} + +resource "coder_metadata" "workspace_info" { + item { + key = "instance type" + value = module.exoscale-instance-type.name + } +} +``` + +![Exoscale instance types](../.images/exoscale-instance-types.png) + +## Examples + +### Customize type + +Change the display name a type using the corresponding maps: + +```hcl +module "exoscale-instance-type" { + source = "https://registry.coder.com/modules/exoscale-instance-type" + default = "standard.medium" + custom_names = { + "standard.medium": "Mittlere Instanz" # German translation + } + custom_descriptions = { + "standard.medium": "4 GB Arbeitsspeicher, 2 Kerne, 10 - 400 GB Festplatte" # German translation + } +} + +resource "exoscale_compute_instance" "instance" { + type = module.exoscale-instance-type.value + ... +} + +resource "coder_metadata" "workspace_info" { + item { + key = "instance type" + value = module.exoscale-instance-type.name + } +} +``` + +![Exoscale instance types Custom](../.images/exoscale-instance-custom.png) + +### Use category and exlude type + +Show only gpu1 types + +```hcl +module "exoscale-instance-type" { + source = "https://registry.coder.com/modules/exoscale-instance-type" + default = "gpu.large" + type_category = ["gpu"] + exclude = [ + "gpu2.small", + "gpu2.medium", + "gpu2.large", + "gpu2.huge", + "gpu3.small", + "gpu3.medium", + "gpu3.large", + "gpu3.huge" + ] +} + +resource "exoscale_compute_instance" "instance" { + type = module.exoscale-instance-type.value + ... +} + +resource "coder_metadata" "workspace_info" { + item { + key = "instance type" + value = module.exoscale-instance-type.name + } +} +``` + +![Exoscale instance types category and exclude](../.images/exoscale-instance-exclude.png) + +## Related templates + +A related exoscale template will be provided soon. diff --git a/exoscale-instance-type/main.test.ts b/exoscale-instance-type/main.test.ts new file mode 100644 index 0000000..eeb6745 --- /dev/null +++ b/exoscale-instance-type/main.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("exoscale-instance-type", 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: "gpu3.huge", + type_category: `["gpu", "cpu"]`, + }); + expect(state.outputs.value.value).toBe("gpu3.huge"); + }); + + it("fails because of wrong categroy definition", async () => { + expect(async () => { + await runTerraformApply(import.meta.dir, { + default: "gpu3.huge", + // type_category: ["standard"] is standard + }); + }).toThrow('default value "gpu3.huge" must be defined as one of options'); + }); +}); diff --git a/exoscale-instance-type/main.tf b/exoscale-instance-type/main.tf new file mode 100644 index 0000000..f7c8998 --- /dev/null +++ b/exoscale-instance-type/main.tf @@ -0,0 +1,279 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12" + } + } +} + +variable "display_name" { + default = "Exoscale instance type" + description = "The display name of the parameter." + type = string +} + +variable "description" { + default = "Select the exoscale instance type to use for the workspace. Check out the pricing page for more information: https://www.exoscale.com/pricing" + description = "The description of the parameter." + type = string +} + +variable "default" { + default = "" + description = "The default instance type to use if no type is specified. One of [\"standard.micro\", \"standard.tiny\", \"standard.small\", \"standard.medium\", \"standard.large\", \"standard.extra\", \"standard.huge\", \"standard.mega\", \"standard.titan\", \"standard.jumbo\", \"standard.colossus\", \"cpu.extra\", \"cpu.huge\", \"cpu.mega\", \"cpu.titan\", \"memory.extra\", \"memory.huge\", \"memory.mega\", \"memory.titan\", \"storage.extra\", \"storage.huge\", \"storage.mega\", \"storage.titan\", \"storage.jumbo\", \"gpu.small\", \"gpu.medium\", \"gpu.large\", \"gpu.huge\", \"gpu2.small\", \"gpu2.medium\", \"gpu2.large\", \"gpu2.huge\", \"gpu3.small\", \"gpu3.medium\", \"gpu3.large\", \"gpu3.huge\"]" + type = string +} + +variable "mutable" { + default = false + description = "Whether the parameter can be changed after creation." + type = bool +} + +variable "custom_names" { + default = {} + description = "A map of custom display names for instance type IDs." + type = map(string) +} +variable "custom_descriptions" { + default = {} + description = "A map of custom descriptions for instance type IDs." + type = map(string) +} + +variable "type_category" { + default = ["standard"] + description = "A list of instance type categories the user is allowed to choose. One of [\"standard\", \"cpu\", \"memory\", \"storage\", \"gpu\"]" + type = list(string) +} + +variable "exclude" { + default = [] + description = "A list of instance type IDs to exclude. One of [\"standard.micro\", \"standard.tiny\", \"standard.small\", \"standard.medium\", \"standard.large\", \"standard.extra\", \"standard.huge\", \"standard.mega\", \"standard.titan\", \"standard.jumbo\", \"standard.colossus\", \"cpu.extra\", \"cpu.huge\", \"cpu.mega\", \"cpu.titan\", \"memory.extra\", \"memory.huge\", \"memory.mega\", \"memory.titan\", \"storage.extra\", \"storage.huge\", \"storage.mega\", \"storage.titan\", \"storage.jumbo\", \"gpu.small\", \"gpu.medium\", \"gpu.large\", \"gpu.huge\", \"gpu2.small\", \"gpu2.medium\", \"gpu2.large\", \"gpu2.huge\", \"gpu3.small\", \"gpu3.medium\", \"gpu3.large\", \"gpu3.huge\"]" + type = list(string) +} + +locals { + # https://www.exoscale.com/pricing/ + + standard_instances = [ + { + value = "standard.micro", + name = "Standard Micro", + description = "512 MB RAM, 1 Core, 10 - 200 GB Disk" + }, + { + value = "standard.tiny", + name = "Standard Tiny", + description = "1 GB RAM, 1 Core, 10 - 400 GB Disk" + }, + { + value = "standard.small", + name = "Standard Small", + description = "2 GB RAM, 2 Cores, 10 - 400 GB Disk" + }, + { + value = "standard.medium", + name = "Standard Medium", + description = "4 GB RAM, 2 Cores, 10 - 400 GB Disk" + }, + { + value = "standard.large", + name = "Standard Large", + description = "8 GB RAM, 4 Cores, 10 - 400 GB Disk" + }, + { + value = "standard.extra", + name = "Standard Extra", + description = "rge", + description = "16 GB RAM, 4 Cores, 10 - 800 GB Disk" + }, + { + value = "standard.huge", + name = "Standard Huge", + description = "32 GB RAM, 8 Cores, 10 - 800 GB Disk" + }, + { + value = "standard.mega", + name = "Standard Mega", + description = "64 GB RAM, 12 Cores, 10 - 800 GB Disk" + }, + { + value = "standard.titan", + name = "Standard Titan", + description = "128 GB RAM, 16 Cores, 10 - 1.6 TB Disk" + }, + { + value = "standard.jumbo", + name = "Standard Jumbo", + description = "256 GB RAM, 24 Cores, 10 - 1.6 TB Disk" + }, + { + value = "standard.colossus", + name = "Standard Colossus", + description = "320 GB RAM, 40 Cores, 10 - 1.6 TB Disk" + } + ] + cpu_instances = [ + { + value = "cpu.extra", + name = "CPU Extra-Large", + description = "16 GB RAM, 8 Cores, 10 - 800 GB Disk" + }, + { + value = "cpu.huge", + name = "CPU Huge", + description = "32 GB RAM, 16 Cores, 10 - 800 GB Disk" + }, + { + value = "cpu.mega", + name = "CPU Mega", + description = "64 GB RAM, 32 Cores, 10 - 800 GB Disk" + }, + { + value = "cpu.titan", + name = "CPU Titan", + description = "128 GB RAM, 40 Cores, 0.1 - 1.6 TB Disk" + } + ] + memory_instances = [ + { + value = "memory.extra", + name = "Memory Extra-Large", + description = "16 GB RAM, 2 Cores, 10 - 800 GB Disk" + }, + { + value = "memory.huge", + name = "Memory Huge", + description = "32 GB RAM, 4 Cores, 10 - 800 GB Disk" + }, + { + value = "memory.mega", + name = "Memory Mega", + description = "64 GB RAM, 8 Cores, 10 - 800 GB Disk" + }, + { + value = "memory.titan", + name = "Memory Titan", + description = "128 GB RAM, 12 Cores, 0.1 - 1.6 TB Disk" + } + ] + storage_instances = [ + { + value = "storage.extra", + name = "Storage Extra-Large", + description = "16 GB RAM, 4 Cores, 1 - 2 TB Disk" + }, + { + value = "storage.huge", + name = "Storage Huge", + description = "32 GB RAM, 8 Cores, 2 - 3 TB Disk" + }, + { + value = "storage.mega", + name = "Storage Mega", + description = "64 GB RAM, 12 Cores, 3 - 5 TB Disk" + }, + { + value = "storage.titan", + name = "Storage Titan", + description = "128 GB RAM, 16 Cores, 5 - 10 TB Disk" + }, + { + value = "storage.jumbo", + name = "Storage Jumbo", + description = "225 GB RAM, 24 Cores, 10 - 15 TB Disk" + } + ] + gpu_instances = [ + { + value = "gpu.small", + name = "GPU1 Small", + description = "56 GB RAM, 12 Cores, 1 GPU, 100 - 800 GB Disk" + }, + { + value = "gpu.medium", + name = "GPU1 Medium", + description = "90 GB RAM, 16 Cores, 2 GPU, 0.1 - 1.2 TB Disk" + }, + { + value = "gpu.large", + name = "GPU1 Large", + description = "120 GB RAM, 24 Cores, 3 GPU, 0.1 - 1.6 TB Disk" + }, + { + value = "gpu.huge", + name = "GPU1 Huge", + description = "225 GB RAM, 48 Cores, 4 GPU, 0.1 - 1.6 TB Disk" + }, + { + value = "gpu2.small", + name = "GPU2 Small", + description = "56 GB RAM, 12 Cores, 1 GPU, 100 - 800 GB Disk" + }, + { + value = "gpu2.medium", + name = "GPU2 Medium", + description = "90 GB RAM, 16 Cores, 2 GPU, 0.1 - 1.2 TB Disk" + }, + { + value = "gpu2.large", + name = "GPU2 Large", + description = "120 GB RAM, 24 Cores, 3 GPU, 0.1 - 1.6 TB Disk" + }, + { + value = "gpu2.huge", + name = "GPU2 Huge", + description = "225 GB RAM, 48 Cores, 4 GPU, 0.1 - 1.6 TB Disk" + }, + { + value = "gpu3.small", + name = "GPU3 Small", + description = "56 GB RAM, 12 Cores, 1 GPU, 100 - 800 GB Disk" + }, + { + value = "gpu3.medium", + name = "GPU3 Medium", + description = "120 GB RAM, 24 Cores, 2 GPU, 0.1 - 1.2 TB Disk" + }, + { + value = "gpu3.large", + name = "GPU3 Large", + description = "224 GB RAM, 48 Cores, 4 GPU, 0.1 - 1.6 TB Disk" + }, + { + value = "gpu3.huge", + name = "GPU3 Huge", + description = "448 GB RAM, 96 Cores, 8 GPU, 0.1 - 1.6 TB Disk" + } + ] +} + +data "coder_parameter" "instance_type" { + name = "exoscale_instance_type" + display_name = var.display_name + description = var.description + default = var.default == "" ? null : var.default + mutable = var.mutable + dynamic "option" { + for_each = [for k, v in concat( + contains(var.type_category, "standard") ? local.standard_instances : [], + contains(var.type_category, "cpu") ? local.cpu_instances : [], + contains(var.type_category, "memory") ? local.memory_instances : [], + contains(var.type_category, "storage") ? local.storage_instances : [], + contains(var.type_category, "gpu") ? local.gpu_instances : [] + ) : v if !(contains(var.exclude, v.value))] + content { + name = try(var.custom_names[option.value.value], option.value.name) + description = try(var.custom_descriptions[option.value.value], option.value.description) + value = option.value.value + } + } +} + +output "value" { + value = data.coder_parameter.instance_type.value +} diff --git a/exoscale-zone/README.md b/exoscale-zone/README.md new file mode 100644 index 0000000..91097c8 --- /dev/null +++ b/exoscale-zone/README.md @@ -0,0 +1,93 @@ +--- +display_name: exoscale-zone +description: A parameter with human zone names and icons +icon: ../.icons/exoscale.svg +maintainer_github: WhizUs +verified: false +tags: [helper, parameter, zones, regions, exoscale] +--- + +# exoscale-zone + +A parameter with all Exoscale zones. This allows developers to select +the zone closest to them. + +Customize the preselected parameter value: + +```hcl +module "exoscale-zone" { + source = "https://registry.coder.com/modules/exoscale-zone" + default = "ch-dk-2" +} + + +data "exoscale_compute_template" "my_template" { + zone = module.exoscale-zone.value + name = "Linux Ubuntu 22.04 LTS 64-bit" +} + +resource "exoscale_compute_instance" "instance" { + zone = module.exoscale-zone.value + .... +} +``` + +![Exoscale Zones](../.images/exoscale-zones.png) + +## Examples + +### Customize zones + +Change the display name and icon for a zone using the corresponding maps: + +```hcl +module "exoscale-zone" { + source = "https://registry.coder.com/modules/exoscale-zone" + default = "at-vie-1" + custom_names = { + "at-vie-1": "Home Vienna" + } + custom_icons = { + "at-vie-1": "/emojis/1f3e0.png" + } +} + +data "exoscale_compute_template" "my_template" { + zone = module.exoscale-zone.value + name = "Linux Ubuntu 22.04 LTS 64-bit" +} + +resource "exoscale_compute_instance" "instance" { + zone = module.exoscale-zone.value + .... +} +``` + +![Exoscale Custom](../.images/exoscale-custom.png) + +### Exclude regions + +Hide the Switzerland zones Geneva and Zurich + +```hcl +module "exoscale-zone" { + source = "https://registry.coder.com/modules/exoscale-zone" + exclude = [ "ch-gva-2", "ch-dk-2" ] +} + +data "exoscale_compute_template" "my_template" { + zone = module.exoscale-zone.value + name = "Linux Ubuntu 22.04 LTS 64-bit" +} + +resource "exoscale_compute_instance" "instance" { + zone = module.exoscale-zone.value + .... +} +``` + +![Exoscale Exclude](../.images/exoscale-exclude.png) + +## Related templates + +An exoscale sample template will be delivered soon. diff --git a/exoscale-zone/main.test.ts b/exoscale-zone/main.test.ts new file mode 100644 index 0000000..7c423e7 --- /dev/null +++ b/exoscale-zone/main.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("exoscale-zone", 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: "at-vie-1", + }); + expect(state.outputs.value.value).toBe("at-vie-1"); + }); +}); diff --git a/exoscale-zone/main.tf b/exoscale-zone/main.tf new file mode 100644 index 0000000..01f1467 --- /dev/null +++ b/exoscale-zone/main.tf @@ -0,0 +1,110 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12" + } + } +} + +variable "display_name" { + default = "Exoscale Region" + description = "The display name of the parameter." + type = string +} + +variable "description" { + default = "The region to deploy workspace infrastructure." + description = "The description of the parameter." + type = string +} + +variable "default" { + default = "" + description = "The default region to use if no region is specified." + type = string +} + +variable "mutable" { + default = false + description = "Whether the parameter can be changed after creation." + type = bool +} + +variable "custom_names" { + default = {} + description = "A map of custom display names for region IDs." + type = map(string) +} + +variable "custom_icons" { + default = {} + description = "A map of custom icons for region IDs." + type = map(string) +} + +variable "exclude" { + default = [] + description = "A list of region IDs to exclude." + type = list(string) +} + + +locals { + # This is a static list because the zones don't change _that_ + # frequently and including the `exoscale_zones` data source requires + # the provider, which requires a zone. + # https://www.exoscale.com/datacenters/ + zones = { + "de-fra-1" = { + name = "Frankfurt - Germany" + icon = "/emojis/1f1e9-1f1ea.png" + } + "at-vie-1" = { + name = "Vienna 1 - Austria" + icon = "/emojis/1f1e6-1f1f9.png" + } + "at-vie-2" = { + name = "Vienna 2 - Austria" + icon = "/emojis/1f1e6-1f1f9.png" + } + "ch-gva-2" = { + name = "Geneva - Switzerland" + icon = "/emojis/1f1e8-1f1ed.png" + } + "ch-dk-2" = { + name = "Zurich - Switzerland" + icon = "/emojis/1f1e8-1f1ed.png" + } + "bg-sof-1" = { + name = "Sofia - Bulgaria" + icon = "/emojis/1f1e7-1f1ec.png" + } + "de-muc-1" = { + name = "Munich - Germany" + icon = "/emojis/1f1e9-1f1ea.png" + } + } +} + +data "coder_parameter" "zone" { + name = "exoscale_zone" + display_name = var.display_name + description = var.description + default = var.default == "" ? null : var.default + mutable = var.mutable + dynamic "option" { + for_each = { for k, v in local.zones : k => v if !(contains(var.exclude, k)) } + content { + name = try(var.custom_names[option.key], option.value.name) + icon = try(var.custom_icons[option.key], option.value.icon) + value = option.key + } + } +} + +output "value" { + value = data.coder_parameter.zone.value +} \ No newline at end of file diff --git a/filebrowser/main.tf b/filebrowser/main.tf index f479488..27790a2 100644 --- a/filebrowser/main.tf +++ b/filebrowser/main.tf @@ -43,6 +43,15 @@ variable "folder" { default = "~" } +variable "share" { + type = string + default = "owner" + validation { + condition = var.share == "owner" || var.share == "authenticated" || var.share == "public" + error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'." + } +} + resource "coder_script" "filebrowser" { agent_id = var.agent_id display_name = "File Browser" @@ -64,5 +73,5 @@ resource "coder_app" "filebrowser" { url = "http://localhost:${var.port}" icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg" subdomain = true - share = "owner" + share = var.share } diff --git a/git-clone/main.tf b/git-clone/main.tf index c5a6d29..110232f 100644 --- a/git-clone/main.tf +++ b/git-clone/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = ">= 0.11" + version = ">= 0.12" } } } diff --git a/git-commit-signing/README.md b/git-commit-signing/README.md new file mode 100644 index 0000000..38a02c0 --- /dev/null +++ b/git-commit-signing/README.md @@ -0,0 +1,24 @@ +--- +display_name: Git commit signing +description: Configures Git to sign commits using your Coder SSH key +icon: ../.icons/git.svg +maintainer_github: phorcys420 +verified: false +tags: [helper, git] +--- + +# git-commit-signing + +This module downloads your SSH key from Coder and uses it to sign commits with Git. +It requires `curl` and `jq` to be installed inside your workspace. + +Please observe that using the SSH key that's part of your Coder account for commit signing, means that in the event of a breach of your Coder account, or a malicious admin, someone could perform commit signing pretending to be you. + +This module has a chance of conflicting with the user's dotfiles / the personalize module if one of those has configuration directives that overwrite this module's / each other's git configuration. + +```hcl +module "git-commit-signing" { + source = "https://registry.coder.com/modules/git-commit-signing" + agent_id = coder_agent.example.id +} +``` diff --git a/git-commit-signing/main.tf b/git-commit-signing/main.tf new file mode 100644 index 0000000..9be7bac --- /dev/null +++ b/git-commit-signing/main.tf @@ -0,0 +1,25 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +resource "coder_script" "git-commit-signing" { + display_name = "Git commit signing" + icon = "https://raw.githubusercontent.com/coder/modules/main/.icons/git.svg" + + script = file("${path.module}/run.sh") + run_on_start = true + + agent_id = var.agent_id +} diff --git a/git-commit-signing/run.sh b/git-commit-signing/run.sh new file mode 100755 index 0000000..57c5139 --- /dev/null +++ b/git-commit-signing/run.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env sh + +if ! command -v git > /dev/null; then + echo "git is not installed" + exit 1 +fi + +if ! command -v curl > /dev/null; then + echo "curl is not installed" + exit 1 +fi + +if ! command -v jq > /dev/null; then + echo "jq is not installed" + exit 1 +fi + +mkdir -p ~/.ssh/git-commit-signing + +echo "Downloading SSH key" + +ssh_key=$(curl --request GET \ + --url "${CODER_AGENT_URL}api/v2/workspaceagents/me/gitsshkey" \ + --header "Coder-Session-Token: ${CODER_AGENT_TOKEN}") + +jq --raw-output ".public_key" > ~/.ssh/git-commit-signing/coder.pub < ~/.ssh/git-commit-signing/coder <