diff --git a/.github/workflows/update-readme.yaml b/.github/workflows/update-readme.yaml
new file mode 100644
index 0000000..0d0e226
--- /dev/null
+++ b/.github/workflows/update-readme.yaml
@@ -0,0 +1,42 @@
+name: Update README on Tag
+
+on:
+ workflow_dispatch:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ update-readme:
+ permissions:
+ contents: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get the latest tag
+ id: get-latest-tag
+ run: echo "TAG=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> $GITHUB_OUTPUT
+
+ - name: Run update script
+ run: ./update-version.sh
+
+ - name: Create Pull Request
+ id: create-pr
+ uses: peter-evans/create-pull-request@v5
+ with:
+ commit-message: 'chore: bump version to ${{ env.TAG }} in README.md files'
+ title: 'chore: bump version to ${{ env.TAG }} in README.md files'
+ body: 'This is an auto-generated PR to update README.md files of all modules with the new tag ${{ env.TAG }}'
+ branch: 'update-readme-branch'
+ base: 'main'
+ env:
+ TAG: ${{ steps.get-latest-tag.outputs.TAG }}
+
+ - name: Auto-approve
+ uses: hmarr/auto-approve-action@v4
+ if: github.ref == 'refs/heads/update-readme-branch'
diff --git a/.icons/jfrog.svg b/.icons/jfrog.svg
index 1852abf..e137700 100644
--- a/.icons/jfrog.svg
+++ b/.icons/jfrog.svg
@@ -1 +1,3 @@
-
\ No newline at end of file
+
diff --git a/.icons/node.svg b/.icons/node.svg
new file mode 100644
index 0000000..e33a588
--- /dev/null
+++ b/.icons/node.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.images/hcp-vault-secrets-credentials.png b/.images/hcp-vault-secrets-credentials.png
new file mode 100644
index 0000000..e5e9cf2
Binary files /dev/null and b/.images/hcp-vault-secrets-credentials.png differ
diff --git a/.sample/README.md b/.sample/README.md
index c842c67..e8754f1 100644
--- a/.sample/README.md
+++ b/.sample/README.md
@@ -13,8 +13,8 @@ tags: [helper]
```tf
module "MODULE_NAME" {
- source = "registry.coder.com/modules/MODULE_NAME/coder"
- version = "1.0.0"
+ source = "registry.coder.com/modules/MODULE_NAME/coder"
+ version = "1.0.2"
}
```
@@ -28,9 +28,9 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```tf
module "MODULE_NAME" {
- source = "registry.coder.com/modules/MODULE_NAME/coder"
- version = "1.0.0"
- agent_id = coder_agent.example.id
+ source = "registry.coder.com/modules/MODULE_NAME/coder"
+ version = "1.0.2"
+ agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
]
@@ -45,11 +45,11 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
```tf
module "MODULE_NAME" {
- source = "registry.coder.com/modules/MODULE_NAME/coder"
- version = "1.0.0"
- agent_id = coder_agent.example.id
+ source = "registry.coder.com/modules/MODULE_NAME/coder"
+ version = "1.0.2"
+ agent_id = coder_agent.example.id
extensions = [ "dracula-theme.theme-dracula" ]
- settings = {
+ settings = {
"workbench.colorTheme" = "Dracula"
}
}
@@ -61,9 +61,9 @@ Run code-server in the background, don't fetch it from GitHub:
```tf
module "MODULE_NAME" {
- source = "registry.coder.com/modules/MODULE_NAME/coder"
- version = "1.0.0"
+ source = "registry.coder.com/modules/MODULE_NAME/coder"
+ version = "1.0.2"
agent_id = coder_agent.example.id
- offline = true
+ offline = true
}
```
diff --git a/.sample/main.tf b/.sample/main.tf
index 733f121..910320e 100644
--- a/.sample/main.tf
+++ b/.sample/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -50,6 +50,12 @@ variable "mutable" {
description = "Whether the parameter is mutable."
default = true
}
+
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
# Add other variables here
@@ -69,9 +75,10 @@ resource "coder_app" "MODULE_NAME" {
slug = "MODULE_NAME"
display_name = "MODULE_NAME"
url = "http://localhost:${var.port}"
- icon = loocal.icon_url
+ icon = local.icon_url
subdomain = false
share = "owner"
+ order = var.order
# Remove if the app does not have a healthcheck endpoint
healthcheck {
diff --git a/README.md b/README.md
index 9cf14e8..4b67594 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ e.g.
```tf
module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.main.id
}
```
diff --git a/aws-region/README.md b/aws-region/README.md
index 01a32fa..934714b 100644
--- a/aws-region/README.md
+++ b/aws-region/README.md
@@ -17,7 +17,7 @@ Customize the preselected parameter value:
```tf
module "aws-region" {
source = "registry.coder.com/modules/aws-region/coder"
- version = "1.0.0"
+ version = "1.0.10"
default = "us-east-1"
}
@@ -37,11 +37,13 @@ Change the display name and icon for a region using the corresponding maps:
```tf
module "aws-region" {
source = "registry.coder.com/modules/aws-region/coder"
- version = "1.0.0"
+ version = "1.0.10"
default = "ap-south-1"
+
custom_names = {
"ap-south-1" : "Awesome Mumbai!"
}
+
custom_icons = {
"ap-south-1" : "/emojis/1f33a.png"
}
@@ -61,7 +63,7 @@ Hide the Asia Pacific regions Seoul and Osaka:
```tf
module "aws-region" {
source = "registry.coder.com/modules/aws-region/coder"
- version = "1.0.0"
+ version = "1.0.10"
exclude = ["ap-northeast-2", "ap-northeast-3"]
}
diff --git a/aws-region/main.tf b/aws-region/main.tf
index b700422..7594320 100644
--- a/aws-region/main.tf
+++ b/aws-region/main.tf
@@ -56,6 +56,14 @@ locals {
# 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"
@@ -72,6 +80,10 @@ locals {
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"
@@ -80,18 +92,42 @@ locals {
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"
@@ -104,6 +140,14 @@ locals {
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"
@@ -145,4 +189,4 @@ data "coder_parameter" "region" {
output "value" {
value = data.coder_parameter.region.value
-}
+}
\ No newline at end of file
diff --git a/azure-region/README.md b/azure-region/README.md
index 1f20cee..d88a2e9 100644
--- a/azure-region/README.md
+++ b/azure-region/README.md
@@ -14,7 +14,7 @@ This module adds a parameter with all Azure regions, allowing developers to sele
```tf
module "azure_region" {
source = "registry.coder.com/modules/azure-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "eastus"
}
@@ -34,7 +34,7 @@ Change the display name and icon for a region using the corresponding maps:
```tf
module "azure-region" {
source = "registry.coder.com/modules/azure-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
custom_names = {
"australia" : "Go Australia!"
}
@@ -57,7 +57,7 @@ Hide all regions in Australia except australiacentral:
```tf
module "azure-region" {
source = "registry.coder.com/modules/azure-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
exclude = [
"australia",
"australiacentral2",
diff --git a/bun.lockb b/bun.lockb
index f8f4940..d3e2214 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/code-server/README.md b/code-server/README.md
index 52c031b..993b9ba 100644
--- a/code-server/README.md
+++ b/code-server/README.md
@@ -14,7 +14,7 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w
```tf
module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
}
```
@@ -28,7 +28,7 @@ module "code-server" {
```tf
module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
install_version = "4.8.3"
}
@@ -41,7 +41,7 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```tf
module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
@@ -56,9 +56,9 @@ Enter the `.` into the extensions array and code-server will autom
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
```tf
-module "settings" {
+module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
@@ -72,22 +72,36 @@ module "settings" {
Just run code-server in the background, don't fetch it from GitHub:
```tf
-module "settings" {
+module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
}
```
-### Offline Mode
+### Offline and Use Cached Modes
+
+By default the module looks for code-server at `/tmp/code-server` but this can be changed with `install_prefix`.
+
+Run an existing copy of code-server if found, otherwise download from GitHub:
+
+```tf
+module "code-server" {
+ source = "registry.coder.com/modules/code-server/coder"
+ version = "1.0.10"
+ agent_id = coder_agent.example.id
+ use_cached = true
+ extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
+}
+```
Just run code-server in the background, don't fetch it from GitHub:
```tf
-module "settings" {
+module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
offline = true
}
diff --git a/code-server/main.test.ts b/code-server/main.test.ts
index daf3ac1..1d6da5e 100644
--- a/code-server/main.test.ts
+++ b/code-server/main.test.ts
@@ -1,5 +1,9 @@
import { describe, expect, it } from "bun:test";
-import { runTerraformInit, testRequiredVariables } from "../test";
+import {
+ runTerraformApply,
+ runTerraformInit,
+ testRequiredVariables,
+} from "../test";
describe("code-server", async () => {
await runTerraformInit(import.meta.dir);
@@ -8,5 +12,27 @@ describe("code-server", async () => {
agent_id: "foo",
});
+ it("use_cached and offline can not be used together", () => {
+ const t = async () => {
+ await runTerraformApply(import.meta.dir, {
+ agent_id: "foo",
+ use_cached: "true",
+ offline: "true",
+ });
+ };
+ expect(t).toThrow("Offline and Use Cached can not be used together");
+ });
+
+ it("offline and extensions can not be used together", () => {
+ const t = async () => {
+ await runTerraformApply(import.meta.dir, {
+ agent_id: "foo",
+ offline: "true",
+ extensions: '["1", "2"]',
+ });
+ };
+ expect(t).toThrow("Offline mode does not allow extensions to be installed");
+ });
+
// More tests depend on shebang refactors
});
diff --git a/code-server/main.tf b/code-server/main.tf
index 86afdd0..30b92bc 100644
--- a/code-server/main.tf
+++ b/code-server/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -32,6 +32,12 @@ variable "display_name" {
default = "code-server"
}
+variable "slug" {
+ type = string
+ description = "The slug for the code-server application."
+ default = "code-server"
+}
+
variable "settings" {
type = map(string)
description = "A map of settings to apply to code-server."
@@ -71,6 +77,24 @@ variable "share" {
}
}
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
+variable "offline" {
+ type = bool
+ description = "Just run code-server in the background, don't fetch it from GitHub"
+ default = false
+}
+
+variable "use_cached" {
+ type = bool
+ description = "Uses cached copy code-server in the background, otherwise fetched it from GitHub"
+ default = false
+}
+
resource "coder_script" "code-server" {
agent_id = var.agent_id
display_name = "code-server"
@@ -78,23 +102,39 @@ resource "coder_script" "code-server" {
script = templatefile("${path.module}/run.sh", {
VERSION : var.install_version,
EXTENSIONS : join(",", var.extensions),
+ APP_NAME : var.display_name,
PORT : var.port,
LOG_PATH : var.log_path,
INSTALL_PREFIX : var.install_prefix,
// This is necessary otherwise the quotes are stripped!
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
+ OFFLINE : var.offline,
+ USE_CACHED : var.use_cached,
})
run_on_start = true
+
+ lifecycle {
+ precondition {
+ condition = !var.offline || length(var.extensions) == 0
+ error_message = "Offline mode does not allow extensions to be installed"
+ }
+
+ precondition {
+ condition = !var.offline || !var.use_cached
+ error_message = "Offline and Use Cached can not be used together"
+ }
+ }
}
resource "coder_app" "code-server" {
agent_id = var.agent_id
- slug = "code-server"
+ slug = var.slug
display_name = var.display_name
url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}"
icon = "/icon/code.svg"
subdomain = false
share = var.share
+ order = var.order
healthcheck {
url = "http://localhost:${var.port}/healthz"
diff --git a/code-server/run.sh b/code-server/run.sh
index 1212beb..2444324 100755
--- a/code-server/run.sh
+++ b/code-server/run.sh
@@ -4,6 +4,34 @@ EXTENSIONS=("${EXTENSIONS}")
BOLD='\033[0;1m'
CODE='\033[36;40;1m'
RESET='\033[0m'
+CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
+
+function run_code_server() {
+ echo "👷 Running code-server in the background..."
+ echo "Check logs at ${LOG_PATH}!"
+ $CODE_SERVER --auth none --port "${PORT}" --app-name "${APP_NAME}" > "${LOG_PATH}" 2>&1 &
+}
+
+# Check if the settings file exists...
+if [ ! -f ~/.local/share/code-server/User/settings.json ]; then
+ echo "⚙️ Creating settings file..."
+ mkdir -p ~/.local/share/code-server/User
+ echo "${SETTINGS}" > ~/.local/share/code-server/User/settings.json
+fi
+
+# Check if code-server is already installed for offline or cached mode
+if [ -f "$CODE_SERVER" ]; then
+ if [ "${OFFLINE}" = true ] || [ "${USE_CACHED}" = true ]; then
+ echo "🥳 Found a copy of code-server"
+ run_code_server
+ exit 0
+ fi
+fi
+# Offline mode always expects a copy of code-server to be present
+if [ "${OFFLINE}" = true ]; then
+ echo "Failed to find a copy of code-server"
+ exit 1
+fi
printf "$${BOLD}Installing code-server!\n"
@@ -22,8 +50,6 @@ if [ $? -ne 0 ]; then
fi
printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n"
-CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
-
# Install each extension...
IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}"
for extension in "$${EXTENSIONLIST[@]}"; do
@@ -38,13 +64,4 @@ for extension in "$${EXTENSIONLIST[@]}"; do
fi
done
-# Check if the settings file exists...
-if [ ! -f ~/.local/share/code-server/User/settings.json ]; then
- echo "⚙️ Creating settings file..."
- mkdir -p ~/.local/share/code-server/User
- echo "${SETTINGS}" > ~/.local/share/code-server/User/settings.json
-fi
-
-echo "👷 Running code-server in the background..."
-echo "Check logs at ${LOG_PATH}!"
-$CODE_SERVER --auth none --port ${PORT} > ${LOG_PATH} 2>&1 &
+run_code_server
diff --git a/coder-login/README.md b/coder-login/README.md
index d12a41d..d68e088 100644
--- a/coder-login/README.md
+++ b/coder-login/README.md
@@ -14,7 +14,7 @@ Automatically logs the user into Coder when creating their workspace.
```tf
module "coder-login" {
source = "registry.coder.com/modules/coder-login/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
}
```
diff --git a/dotfiles/README.md b/dotfiles/README.md
index b5d96c1..3e9bf3a 100644
--- a/dotfiles/README.md
+++ b/dotfiles/README.md
@@ -18,7 +18,7 @@ Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/
```tf
module "dotfiles" {
source = "registry.coder.com/modules/dotfiles/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
}
```
diff --git a/exoscale-instance-type/README.md b/exoscale-instance-type/README.md
index 28bf2e5..4b493f9 100644
--- a/exoscale-instance-type/README.md
+++ b/exoscale-instance-type/README.md
@@ -17,7 +17,7 @@ Customize the preselected parameter value:
```tf
module "exoscale-instance-type" {
source = "registry.coder.com/modules/exoscale-instance-type/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "standard.medium"
}
@@ -45,11 +45,13 @@ Change the display name a type using the corresponding maps:
```tf
module "exoscale-instance-type" {
source = "registry.coder.com/modules/exoscale-instance-type/coder"
- version = "1.0.0"
+ version = "1.0.2"
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
}
@@ -77,7 +79,7 @@ Show only gpu1 types
```tf
module "exoscale-instance-type" {
source = "registry.coder.com/modules/exoscale-instance-type/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "gpu.large"
type_category = ["gpu"]
exclude = [
diff --git a/exoscale-zone/README.md b/exoscale-zone/README.md
index b56a858..4297bed 100644
--- a/exoscale-zone/README.md
+++ b/exoscale-zone/README.md
@@ -17,7 +17,7 @@ Customize the preselected parameter value:
```tf
module "exoscale-zone" {
source = "registry.coder.com/modules/exoscale-zone/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "ch-dk-2"
}
@@ -44,11 +44,13 @@ Change the display name and icon for a zone using the corresponding maps:
```tf
module "exoscale-zone" {
source = "registry.coder.com/modules/exoscale-zone/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "at-vie-1"
+
custom_names = {
"at-vie-1" : "Home Vienna"
}
+
custom_icons = {
"at-vie-1" : "/emojis/1f3e0.png"
}
@@ -74,7 +76,7 @@ Hide the Switzerland zones Geneva and Zurich
```tf
module "exoscale-zone" {
source = "registry.coder.com/modules/exoscale-zone/coder"
- version = "1.0.0"
+ version = "1.0.2"
exclude = ["ch-gva-2", "ch-dk-2"]
}
diff --git a/filebrowser/README.md b/filebrowser/README.md
index 6dcefb5..2881376 100644
--- a/filebrowser/README.md
+++ b/filebrowser/README.md
@@ -14,7 +14,7 @@ A file browser for your workspace.
```tf
module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
}
```
@@ -28,7 +28,7 @@ module "filebrowser" {
```tf
module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
@@ -39,7 +39,7 @@ module "filebrowser" {
```tf
module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
database_path = ".config/filebrowser.db"
}
diff --git a/filebrowser/main.tf b/filebrowser/main.tf
index 27790a2..a07072b 100644
--- a/filebrowser/main.tf
+++ b/filebrowser/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -52,6 +52,12 @@ variable "share" {
}
}
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
resource "coder_script" "filebrowser" {
agent_id = var.agent_id
display_name = "File Browser"
@@ -74,4 +80,5 @@ resource "coder_app" "filebrowser" {
icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg"
subdomain = true
share = var.share
+ order = var.order
}
diff --git a/fly-region/README.md b/fly-region/README.md
index 5fec1a3..e5f446e 100644
--- a/fly-region/README.md
+++ b/fly-region/README.md
@@ -16,7 +16,7 @@ We can use the simplest format here, only adding a default selection as the `atl
```tf
module "fly-region" {
source = "registry.coder.com/modules/fly-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "atl"
}
```
@@ -32,7 +32,7 @@ The regions argument can be used to display only the desired regions in the Code
```tf
module "fly-region" {
source = "registry.coder.com/modules/fly-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "ams"
regions = ["ams", "arn", "atl"]
}
@@ -47,11 +47,13 @@ Set custom icons and names with their respective maps.
```tf
module "fly-region" {
source = "registry.coder.com/modules/fly-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = "ams"
+
custom_icons = {
"ams" = "/emojis/1f90e.png"
}
+
custom_names = {
"ams" = "We love the Netherlands!"
}
diff --git a/gcp-region/README.md b/gcp-region/README.md
index e860ed9..bb6063a 100644
--- a/gcp-region/README.md
+++ b/gcp-region/README.md
@@ -14,7 +14,7 @@ This module adds Google Cloud Platform regions to your Coder template.
```tf
module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
regions = ["us", "europe"]
}
@@ -34,7 +34,7 @@ Note: setting `gpu_only = true` and using a default region without GPU support,
```tf
module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
default = ["us-west1-a"]
regions = ["us-west1"]
gpu_only = false
@@ -50,7 +50,7 @@ resource "google_compute_instance" "example" {
```tf
module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
regions = ["europe-west"]
single_zone_per_region = false
}
@@ -60,12 +60,12 @@ resource "google_compute_instance" "example" {
}
```
-### Add a single zone from each region in US and Europe that laos has GPUs
+### Add a single zone from each region in US and Europe that has GPUs
```tf
module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder"
- version = "1.0.0"
+ version = "1.0.2"
regions = ["us", "europe"]
gpu_only = true
single_zone_per_region = true
diff --git a/git-clone/README.md b/git-clone/README.md
index a0e8e46..054e30c 100644
--- a/git-clone/README.md
+++ b/git-clone/README.md
@@ -14,30 +14,39 @@ This module allows you to automatically clone a repository by URL and skip if it
```tf
module "git-clone" {
source = "registry.coder.com/modules/git-clone/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
```
-To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-providers), add the provider by ID to your template:
+## Examples
+
+### Custom Path
```tf
-data "coder_git_auth" "github" {
- id = "github"
+module "git-clone" {
+ source = "registry.coder.com/modules/git-clone/coder"
+ version = "1.0.2"
+ agent_id = coder_agent.example.id
+ url = "https://github.com/coder/coder"
+ base_dir = "~/projects/coder"
}
```
-## Examples
+### Git Authentication
-### Custom Path
+To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-providers), add the provider by ID to your template:
```tf
module "git-clone" {
source = "registry.coder.com/modules/git-clone/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
- base_dir = "~/projects/coder"
+}
+
+data "coder_git_auth" "github" {
+ id = "github"
}
```
diff --git a/git-commit-signing/README.md b/git-commit-signing/README.md
index 05759d1..37633a2 100644
--- a/git-commit-signing/README.md
+++ b/git-commit-signing/README.md
@@ -19,7 +19,7 @@ This module has a chance of conflicting with the user's dotfiles / the personali
```tf
module "git-commit-signing" {
source = "registry.coder.com/modules/git-commit-signing/coder"
- version = "1.0.0"
+ version = "1.0.11"
agent_id = coder_agent.example.id
}
```
diff --git a/git-commit-signing/main.tf b/git-commit-signing/main.tf
index 9be7bac..7c8cd3b 100644
--- a/git-commit-signing/main.tf
+++ b/git-commit-signing/main.tf
@@ -16,7 +16,7 @@ variable "agent_id" {
resource "coder_script" "git-commit-signing" {
display_name = "Git commit signing"
- icon = "https://raw.githubusercontent.com/coder/modules/main/.icons/git.svg"
+ icon = "/icon/git.svg"
script = file("${path.module}/run.sh")
run_on_start = true
diff --git a/git-commit-signing/run.sh b/git-commit-signing/run.sh
index d757179..c0e0faa 100755
--- a/git-commit-signing/run.sh
+++ b/git-commit-signing/run.sh
@@ -21,7 +21,8 @@ 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}")
+ --header "Coder-Session-Token: ${CODER_AGENT_TOKEN}" \
+ --silent --show-error)
jq --raw-output ".public_key" > ~/.ssh/git-commit-signing/coder.pub << EOF
$ssh_key
@@ -31,8 +32,8 @@ jq --raw-output ".private_key" > ~/.ssh/git-commit-signing/coder << EOF
$ssh_key
EOF
-chmod -R 400 ~/.ssh/git-commit-signing/coder
-chmod -R 400 ~/.ssh/git-commit-signing/coder.pub
+chmod -R 600 ~/.ssh/git-commit-signing/coder
+chmod -R 644 ~/.ssh/git-commit-signing/coder.pub
echo "Configuring git to use the SSH key"
diff --git a/git-config/README.md b/git-config/README.md
index 71f09fa..9b76658 100644
--- a/git-config/README.md
+++ b/git-config/README.md
@@ -14,7 +14,7 @@ Runs a script that updates git credentials in the workspace to match the user's
```tf
module "git-config" {
source = "registry.coder.com/modules/git-config/coder"
- version = "1.0.0"
+ version = "1.0.3"
agent_id = coder_agent.example.id
}
```
@@ -28,7 +28,7 @@ TODO: Add screenshot
```tf
module "git-config" {
source = "registry.coder.com/modules/git-config/coder"
- version = "1.0.0"
+ version = "1.0.3"
agent_id = coder_agent.example.id
allow_email_change = true
}
@@ -41,11 +41,9 @@ TODO: Add screenshot
```tf
module "git-config" {
source = "registry.coder.com/modules/git-config/coder"
- version = "1.0.0"
+ version = "1.0.3"
agent_id = coder_agent.example.id
allow_username_change = false
allow_email_change = false
}
```
-
-TODO: Add screenshot
diff --git a/git-config/main.test.ts b/git-config/main.test.ts
deleted file mode 100644
index 6fbdbc5..0000000
--- a/git-config/main.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { describe, expect, it } from "bun:test";
-import {
- executeScriptInContainer,
- runTerraformApply,
- runTerraformInit,
- testRequiredVariables,
-} from "../test";
-
-describe("git-config", async () => {
- await runTerraformInit(import.meta.dir);
-
- testRequiredVariables(import.meta.dir, {
- agent_id: "foo",
- });
-
- it("fails without git", async () => {
- const state = await runTerraformApply(import.meta.dir, {
- agent_id: "foo",
- });
- const output = await executeScriptInContainer(state, "alpine");
- expect(output.exitCode).toBe(1);
- expect(output.stdout).toEqual([
- "\u001B[0;1mChecking git-config!",
- "Git is not installed!",
- ]);
- });
-
- it("runs with git", async () => {
- const state = await runTerraformApply(import.meta.dir, {
- agent_id: "foo",
- });
- const output = await executeScriptInContainer(state, "alpine/git");
- expect(output.exitCode).toBe(0);
- expect(output.stdout).toEqual([
- "\u001B[0;1mChecking git-config!",
- "git-config: No user.email found, setting to ",
- "git-config: No user.name found, setting to default",
- "",
- "\u001B[0;1mgit-config: using email: ",
- "\u001B[0;1mgit-config: using username: default",
- ]);
- });
-});
diff --git a/git-config/main.tf b/git-config/main.tf
index 55d9cca..d92a0b7 100644
--- a/git-config/main.tf
+++ b/git-config/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.13"
}
}
}
@@ -34,7 +34,7 @@ data "coder_parameter" "user_email" {
name = "user_email"
type = "string"
default = ""
- description = "Git user.email to be used for commits. Leave empty to default to Coder username."
+ description = "Git user.email to be used for commits. Leave empty to default to Coder user's email."
display_name = "Git config user.email"
mutable = true
}
@@ -44,18 +44,31 @@ data "coder_parameter" "username" {
name = "username"
type = "string"
default = ""
- description = "Git user.name to be used for commits. Leave empty to default to Coder username."
- display_name = "Git config user.name"
+ description = "Git user.name to be used for commits. Leave empty to default to Coder user's Full Name."
+ display_name = "Full Name for Git config"
mutable = true
}
-resource "coder_script" "git_config" {
+resource "coder_env" "git_author_name" {
agent_id = var.agent_id
- script = templatefile("${path.module}/run.sh", {
- GIT_USERNAME = try(data.coder_parameter.username[0].value, "") == "" ? data.coder_workspace.me.owner : try(data.coder_parameter.username[0].value, "")
- GIT_EMAIL = try(data.coder_parameter.user_email[0].value, "") == "" ? data.coder_workspace.me.owner_email : try(data.coder_parameter.user_email[0].value, "")
- })
- display_name = "Git Config"
- icon = "/icon/git.svg"
- run_on_start = true
+ name = "GIT_AUTHOR_NAME"
+ value = coalesce(try(data.coder_parameter.username[0].value, ""), data.coder_workspace.me.owner_name, data.coder_workspace.me.owner)
+}
+
+resource "coder_env" "git_commmiter_name" {
+ agent_id = var.agent_id
+ name = "GIT_COMMITTER_NAME"
+ value = coalesce(try(data.coder_parameter.username[0].value, ""), data.coder_workspace.me.owner_name, data.coder_workspace.me.owner)
+}
+
+resource "coder_env" "git_author_email" {
+ agent_id = var.agent_id
+ name = "GIT_AUTHOR_EMAIL"
+ value = coalesce(try(data.coder_parameter.user_email[0].value, ""), data.coder_workspace.me.owner_email)
+}
+
+resource "coder_env" "git_commmiter_email" {
+ agent_id = var.agent_id
+ name = "GIT_COMMITTER_EMAIL"
+ value = coalesce(try(data.coder_parameter.user_email[0].value, ""), data.coder_workspace.me.owner_email)
}
diff --git a/git-config/run.sh b/git-config/run.sh
deleted file mode 100644
index 36dc768..0000000
--- a/git-config/run.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env sh
-
-BOLD='\033[0;1m'
-printf "$${BOLD}Checking git-config!\n"
-
-# Check if git is installed
-command -v git > /dev/null 2>&1 || {
- echo "Git is not installed!"
- exit 1
-}
-
-# Set git username and email if missing
-if [ -z $(git config --get user.email) ]; then
- printf "git-config: No user.email found, setting to ${GIT_EMAIL}\n"
- git config --global user.email "${GIT_EMAIL}"
-fi
-
-if [ -z $(git config --get user.name) ]; then
- printf "git-config: No user.name found, setting to ${GIT_USERNAME}\n"
- git config --global user.name "${GIT_USERNAME}"
-fi
-
-printf "\n$${BOLD}git-config: using email: $(git config --get user.email)\n"
-printf "$${BOLD}git-config: using username: $(git config --get user.name)\n\n"
diff --git a/hcp-vault-secrets/README.md b/hcp-vault-secrets/README.md
new file mode 100644
index 0000000..fc71230
--- /dev/null
+++ b/hcp-vault-secrets/README.md
@@ -0,0 +1,80 @@
+---
+display_name: "HCP Vault Secrets"
+description: "Fetch secrets from HCP Vault"
+icon: ../.icons/vault.svg
+maintainer_github: coder
+partner_github: hashicorp
+verified: true
+tags: [helper, integration, vault, hashicorp, hvs]
+---
+
+# HCP Vault Secrets
+
+This module lets you fetch all or selective secrets from a [HCP Vault Secrets](https://developer.hashicorp.com/hcp/docs/vault-secrets) app into your [Coder](https://coder.com) workspaces. It makes use of the [`hcp_vault_secrets_app`](https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/vault_secrets_app) data source from the [HCP provider](https://registry.terraform.io/providers/hashicorp/hcp/latest).
+
+```tf
+module "vault" {
+ source = "registry.coder.com/modules/hcp-vault-secrets/coder"
+ version = "1.0.7"
+ agent_id = coder_agent.example.id
+ app_name = "demo-app"
+ project_id = "aaa-bbb-ccc"
+}
+```
+
+## Configuration
+
+To configure the HCP Vault Secrets module, follow these steps,
+
+1. [Create secrets in HCP Vault Secrets](https://developer.hashicorp.com/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-create-secret)
+2. Create an HCP Service Principal from the HCP Vault Secrets app in the HCP console. This will give you the `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` that you need to authenticate with HCP Vault Secrets.
+ 
+3. Set `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` variables on the coder provisioner (recommended) or supply them as input to the module.
+4. Set the `project_id`. This is the ID of the project where the HCP Vault Secrets app is running.
+
+> See the [HCP Vault Secrets documentation](https://developer.hashicorp.com/hcp/docs/vault-secrets) for more information.
+
+## Fetch All Secrets
+
+To fetch all secrets from the HCP Vault Secrets app, skip the `secrets` input.
+
+```tf
+module "vault" {
+ source = "registry.coder.com/modules/hcp-vault-secrets/coder"
+ version = "1.0.7"
+ agent_id = coder_agent.example.id
+ app_name = "demo-app"
+ project_id = "aaa-bbb-ccc"
+}
+```
+
+## Fetch Selective Secrets
+
+To fetch selective secrets from the HCP Vault Secrets app, set the `secrets` input.
+
+```tf
+module "vault" {
+ source = "registry.coder.com/modules/hcp-vault-secrets/coder"
+ version = "1.0.7"
+ agent_id = coder_agent.example.id
+ app_name = "demo-app"
+ project_id = "aaa-bbb-ccc"
+ secrets = ["MY_SECRET_1", "MY_SECRET_2"]
+}
+```
+
+## Set Client ID and Client Secret as Inputs
+
+Set `client_id` and `client_secret` as module inputs.
+
+```tf
+module "vault" {
+ source = "registry.coder.com/modules/hcp-vault-secrets/coder"
+ version = "1.0.7"
+ agent_id = coder_agent.example.id
+ app_name = "demo-app"
+ project_id = "aaa-bbb-ccc"
+ client_id = "HCP_CLIENT_ID"
+ client_secret = "HCP_CLIENT_SECRET"
+}
+```
diff --git a/hcp-vault-secrets/main.tf b/hcp-vault-secrets/main.tf
new file mode 100644
index 0000000..9a5e94b
--- /dev/null
+++ b/hcp-vault-secrets/main.tf
@@ -0,0 +1,73 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = ">= 0.12.4"
+ }
+ hcp = {
+ source = "hashicorp/hcp"
+ version = ">= 0.82.0"
+ }
+ }
+}
+
+provider "hcp" {
+ client_id = var.client_id
+ client_secret = var.client_secret
+ project_id = var.project_id
+}
+
+provider "coder" {}
+
+variable "agent_id" {
+ type = string
+ description = "The ID of a Coder agent."
+}
+
+variable "project_id" {
+ type = string
+ description = "The ID of the HCP project."
+}
+
+variable "client_id" {
+ type = string
+ description = <<-EOF
+ The client ID for the HCP Vault Secrets service principal. (Optional if HCP_CLIENT_ID is set as an environment variable.)
+ EOF
+ default = null
+ sensitive = true
+}
+
+variable "client_secret" {
+ type = string
+ description = <<-EOF
+ The client secret for the HCP Vault Secrets service principal. (Optional if HCP_CLIENT_SECRET is set as an environment variable.)
+ EOF
+ default = null
+ sensitive = true
+}
+
+variable "app_name" {
+ type = string
+ description = "The name of the secrets app in HCP Vault Secrets"
+}
+
+variable "secrets" {
+ type = list(string)
+ description = "The names of the secrets to retrieve from HCP Vault Secrets"
+ default = null
+}
+
+data "hcp_vault_secrets_app" "secrets" {
+ app_name = var.app_name
+}
+
+resource "coder_env" "hvs_secrets" {
+ # https://support.hashicorp.com/hc/en-us/articles/4538432032787-Variable-has-a-sensitive-value-and-cannot-be-used-as-for-each-arguments
+ for_each = var.secrets != null ? toset(var.secrets) : nonsensitive(toset(keys(data.hcp_vault_secrets_app.secrets.secrets)))
+ agent_id = var.agent_id
+ name = each.key
+ value = data.hcp_vault_secrets_app.secrets.secrets[each.key]
+}
\ No newline at end of file
diff --git a/jetbrains-gateway/README.md b/jetbrains-gateway/README.md
index 6e8031a..629afb2 100644
--- a/jetbrains-gateway/README.md
+++ b/jetbrains-gateway/README.md
@@ -14,11 +14,12 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
- version = "1.0.0"
+ version = "1.0.11"
agent_id = coder_agent.example.id
+ agent_name = "example"
folder = "/home/coder/example"
- jetbrains_ides = ["GO", "WS", "IU", "PY", "PS", "CL", "RM"]
- default = "PY"
+ jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
+ default = "GO"
}
```
@@ -31,8 +32,9 @@ module "jetbrains_gateway" {
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
- version = "1.0.0"
+ version = "1.0.11"
agent_id = coder_agent.example.id
+ agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
@@ -50,3 +52,4 @@ This module and JetBrains Gateway support the following JetBrains IDEs:
- PhpStorm (`PS`)
- CLion (`CL`)
- RubyMine (`RM`)
+- Rider (`RD`)
diff --git a/jetbrains-gateway/main.test.ts b/jetbrains-gateway/main.test.ts
index bc0ef2f..b327e41 100644
--- a/jetbrains-gateway/main.test.ts
+++ b/jetbrains-gateway/main.test.ts
@@ -11,18 +11,16 @@ describe("jetbrains-gateway", async () => {
await testRequiredVariables(import.meta.dir, {
agent_id: "foo",
agent_name: "foo",
- folder: "/baz/",
+ folder: "/home/foo",
});
it("default to first ide", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
agent_name: "foo",
- folder: "/baz/",
+ folder: "/home/foo",
jetbrains_ides: '["IU", "GO", "PY"]',
});
- expect(state.outputs.jetbrains_ides.value).toBe(
- '["IU","232.10203.10","https://download.jetbrains.com/idea/ideaIU-2023.2.4.tar.gz"]',
- );
+ expect(state.outputs.identifier.value).toBe("IU");
});
});
diff --git a/jetbrains-gateway/main.tf b/jetbrains-gateway/main.tf
index c108f61..7c57bee 100644
--- a/jetbrains-gateway/main.tf
+++ b/jetbrains-gateway/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.11"
+ version = ">= 0.17"
}
}
}
@@ -22,6 +22,10 @@ variable "agent_name" {
variable "folder" {
type = string
description = "The directory to open in the IDE. e.g. /home/coder/project"
+ validation {
+ condition = can(regex("^(?:/[^/]+)+$", var.folder))
+ error_message = "The folder must be a full path and must not start with a ~."
+ }
}
variable "default" {
@@ -30,17 +34,79 @@ variable "default" {
description = "Default IDE"
}
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
+variable "coder_parameter_order" {
+ type = number
+ description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
+ default = null
+}
+
+variable "jetbrains_ide_versions" {
+ type = map(object({
+ build_number = string
+ version = string
+ }))
+ description = "The set of versions for each jetbrains IDE"
+ default = {
+ "IU" = {
+ build_number = "241.14494.240"
+ version = "2024.1"
+ }
+ "PS" = {
+ build_number = "241.14494.237"
+ version = "2024.1"
+ }
+ "WS" = {
+ build_number = "241.14494.235"
+ version = "2024.1"
+ }
+ "PY" = {
+ build_number = "241.14494.241"
+ version = "2024.1"
+ }
+ "CL" = {
+ build_number = "241.14494.288"
+ version = "2024.1"
+ }
+ "GO" = {
+ build_number = "241.14494.238"
+ version = "2024.1"
+ }
+ "RM" = {
+ build_number = "241.14494.234"
+ version = "2024.1"
+ }
+ "RD" = {
+ build_number = "241.14494.307"
+ version = "2024.1"
+ }
+ }
+ validation {
+ condition = (
+ alltrue([
+ for code in keys(var.jetbrains_ide_versions) : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"], code)
+ ])
+ )
+ error_message = "The jetbrains_ide_versions must contain a map of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"])}."
+ }
+}
+
variable "jetbrains_ides" {
type = list(string)
description = "The list of IDE product codes."
- default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM"]
+ default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"]
validation {
condition = (
alltrue([
- for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM"], code)
+ for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"], code)
])
)
- error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are IU, PS, WS, PY, CL, GO, RM."
+ error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"])}."
}
# check if the list is empty
validation {
@@ -57,58 +123,79 @@ variable "jetbrains_ides" {
locals {
jetbrains_ides = {
"GO" = {
- icon = "/icon/goland.svg",
- name = "GoLand",
- value = jsonencode(["GO", "232.10203.20", "https://download.jetbrains.com/go/goland-2023.2.4.tar.gz"])
+ icon = "/icon/goland.svg",
+ name = "GoLand",
+ identifier = "GO",
+ build_number = var.jetbrains_ide_versions["GO"].build_number,
+ download_link = "https://download.jetbrains.com/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz"
},
"WS" = {
- icon = "/icon/webstorm.svg",
- name = "WebStorm",
- value = jsonencode(["WS", "232.10203.14", "https://download.jetbrains.com/webstorm/WebStorm-2023.2.4.tar.gz"])
+ icon = "/icon/webstorm.svg",
+ name = "WebStorm",
+ identifier = "WS",
+ build_number = var.jetbrains_ide_versions["WS"].build_number,
+ download_link = "https://download.jetbrains.com/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz"
},
"IU" = {
- icon = "/icon/intellij.svg",
- name = "IntelliJ IDEA Ultimate",
- value = jsonencode(["IU", "232.10203.10", "https://download.jetbrains.com/idea/ideaIU-2023.2.4.tar.gz"])
+ icon = "/icon/intellij.svg",
+ name = "IntelliJ IDEA Ultimate",
+ identifier = "IU",
+ build_number = var.jetbrains_ide_versions["IU"].build_number,
+ download_link = "https://download.jetbrains.com/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz"
},
"PY" = {
- icon = "/icon/pycharm.svg",
- name = "PyCharm Professional",
- value = jsonencode(["PY", "232.10203.26", "https://download.jetbrains.com/python/pycharm-professional-2023.2.4.tar.gz"])
+ icon = "/icon/pycharm.svg",
+ name = "PyCharm Professional",
+ identifier = "PY",
+ build_number = var.jetbrains_ide_versions["PY"].build_number,
+ download_link = "https://download.jetbrains.com/python/pycharm-professional-${var.jetbrains_ide_versions["PY"].version}.tar.gz"
},
"CL" = {
- icon = "/icon/clion.svg",
- name = "CLion",
- value = jsonencode(["CL", "232.9921.42", "https://download.jetbrains.com/cpp/CLion-2023.2.2.tar.gz"])
+ icon = "/icon/clion.svg",
+ name = "CLion",
+ identifier = "CL",
+ build_number = var.jetbrains_ide_versions["CL"].build_number,
+ download_link = "https://download.jetbrains.com/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz"
},
"PS" = {
- icon = "/icon/phpstorm.svg",
- name = "PhpStorm",
- value = jsonencode(["PS", "232.10072.32", "https://download.jetbrains.com/webide/PhpStorm-2023.2.3.tar.gz"])
+ icon = "/icon/phpstorm.svg",
+ name = "PhpStorm",
+ identifier = "PS",
+ build_number = var.jetbrains_ide_versions["PS"].build_number,
+ download_link = "https://download.jetbrains.com/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz"
},
"RM" = {
- icon = "/icon/rubymine.svg",
- name = "RubyMine",
- value = jsonencode(["RM", "232.10203.15", "https://download.jetbrains.com/ruby/RubyMine-2023.2.4.tar.gz"])
+ icon = "/icon/rubymine.svg",
+ name = "RubyMine",
+ identifier = "RM",
+ build_number = var.jetbrains_ide_versions["RM"].build_number,
+ download_link = "https://download.jetbrains.com/ruby/RubyMine-${var.jetbrains_ide_versions["RM"].version}.tar.gz"
+ }
+ "RD" = {
+ icon = "/icon/rider.svg",
+ name = "Rider",
+ identifier = "RD",
+ build_number = var.jetbrains_ide_versions["RD"].build_number,
+ download_link = "https://download.jetbrains.com/rider/JetBrains.Rider-${var.jetbrains_ide_versions["RD"].version}.tar.gz"
}
}
}
data "coder_parameter" "jetbrains_ide" {
- type = "list(string)"
+ type = "string"
name = "jetbrains_ide"
display_name = "JetBrains IDE"
icon = "/icon/gateway.svg"
mutable = true
- # check if default is in the jet_brains_ides list and if it is not empty or null otherwise set it to null
- default = var.default != null && var.default != "" && contains(var.jetbrains_ides, var.default) ? local.jetbrains_ides[var.default].value : local.jetbrains_ides[var.jetbrains_ides[0]].value
+ default = var.default == "" ? var.jetbrains_ides[0] : var.default
+ order = var.coder_parameter_order
dynamic "option" {
- for_each = { for key, value in local.jetbrains_ides : key => value if contains(var.jetbrains_ides, key) }
+ for_each = var.jetbrains_ides
content {
- icon = option.value.icon
- name = option.value.name
- value = option.value.value
+ icon = lookup(local.jetbrains_ides, option.value).icon
+ name = lookup(local.jetbrains_ides, option.value).name
+ value = lookup(local.jetbrains_ides, option.value).identifier
}
}
}
@@ -117,10 +204,11 @@ data "coder_workspace" "me" {}
resource "coder_app" "gateway" {
agent_id = var.agent_id
- display_name = data.coder_parameter.jetbrains_ide.option[index(data.coder_parameter.jetbrains_ide.option.*.value, data.coder_parameter.jetbrains_ide.value)].name
slug = "gateway"
- icon = data.coder_parameter.jetbrains_ide.option[index(data.coder_parameter.jetbrains_ide.option.*.value, data.coder_parameter.jetbrains_ide.value)].icon
+ display_name = try(lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).name, "JetBrains IDE")
+ icon = try(lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).icon, "/icon/gateway.svg")
external = true
+ order = var.order
url = join("", [
"jetbrains-gateway://connect#type=coder&workspace=",
data.coder_workspace.me.name,
@@ -133,14 +221,38 @@ resource "coder_app" "gateway" {
"&token=",
"$SESSION_TOKEN",
"&ide_product_code=",
- jsondecode(data.coder_parameter.jetbrains_ide.value)[0],
+ local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].identifier,
"&ide_build_number=",
- jsondecode(data.coder_parameter.jetbrains_ide.value)[1],
+ local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].build_number,
"&ide_download_link=",
- jsondecode(data.coder_parameter.jetbrains_ide.value)[2],
+ local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link
])
}
-output "jetbrains_ides" {
+output "identifier" {
value = data.coder_parameter.jetbrains_ide.value
}
+
+output "name" {
+ value = coder_app.gateway.display_name
+}
+
+output "icon" {
+ value = coder_app.gateway.icon
+}
+
+output "download_link" {
+ value = lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).download_link
+}
+
+output "build_number" {
+ value = lookup(local.jetbrains_ides, data.coder_parameter.jetbrains_ide.value).build_number
+}
+
+output "version" {
+ value = var.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].version
+}
+
+output "url" {
+ value = coder_app.gateway.url
+}
diff --git a/jfrog-oauth/README.md b/jfrog-oauth/README.md
index 84dbbbd..60b3bb7 100644
--- a/jfrog-oauth/README.md
+++ b/jfrog-oauth/README.md
@@ -17,10 +17,11 @@ Install the JF CLI and authenticate package managers with Artifactory using OAut
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-oauth/coder"
- version = "1.0.0"
+ version = "1.0.5"
agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io"
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
+
package_managers = {
"npm" : "npm",
"go" : "go",
@@ -43,10 +44,11 @@ Configure the Python pip package manager to fetch packages from Artifactory whil
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-oauth/coder"
- version = "1.0.0"
+ version = "1.0.5"
agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io"
username_field = "email"
+
package_managers = {
"pypi" : "pypi"
}
@@ -70,7 +72,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-oauth/coder"
- version = "1.0.0"
+ version = "1.0.5"
agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io"
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
diff --git a/jfrog-oauth/main.tf b/jfrog-oauth/main.tf
index 9f05e56..70fd0e8 100644
--- a/jfrog-oauth/main.tf
+++ b/jfrog-oauth/main.tf
@@ -19,6 +19,12 @@ variable "jfrog_url" {
}
}
+variable "jfrog_server_id" {
+ type = string
+ description = "The server ID of the JFrog instance for JFrog CLI configuration"
+ default = "0"
+}
+
variable "username_field" {
type = string
description = "The field to use for the artifactory username. i.e. Coder username or email."
@@ -79,6 +85,7 @@ resource "coder_script" "jfrog" {
script = templatefile("${path.module}/run.sh", {
JFROG_URL : var.jfrog_url,
JFROG_HOST : local.jfrog_host,
+ JFROG_SERVER_ID : var.jfrog_server_id,
ARTIFACTORY_USERNAME : local.username,
ARTIFACTORY_EMAIL : data.coder_workspace.me.owner_email,
ARTIFACTORY_ACCESS_TOKEN : data.coder_external_auth.jfrog.access_token,
diff --git a/jfrog-oauth/run.sh b/jfrog-oauth/run.sh
index c0fa589..2a7b0d1 100644
--- a/jfrog-oauth/run.sh
+++ b/jfrog-oauth/run.sh
@@ -15,9 +15,9 @@ fi
# flows.
export CI=true
# Authenticate JFrog CLI with Artifactory.
-echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite 0
+echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite "${JFROG_SERVER_ID}"
# Set the configured server as the default.
-jf c use 0
+jf c use "${JFROG_SERVER_ID}"
# Configure npm to use the Artifactory "npm" repository.
if [ -z "${REPOSITORY_NPM}" ]; then
diff --git a/jfrog-token/README.md b/jfrog-token/README.md
index 032e313..0bd4781 100644
--- a/jfrog-token/README.md
+++ b/jfrog-token/README.md
@@ -15,7 +15,7 @@ Install the JF CLI and authenticate package managers with Artifactory using Arti
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-token/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token
@@ -41,7 +41,7 @@ For detailed instructions, please see this [guide](https://coder.com/docs/v2/lat
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-token/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
jfrog_url = "https://YYYY.jfrog.io"
artifactory_access_token = var.artifactory_access_token # An admin access token
@@ -74,7 +74,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-token/coder"
- version = "1.0.0"
+ version = "1.0.10"
agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token
@@ -87,6 +87,26 @@ module "jfrog" {
}
```
+### Add a custom token description
+
+```tf
+data "coder_workspace" "me" {}
+
+module "jfrog" {
+ source = "registry.coder.com/modules/jfrog-token/coder"
+ version = "1.0.10"
+ agent_id = coder_agent.example.id
+ jfrog_url = "https://XXXX.jfrog.io"
+ artifactory_access_token = var.artifactory_access_token
+ token_description = "Token for Coder workspace: ${data.coder_workspace.me.owner}/${data.coder_workspace.me.name}"
+ package_managers = {
+ "npm" : "npm",
+ "go" : "go",
+ "pypi" : "pypi"
+ }
+}
+```
+
### Using the access token in other terraform resources
JFrog Access token is also available as a terraform output. You can use it in other terraform resources. For example, you can use it to configure an [Artifactory docker registry](https://jfrog.com/help/r/jfrog-artifactory-documentation/docker-registry) with the [docker terraform provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs).
@@ -102,3 +122,5 @@ provider "docker" {
}
}
```
+
+> Here `REPO_KEY` is the name of docker repository in Artifactory.
diff --git a/jfrog-token/main.tf b/jfrog-token/main.tf
index 7e63649..d1f99f1 100644
--- a/jfrog-token/main.tf
+++ b/jfrog-token/main.tf
@@ -23,11 +23,23 @@ variable "jfrog_url" {
}
}
+variable "jfrog_server_id" {
+ type = string
+ description = "The server ID of the JFrog instance for JFrog CLI configuration"
+ default = "0"
+}
+
variable "artifactory_access_token" {
type = string
description = "The admin-level access token to use for JFrog."
}
+variable "token_description" {
+ type = string
+ description = "Free text token description. Useful for filtering and managing tokens."
+ default = "Token for Coder workspace"
+}
+
variable "check_license" {
type = bool
description = "Toggle for pre-flight checking of Artifactory license. Default to `true`."
@@ -101,6 +113,7 @@ resource "artifactory_scoped_token" "me" {
scopes = ["applied-permissions/user"]
refreshable = var.refreshable
expires_in = var.expires_in
+ description = var.token_description
}
data "coder_workspace" "me" {}
@@ -112,6 +125,7 @@ resource "coder_script" "jfrog" {
script = templatefile("${path.module}/run.sh", {
JFROG_URL : var.jfrog_url,
JFROG_HOST : local.jfrog_host,
+ JFROG_SERVER_ID : var.jfrog_server_id,
ARTIFACTORY_USERNAME : local.username,
ARTIFACTORY_EMAIL : data.coder_workspace.me.owner_email,
ARTIFACTORY_ACCESS_TOKEN : artifactory_scoped_token.me.access_token,
diff --git a/jfrog-token/run.sh b/jfrog-token/run.sh
index 629a65d..52b3513 100644
--- a/jfrog-token/run.sh
+++ b/jfrog-token/run.sh
@@ -15,9 +15,9 @@ fi
# flows.
export CI=true
# Authenticate JFrog CLI with Artifactory.
-echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite 0
+echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite "${JFROG_SERVER_ID}"
# Set the configured server as the default.
-jf c use 0
+jf c use "${JFROG_SERVER_ID}"
# Configure npm to use the Artifactory "npm" repository.
if [ -z "${REPOSITORY_NPM}" ]; then
diff --git a/jupyter-notebook/README.md b/jupyter-notebook/README.md
index e25722a..6338f11 100644
--- a/jupyter-notebook/README.md
+++ b/jupyter-notebook/README.md
@@ -16,7 +16,7 @@ A module that adds Jupyter Notebook in your Coder template.
```tf
module "jupyter-notebook" {
source = "registry.coder.com/modules/jupyter-notebook/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
}
```
diff --git a/jupyter-notebook/main.tf b/jupyter-notebook/main.tf
index 236dee8..a588ef1 100644
--- a/jupyter-notebook/main.tf
+++ b/jupyter-notebook/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -36,6 +36,12 @@ variable "share" {
}
}
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
resource "coder_script" "jupyter-notebook" {
agent_id = var.agent_id
display_name = "jupyter-notebook"
@@ -55,4 +61,5 @@ resource "coder_app" "jupyter-notebook" {
icon = "/icon/jupyter.svg"
subdomain = true
share = var.share
+ order = var.order
}
diff --git a/jupyterlab/README.md b/jupyterlab/README.md
index 4ab7782..3d04cf3 100644
--- a/jupyterlab/README.md
+++ b/jupyterlab/README.md
@@ -16,7 +16,7 @@ A module that adds JupyterLab in your Coder template.
```tf
module "jupyterlab" {
source = "registry.coder.com/modules/jupyterlab/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
}
```
diff --git a/jupyterlab/main.tf b/jupyterlab/main.tf
index 3e5e14b..d7928f0 100644
--- a/jupyterlab/main.tf
+++ b/jupyterlab/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -36,6 +36,12 @@ variable "share" {
}
}
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
resource "coder_script" "jupyterlab" {
agent_id = var.agent_id
display_name = "jupyterlab"
@@ -55,4 +61,5 @@ resource "coder_app" "jupyterlab" {
icon = "/icon/jupyter.svg"
subdomain = true
share = var.share
+ order = var.order
}
diff --git a/lint.ts b/lint.ts
index 7b859a0..db1ee9a 100644
--- a/lint.ts
+++ b/lint.ts
@@ -92,6 +92,7 @@ for (const dir of dirs) {
let h1 = false;
let code = false;
let paragraph = false;
+ let version = true;
for (const token of tokens) {
if (token.type === "heading" && token.depth === 1) {
@@ -107,6 +108,10 @@ for (const dir of dirs) {
}
if (token.type === "code") {
code = true;
+ if (token.lang === "tf" && !token.text.includes("version")) {
+ version = false;
+ error(dir.name, "missing version in tf code block");
+ }
continue;
}
}
diff --git a/nodejs/README.md b/nodejs/README.md
new file mode 100644
index 0000000..25714aa
--- /dev/null
+++ b/nodejs/README.md
@@ -0,0 +1,58 @@
+---
+display_name: nodejs
+description: Install Node.js via nvm
+icon: ../.icons/node.svg
+maintainer_github: TheZoker
+verified: false
+tags: [helper]
+---
+
+# nodejs
+
+Automatically installs [Node.js](https://github.com/nodejs/node) via [nvm](https://github.com/nvm-sh/nvm). It can also install multiple versions of node and set a default version. If no options are specified, the latest version is installed.
+
+```tf
+module "nodejs" {
+ source = "registry.coder.com/modules/nodejs/coder"
+ version = "1.0.10"
+ agent_id = coder_agent.example.id
+}
+```
+
+### Install multiple versions
+
+This installs multiple versions of Node.js:
+
+```tf
+module "nodejs" {
+ source = "registry.coder.com/modules/nodejs/coder"
+ version = "1.0.10"
+ agent_id = coder_agent.example.id
+ node_versions = [
+ "18",
+ "20",
+ "node"
+ ]
+ default_node_version = "20"
+}
+```
+
+### Full example
+
+A example with all available options:
+
+```tf
+module "nodejs" {
+ source = "registry.coder.com/modules/nodejs/coder"
+ version = "1.0.10"
+ agent_id = coder_agent.example.id
+ nvm_version = "v0.39.7"
+ nvm_install_prefix = "/opt/nvm"
+ node_versions = [
+ "16",
+ "18",
+ "node"
+ ]
+ default_node_version = "16"
+}
+```
diff --git a/nodejs/main.test.ts b/nodejs/main.test.ts
new file mode 100644
index 0000000..07fc7a5
--- /dev/null
+++ b/nodejs/main.test.ts
@@ -0,0 +1,12 @@
+import { describe, expect, it } from "bun:test";
+import { runTerraformInit, testRequiredVariables } from "../test";
+
+describe("nodejs", async () => {
+ await runTerraformInit(import.meta.dir);
+
+ testRequiredVariables(import.meta.dir, {
+ agent_id: "foo",
+ });
+
+ // More tests depend on shebang refactors
+});
diff --git a/nodejs/main.tf b/nodejs/main.tf
new file mode 100644
index 0000000..9c9c5c7
--- /dev/null
+++ b/nodejs/main.tf
@@ -0,0 +1,52 @@
+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."
+}
+
+variable "nvm_version" {
+ type = string
+ description = "The version of nvm to install."
+ default = "master"
+}
+
+variable "nvm_install_prefix" {
+ type = string
+ description = "The prefix to install nvm to (relative to $HOME)."
+ default = ".nvm"
+}
+
+variable "node_versions" {
+ type = list(string)
+ description = "A list of Node.js versions to install."
+ default = ["node"]
+}
+
+variable "default_node_version" {
+ type = string
+ description = "The default Node.js version"
+ default = "node"
+}
+
+resource "coder_script" "nodejs" {
+ agent_id = var.agent_id
+ display_name = "Node.js:"
+ script = templatefile("${path.module}/run.sh", {
+ NVM_VERSION : var.nvm_version,
+ INSTALL_PREFIX : var.nvm_install_prefix,
+ NODE_VERSIONS : join(",", var.node_versions),
+ DEFAULT : var.default_node_version,
+ })
+ run_on_start = true
+ start_blocks_login = true
+}
diff --git a/nodejs/run.sh b/nodejs/run.sh
new file mode 100755
index 0000000..78e940a
--- /dev/null
+++ b/nodejs/run.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+NVM_VERSION='${NVM_VERSION}'
+NODE_VERSIONS='${NODE_VERSIONS}'
+INSTALL_PREFIX='${INSTALL_PREFIX}'
+DEFAULT='${DEFAULT}'
+BOLD='\033[0;1m'
+CODE='\033[36;40;1m'
+RESET='\033[0m'
+
+printf "$${BOLD}Installing nvm!$${RESET}\n"
+
+export NVM_DIR="$HOME/$${INSTALL_PREFIX}/nvm"
+mkdir -p "$NVM_DIR"
+
+script="$(curl -sS -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$${NVM_VERSION}/install.sh" 2>&1)"
+if [ $? -ne 0 ]; then
+ echo "Failed to download nvm installation script: $script"
+ exit 1
+fi
+
+output="$(bash <<< "$script" 2>&1)"
+if [ $? -ne 0 ]; then
+ echo "Failed to install nvm: $output"
+ exit 1
+fi
+
+printf "🥳 nvm has been installed\n\n"
+
+# Set up nvm for the rest of the script.
+[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
+
+# Install each node version...
+IFS=',' read -r -a VERSIONLIST <<< "$${NODE_VERSIONS}"
+for version in "$${VERSIONLIST[@]}"; do
+ if [ -z "$version" ]; then
+ continue
+ fi
+ printf "🛠️ Installing node version $${CODE}$version$${RESET}...\n"
+ output=$(nvm install "$version" 2>&1)
+ if [ $? -ne 0 ]; then
+ echo "Failed to install version: $version: $output"
+ exit 1
+ fi
+done
+
+# Set default if provided
+if [ -n "$${DEFAULT}" ]; then
+ printf "🛠️ Setting default node version $${CODE}$DEFAULT$${RESET}...\n"
+ output=$(nvm alias default $DEFAULT 2>&1)
+fi
diff --git a/package.json b/package.json
index 3ccd906..5a73d51 100644
--- a/package.json
+++ b/package.json
@@ -4,12 +4,13 @@
"test": "bun test",
"fmt": "bun x prettier -w **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt **/*.tf .sample/main.tf",
"fmt:ci": "bun x prettier --check **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt -check **/*.tf .sample/main.tf",
- "lint": "bun run lint.ts"
+ "lint": "bun run lint.ts && ./terraform_validate.sh",
+ "update-version": "./update-version.sh"
},
"devDependencies": {
"bun-types": "^1.0.18",
"gray-matter": "^4.0.3",
- "marked": "^11.1.0",
+ "marked": "^12.0.0",
"prettier-plugin-sh": "^0.13.1",
"prettier-plugin-terraform-formatter": "^1.2.1"
},
@@ -22,4 +23,4 @@
"prettier-plugin-terraform-formatter"
]
}
-}
+}
\ No newline at end of file
diff --git a/personalize/README.md b/personalize/README.md
index c36467f..24d19a9 100644
--- a/personalize/README.md
+++ b/personalize/README.md
@@ -14,7 +14,7 @@ Run a script on workspace start that allows developers to run custom commands to
```tf
module "personalize" {
source = "registry.coder.com/modules/personalize/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
}
```
diff --git a/slackme/README.md b/slackme/README.md
index a43033e..0858c3d 100644
--- a/slackme/README.md
+++ b/slackme/README.md
@@ -57,7 +57,7 @@ slackme npm run long-build
```tf
module "slackme" {
source = "registry.coder.com/modules/slackme/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
auth_provider_id = "slack"
}
@@ -73,7 +73,7 @@ slackme npm run long-build
```tf
module "slackme" {
source = "registry.coder.com/modules/slackme/coder"
- version = "1.0.0"
+ version = "1.0.2"
agent_id = coder_agent.example.id
auth_provider_id = "slack"
slack_message = < "$tmpfile" && mv "$tmpfile" "$file"
+ fi
+done
diff --git a/vault-github/README.md b/vault-github/README.md
index b6bfb36..ac73972 100644
--- a/vault-github/README.md
+++ b/vault-github/README.md
@@ -3,6 +3,7 @@ display_name: Hashicorp Vault Integration (GitHub)
description: Authenticates with Vault using GitHub
icon: ../.icons/vault.svg
maintainer_github: coder
+partner_github: hashicorp
verified: true
tags: [helper, integration, vault, github]
---
@@ -14,7 +15,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec
```tf
module "vault" {
source = "registry.coder.com/modules/vault-github/coder"
- version = "1.0.0"
+ version = "1.0.7"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
}
@@ -23,13 +24,13 @@ module "vault" {
Then you can use the Vault CLI in your workspaces to fetch secrets from Vault:
```shell
-vault kv get -mount=secret my-secret
+vault kv get -namespace=coder -mount=secrets coder
```
or using the Vault API:
```shell
-curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/secret/data/my-secret"
+curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder"
```

@@ -45,7 +46,7 @@ To configure the Vault module, you must set up a Vault GitHub auth method. See t
```tf
module "vault" {
source = "registry.coder.com/modules/vault-github/coder"
- version = "1.0.0"
+ version = "1.0.7"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
coder_github_auth_id = "my-github-auth-id"
@@ -57,7 +58,7 @@ module "vault" {
```tf
module "vault" {
source = "registry.coder.com/modules/vault-github/coder"
- version = "1.0.0"
+ version = "1.0.7"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
coder_github_auth_id = "my-github-auth-id"
@@ -70,7 +71,7 @@ module "vault" {
```tf
module "vault" {
source = "registry.coder.com/modules/vault-github/coder"
- version = "1.0.0"
+ version = "1.0.7"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_cli_version = "1.15.0"
diff --git a/vault-github/main.test.ts b/vault-github/main.test.ts
new file mode 100644
index 0000000..25934c8
--- /dev/null
+++ b/vault-github/main.test.ts
@@ -0,0 +1,11 @@
+import { describe } from "bun:test";
+import { runTerraformInit, testRequiredVariables } from "../test";
+
+describe("vault-github", async () => {
+ await runTerraformInit(import.meta.dir);
+
+ testRequiredVariables(import.meta.dir, {
+ agent_id: "foo",
+ vault_addr: "foo",
+ });
+});
diff --git a/vault-github/main.tf b/vault-github/main.tf
index f059b3a..286025a 100644
--- a/vault-github/main.tf
+++ b/vault-github/main.tf
@@ -49,7 +49,6 @@ resource "coder_script" "vault" {
display_name = "Vault (GitHub)"
icon = "/icon/vault.svg"
script = templatefile("${path.module}/run.sh", {
- VAULT_ADDR : var.vault_addr,
AUTH_PATH : var.vault_github_auth_path,
GITHUB_EXTERNAL_AUTH_ID : data.coder_external_auth.github.id,
INSTALL_VERSION : var.vault_cli_version,
diff --git a/vault-github/run.sh b/vault-github/run.sh
index 383ad9f..8ca96c0 100644
--- a/vault-github/run.sh
+++ b/vault-github/run.sh
@@ -1,8 +1,7 @@
-#!/usr/bin/env sh
+#!/usr/bin/env bash
# Convert all templated variables to shell variables
INSTALL_VERSION=${INSTALL_VERSION}
-VAULT_ADDR=${VAULT_ADDR}
GITHUB_EXTERNAL_AUTH_ID=${GITHUB_EXTERNAL_AUTH_ID}
AUTH_PATH=${AUTH_PATH}
@@ -21,7 +20,7 @@ fetch() {
fi
}
-unzip() {
+unzip_safe() {
if command -v unzip > /dev/null 2>&1; then
command unzip "$@"
elif command -v busybox > /dev/null 2>&1; then
@@ -32,57 +31,78 @@ unzip() {
fi
}
-# Fetch the latest version of Vault if INSTALL_VERSION is 'latest'
-if [ "$${INSTALL_VERSION}" = "latest" ]; then
- LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -oP 'vault/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1)
- printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
- if [ -z "$${LATEST_VERSION}" ]; then
- printf "Failed to determine the latest Vault version.\n"
- exit 1
- fi
- VERSION=$${LATEST_VERSION}
-fi
-
-# Check if the vault CLI is installed and has the correct version
-installation_needed=1
-if command -v vault > /dev/null 2>&1; then
- CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
- if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then
- printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
- installation_needed=0
- fi
-fi
-
-if [ $${installation_needed} -eq 1 ]; then
- # Download and install Vault
- if [ -z "$${CURRENT_VERSION}" ]; then
- printf "Installing Vault CLI ...\n\n"
+install() {
+ # Get the architecture of the system
+ ARCH=$(uname -m)
+ if [ "$${ARCH}" = "x86_64" ]; then
+ ARCH="amd64"
+ elif [ "$${ARCH}" = "aarch64" ]; then
+ ARCH="arm64"
else
- printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "$${VERSION}"
+ printf "Unsupported architecture: $${ARCH}\n"
+ return 1
fi
- fetch vault.zip "https://releases.hashicorp.com/vault/$${VERSION}/vault_$${VERSION}_linux_amd64.zip"
- if [ $? -ne 0 ]; then
- printf "Failed to download Vault.\n"
- exit 1
+ # Fetch the latest version of Vault if INSTALL_VERSION is 'latest'
+ if [ "$${INSTALL_VERSION}" = "latest" ]; then
+ LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v 'rc' | grep -oE 'vault/[0-9]+\.[0-9]+\.[0-9]+' | sed 's/vault\///' | sort -V | tail -n 1)
+ printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
+ if [ -z "$${LATEST_VERSION}" ]; then
+ printf "Failed to determine the latest Vault version.\n"
+ return 1
+ fi
+ INSTALL_VERSION=$${LATEST_VERSION}
fi
- unzip vault.zip
- if [ $? -ne 0 ]; then
- printf "Failed to unzip Vault.\n"
- exit 1
+
+ # Check if the vault CLI is installed and has the correct version
+ installation_needed=1
+ if command -v vault > /dev/null 2>&1; then
+ CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
+ if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then
+ printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
+ installation_needed=0
+ fi
fi
- rm vault.zip
- if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
- printf "Vault installed successfully!\n\n"
- else
- mkdir -p ~/.local/bin
- mv vault ~/.local/bin/vault
- if [ ! -f ~/.local/bin/vault ]; then
- printf "Failed to move Vault to local bin.\n"
- exit 1
+
+ if [ $${installation_needed} -eq 1 ]; then
+ # Download and install Vault
+ if [ -z "$${CURRENT_VERSION}" ]; then
+ printf "Installing Vault CLI ...\n\n"
+ else
+ printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${INSTALL_VERSION}"
+ fi
+ fetch vault.zip "https://releases.hashicorp.com/vault/$${INSTALL_VERSION}/vault_$${INSTALL_VERSION}_linux_$${ARCH}.zip"
+ if [ $? -ne 0 ]; then
+ printf "Failed to download Vault.\n"
+ return 1
+ fi
+ if ! unzip_safe vault.zip; then
+ printf "Failed to unzip Vault.\n"
+ return 1
+ fi
+ rm vault.zip
+ if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
+ printf "Vault installed successfully!\n\n"
+ else
+ mkdir -p ~/.local/bin
+ if ! mv vault ~/.local/bin/vault; then
+ printf "Failed to move Vault to local bin.\n"
+ return 1
+ fi
+ printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
fi
- printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
fi
+ return 0
+}
+
+TMP=$(mktemp -d)
+if ! (
+ cd "$TMP"
+ install
+); then
+ echo "Failed to install Vault CLI."
+ exit 1
fi
+rm -rf "$TMP"
# Authenticate with Vault
printf "🔑 Authenticating with Vault ...\n\n"
@@ -92,8 +112,6 @@ if [ $? -ne 0 ]; then
exit 1
fi
-export VAULT_ADDR="$${VAULT_ADDR}"
-
# Login to vault using the GitHub token
printf "🔑 Logging in to Vault ...\n\n"
vault login -no-print -method=github -path=/$${AUTH_PATH} token="$${GITHUB_TOKEN}"
diff --git a/vault-token/README.md b/vault-token/README.md
new file mode 100644
index 0000000..7e632a5
--- /dev/null
+++ b/vault-token/README.md
@@ -0,0 +1,83 @@
+---
+display_name: Hashicorp Vault Integration (Token)
+description: Authenticates with Vault using Token
+icon: ../.icons/vault.svg
+maintainer_github: coder
+partner_github: hashicorp
+verified: true
+tags: [helper, integration, vault, token]
+---
+
+# Hashicorp Vault Integration (Token)
+
+This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using a [Vault token](https://developer.hashicorp.com/vault/docs/auth/token).
+
+```tf
+variable "vault_token" {
+ type = string
+ description = "The Vault token to use for authentication."
+ sensitive = true
+}
+
+module "vault" {
+ source = "registry.coder.com/modules/vault-token/coder"
+ version = "1.0.7"
+ agent_id = coder_agent.example.id
+ vault_token = var.token
+ vault_addr = "https://vault.example.com"
+}
+```
+
+Then you can use the Vault CLI in your workspaces to fetch secrets from Vault:
+
+```shell
+vault kv get -namespace=coder -mount=secrets coder
+```
+
+or using the Vault API:
+
+```shell
+curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder"
+```
+
+## Configuration
+
+To configure the Vault module, you must create a Vault token with the the required permissions and configure the module with the token and Vault address.
+
+1. Create a vault policy with read access to the secret mount you need your developers to access.
+ ```shell
+ vault policy write read-coder-secrets - < {
+ await runTerraformInit(import.meta.dir);
+
+ testRequiredVariables(import.meta.dir, {
+ agent_id: "foo",
+ vault_addr: "foo",
+ vault_token: "foo",
+ });
+});
diff --git a/vault-token/main.tf b/vault-token/main.tf
new file mode 100644
index 0000000..94517d1
--- /dev/null
+++ b/vault-token/main.tf
@@ -0,0 +1,62 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = ">= 0.12.4"
+ }
+ }
+}
+
+# Add required variables for your modules and remove any unneeded variables
+variable "agent_id" {
+ type = string
+ description = "The ID of a Coder agent."
+}
+
+variable "vault_addr" {
+ type = string
+ description = "The address of the Vault server."
+}
+
+variable "vault_token" {
+ type = string
+ description = "The Vault token to use for authentication."
+ sensitive = true
+}
+
+variable "vault_cli_version" {
+ type = string
+ description = "The version of Vault to install."
+ default = "latest"
+ validation {
+ condition = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version))
+ error_message = "Vault version must be in the format 0.0.0 or latest"
+ }
+}
+
+data "coder_workspace" "me" {}
+
+resource "coder_script" "vault" {
+ agent_id = var.agent_id
+ display_name = "Vault (Token)"
+ icon = "/icon/vault.svg"
+ script = templatefile("${path.module}/run.sh", {
+ INSTALL_VERSION : var.vault_cli_version,
+ })
+ run_on_start = true
+ start_blocks_login = true
+}
+
+resource "coder_env" "vault_addr" {
+ agent_id = var.agent_id
+ name = "VAULT_ADDR"
+ value = var.vault_addr
+}
+
+resource "coder_env" "vault_token" {
+ agent_id = var.agent_id
+ name = "VAULT_TOKEN"
+ value = var.vault_token
+}
diff --git a/vault-token/run.sh b/vault-token/run.sh
new file mode 100644
index 0000000..e1da6ee
--- /dev/null
+++ b/vault-token/run.sh
@@ -0,0 +1,103 @@
+#!/usr/bin/env bash
+
+# Convert all templated variables to shell variables
+INSTALL_VERSION=${INSTALL_VERSION}
+
+fetch() {
+ dest="$1"
+ url="$2"
+ if command -v curl > /dev/null 2>&1; then
+ curl -sSL --fail "$${url}" -o "$${dest}"
+ elif command -v wget > /dev/null 2>&1; then
+ wget -O "$${dest}" "$${url}"
+ elif command -v busybox > /dev/null 2>&1; then
+ busybox wget -O "$${dest}" "$${url}"
+ else
+ printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n"
+ return 1
+ fi
+}
+
+unzip_safe() {
+ if command -v unzip > /dev/null 2>&1; then
+ command unzip "$@"
+ elif command -v busybox > /dev/null 2>&1; then
+ busybox unzip "$@"
+ else
+ printf "unzip or busybox is not installed. Please install unzip in your image.\n"
+ return 1
+ fi
+}
+
+install() {
+ # Get the architecture of the system
+ ARCH=$(uname -m)
+ if [ "$${ARCH}" = "x86_64" ]; then
+ ARCH="amd64"
+ elif [ "$${ARCH}" = "aarch64" ]; then
+ ARCH="arm64"
+ else
+ printf "Unsupported architecture: $${ARCH}\n"
+ return 1
+ fi
+ # Fetch the latest version of Vault if INSTALL_VERSION is 'latest'
+ if [ "$${INSTALL_VERSION}" = "latest" ]; then
+ LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v 'rc' | grep -oE 'vault/[0-9]+\.[0-9]+\.[0-9]+' | sed 's/vault\///' | sort -V | tail -n 1)
+ printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
+ if [ -z "$${LATEST_VERSION}" ]; then
+ printf "Failed to determine the latest Vault version.\n"
+ return 1
+ fi
+ INSTALL_VERSION=$${LATEST_VERSION}
+ fi
+
+ # Check if the vault CLI is installed and has the correct version
+ installation_needed=1
+ if command -v vault > /dev/null 2>&1; then
+ CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
+ if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then
+ printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
+ installation_needed=0
+ fi
+ fi
+
+ if [ $${installation_needed} -eq 1 ]; then
+ # Download and install Vault
+ if [ -z "$${CURRENT_VERSION}" ]; then
+ printf "Installing Vault CLI ...\n\n"
+ else
+ printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${INSTALL_VERSION}"
+ fi
+ fetch vault.zip "https://releases.hashicorp.com/vault/$${INSTALL_VERSION}/vault_$${INSTALL_VERSION}_linux_amd64.zip"
+ if [ $? -ne 0 ]; then
+ printf "Failed to download Vault.\n"
+ return 1
+ fi
+ if ! unzip_safe vault.zip; then
+ printf "Failed to unzip Vault.\n"
+ return 1
+ fi
+ rm vault.zip
+ if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
+ printf "Vault installed successfully!\n\n"
+ else
+ mkdir -p ~/.local/bin
+ if ! mv vault ~/.local/bin/vault; then
+ printf "Failed to move Vault to local bin.\n"
+ return 1
+ fi
+ printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
+ fi
+ fi
+ return 0
+}
+
+TMP=$(mktemp -d)
+if ! (
+ cd "$TMP"
+ install
+); then
+ echo "Failed to install Vault CLI."
+ exit 1
+fi
+rm -rf "$TMP"
diff --git a/vscode-desktop/README.md b/vscode-desktop/README.md
index 76ba165..e0d1ff2 100644
--- a/vscode-desktop/README.md
+++ b/vscode-desktop/README.md
@@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
```tf
module "vscode" {
source = "registry.coder.com/modules/vscode-desktop/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
}
```
@@ -28,7 +28,7 @@ module "vscode" {
```tf
module "vscode" {
source = "registry.coder.com/modules/vscode-desktop/coder"
- version = "1.0.0"
+ version = "1.0.8"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
diff --git a/vscode-desktop/main.test.ts b/vscode-desktop/main.test.ts
index 304655d..53fba96 100644
--- a/vscode-desktop/main.test.ts
+++ b/vscode-desktop/main.test.ts
@@ -20,5 +20,18 @@ describe("vscode-desktop", async () => {
expect(state.outputs.vscode_url.value).toBe(
"vscode://coder.coder-remote/open?owner=default&workspace=default&token=$SESSION_TOKEN",
);
+
+ const resources: any = state.resources;
+ expect(resources[1].instances[0].attributes.order).toBeNull();
+ });
+
+ it("expect order to be set", async () => {
+ const state = await runTerraformApply(import.meta.dir, {
+ agent_id: "foo",
+ order: "22",
+ });
+
+ const resources: any = state.resources;
+ expect(resources[1].instances[0].attributes.order).toBe(22);
});
});
diff --git a/vscode-desktop/main.tf b/vscode-desktop/main.tf
index a76ee88..7a0a052 100644
--- a/vscode-desktop/main.tf
+++ b/vscode-desktop/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -20,6 +20,12 @@ variable "folder" {
default = ""
}
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
data "coder_workspace" "me" {}
resource "coder_app" "vscode" {
@@ -28,6 +34,7 @@ resource "coder_app" "vscode" {
icon = "/icon/code.svg"
slug = "vscode"
display_name = "VS Code Desktop"
+ order = var.order
url = var.folder != "" ? join("", [
"vscode://coder.coder-remote/open?owner=",
data.coder_workspace.me.owner,
diff --git a/vscode-web/README.md b/vscode-web/README.md
index f90ba00..f037607 100644
--- a/vscode-web/README.md
+++ b/vscode-web/README.md
@@ -9,12 +9,12 @@ tags: [helper, ide, vscode, web]
# VS Code Web
-Automatically install [Visual Studio Code Server](https://code.visualstudio.com/docs/remote/vscode-server) in a workspace using the [VS Code CLI](https://code.visualstudio.com/docs/editor/command-line) and create an app to access it via the dashboard.
+Automatically install [Visual Studio Code Server](https://code.visualstudio.com/docs/remote/vscode-server) in a workspace and create an app to access it via the dashboard.
```tf
module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder"
- version = "1.0.0"
+ version = "1.0.11"
agent_id = coder_agent.example.id
accept_license = true
}
@@ -29,10 +29,39 @@ module "vscode-web" {
```tf
module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder"
- version = "1.0.0"
+ version = "1.0.11"
agent_id = coder_agent.example.id
- install_dir = "/home/coder/.vscode-web"
+ install_prefix = "/home/coder/.vscode-web"
folder = "/home/coder"
accept_license = true
}
```
+
+### Install Extensions
+
+```tf
+module "vscode-web" {
+ source = "registry.coder.com/modules/vscode-web/coder"
+ version = "1.0.11"
+ agent_id = coder_agent.example.id
+ extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
+ accept_license = true
+}
+```
+
+### Pre-configure Settings
+
+Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
+
+```tf
+module "vscode-web" {
+ source = "registry.coder.com/modules/vscode-web/coder"
+ version = "1.0.11"
+ agent_id = coder_agent.example.id
+ extensions = ["dracula-theme.theme-dracula"]
+ settings = {
+ "workbench.colorTheme" = "Dracula"
+ }
+ accept_license = true
+}
+```
diff --git a/vscode-web/main.test.ts b/vscode-web/main.test.ts
deleted file mode 100644
index 57277df..0000000
--- a/vscode-web/main.test.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { describe, expect, it } from "bun:test";
-import {
- executeScriptInContainer,
- runTerraformApply,
- runTerraformInit,
-} from "../test";
-
-describe("vscode-web", async () => {
- await runTerraformInit(import.meta.dir);
-
- // replaces testRequiredVariables due to license variable
- // may add a testRequiredVariablesWithLicense function later
- it("missing agent_id", async () => {
- try {
- await runTerraformApply(import.meta.dir, {
- accept_license: "true",
- });
- } catch (ex) {
- expect(ex.message).toContain('input variable "agent_id" is not set');
- }
- });
-
- it("invalid license_agreement", async () => {
- try {
- await runTerraformApply(import.meta.dir, {
- agent_id: "foo",
- });
- } catch (ex) {
- expect(ex.message).toContain(
- "You must accept the VS Code license agreement by setting accept_license=true",
- );
- }
- });
-
- it("fails without curl", async () => {
- const state = await runTerraformApply(import.meta.dir, {
- agent_id: "foo",
- accept_license: "true",
- });
- const output = await executeScriptInContainer(state, "alpine");
- expect(output.exitCode).toBe(1);
- expect(output.stdout).toEqual([
- "\u001b[0;1mInstalling vscode-cli!",
- "Failed to install vscode-cli:", // TODO: manually test error log
- ]);
- });
-
- it("runs with curl", async () => {
- const state = await runTerraformApply(import.meta.dir, {
- agent_id: "foo",
- accept_license: "true",
- });
- const output = await executeScriptInContainer(state, "alpine/curl");
- expect(output.exitCode).toBe(0);
- expect(output.stdout).toEqual([
- "\u001b[0;1mInstalling vscode-cli!",
- "🥳 vscode-cli has been installed.",
- "",
- "👷 Running /tmp/vscode-cli/bin/code serve-web --port 13338 --without-connection-token --accept-server-license-terms in the background...",
- "Check logs at /tmp/vscode-web.log!",
- ]);
- });
-});
diff --git a/vscode-web/main.tf b/vscode-web/main.tf
index 1c5e9e7..dd2ab3b 100644
--- a/vscode-web/main.tf
+++ b/vscode-web/main.tf
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = ">= 0.12"
+ version = ">= 0.17"
}
}
}
@@ -20,6 +20,18 @@ variable "port" {
default = 13338
}
+variable "display_name" {
+ type = string
+ description = "The display name for the VS Code Web application."
+ default = "VS Code Web"
+}
+
+variable "slug" {
+ type = string
+ description = "The slug for the VS Code Web application."
+ default = "vscode-web"
+}
+
variable "folder" {
type = string
description = "The folder to open in vscode-web."
@@ -41,15 +53,21 @@ variable "log_path" {
default = "/tmp/vscode-web.log"
}
-variable "install_dir" {
+variable "install_prefix" {
type = string
- description = "The directory to install VS Code CLI"
- default = "/tmp/vscode-cli"
+ description = "The prefix to install vscode-web to."
+ default = "/tmp/vscode-web"
+}
+
+variable "extensions" {
+ type = list(string)
+ description = "A list of extensions to install."
+ default = []
}
variable "accept_license" {
type = bool
- description = "Accept the VS Code license. https://code.visualstudio.com/license"
+ description = "Accept the VS Code Server license. https://code.visualstudio.com/license/server"
default = false
validation {
condition = var.accept_license == true
@@ -57,6 +75,28 @@ variable "accept_license" {
}
}
+variable "telemetry_level" {
+ type = string
+ description = "Set the telemetry level for VS Code Web."
+ default = "error"
+ validation {
+ condition = var.telemetry_level == "off" || var.telemetry_level == "crash" || var.telemetry_level == "error" || var.telemetry_level == "all"
+ error_message = "Incorrect value. Please set either 'off', 'crash', 'error', or 'all'."
+ }
+}
+
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
+variable "settings" {
+ type = map(string)
+ description = "A map of settings to apply to VS Code web."
+ default = {}
+}
+
resource "coder_script" "vscode-web" {
agent_id = var.agent_id
display_name = "VS Code Web"
@@ -64,19 +104,24 @@ resource "coder_script" "vscode-web" {
script = templatefile("${path.module}/run.sh", {
PORT : var.port,
LOG_PATH : var.log_path,
- INSTALL_DIR : var.install_dir,
+ INSTALL_PREFIX : var.install_prefix,
+ EXTENSIONS : join(",", var.extensions),
+ TELEMETRY_LEVEL : var.telemetry_level,
+ // This is necessary otherwise the quotes are stripped!
+ SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
})
run_on_start = true
}
resource "coder_app" "vscode-web" {
agent_id = var.agent_id
- slug = "vscode-web"
- display_name = "VS Code Web"
+ slug = var.slug
+ display_name = var.display_name
url = var.folder == "" ? "http://localhost:${var.port}" : "http://localhost:${var.port}?folder=${var.folder}"
icon = "/icon/code.svg"
subdomain = true
share = var.share
+ order = var.order
healthcheck {
url = "http://localhost:${var.port}/healthz"
diff --git a/vscode-web/run.sh b/vscode-web/run.sh
old mode 100644
new mode 100755
index d19be62..491906f
--- a/vscode-web/run.sh
+++ b/vscode-web/run.sh
@@ -1,21 +1,56 @@
-#!/usr/bin/env sh
+#!/usr/bin/env bash
BOLD='\033[0;1m'
+EXTENSIONS=("${EXTENSIONS}")
-# Create install directory if it doesn't exist
-mkdir -p ${INSTALL_DIR}
+# Create install prefix
+mkdir -p ${INSTALL_PREFIX}
-printf "$${BOLD}Installing vscode-cli!\n"
+printf "$${BOLD}Installing Microsoft Visual Studio Code Server!\n"
-# Download and extract code-cli tarball
-output=$(curl -Lk 'https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64' --output vscode_cli.tar.gz && tar -xf vscode_cli.tar.gz -C ${INSTALL_DIR} && rm vscode_cli.tar.gz)
+# Download and extract vscode-server
+ARCH=$(uname -m)
+case "$ARCH" in
+ x86_64) ARCH="x64" ;;
+ aarch64) ARCH="arm64" ;;
+ *)
+ echo "Unsupported architecture"
+ exit 1
+ ;;
+esac
+
+HASH=$(curl -fsSL https://update.code.visualstudio.com/api/commits/stable/server-linux-$ARCH-web | cut -d '"' -f 2)
+output=$(curl -fsSL https://vscode.download.prss.microsoft.com/dbazure/download/stable/$HASH/vscode-server-linux-$ARCH-web.tar.gz | tar -xz -C ${INSTALL_PREFIX} --strip-components 1)
if [ $? -ne 0 ]; then
- echo "Failed to install vscode-cli: $output"
+ echo "Failed to install Microsoft Visual Studio Code Server: $output"
exit 1
fi
-printf "🥳 vscode-cli has been installed.\n\n"
+printf "$${BOLD}Microsoft Visual Studio Code Server has been installed.\n"
+
+VSCODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
+
+# Install each extension...
+IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}"
+for extension in "$${EXTENSIONLIST[@]}"; do
+ if [ -z "$extension" ]; then
+ continue
+ fi
+ printf "🧩 Installing extension $${CODE}$extension$${RESET}...\n"
+ output=$($VSCODE_SERVER --install-extension "$extension" --force)
+ if [ $? -ne 0 ]; then
+ echo "Failed to install extension: $extension: $output"
+ exit 1
+ fi
+done
+
+# Check if the settings file exists...
+if [ ! -f ~/.vscode-server/data/Machine/settings.json ]; then
+ echo "⚙️ Creating settings file..."
+ mkdir -p ~/.vscode-server/data/Machine
+ echo "${SETTINGS}" > ~/.vscode-server/data/Machine/settings.json
+fi
-echo "👷 Running ${INSTALL_DIR}/bin/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms in the background..."
+echo "👷 Running ${INSTALL_PREFIX}/bin/code-server serve-local --port ${PORT} --host 127.0.0.1 --accept-server-license-terms serve-local --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
echo "Check logs at ${LOG_PATH}!"
-${INSTALL_DIR}/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms > ${LOG_PATH} 2>&1 &
+"${INSTALL_PREFIX}/bin/code-server" serve-local --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms serve-local --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &