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. + ![HCP vault secrets credentials](../.images/hcp-vault-secrets-credentials.png) +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" ``` ![Vault login](../.images/vault-login.png) @@ -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 &