Compare commits

...

10 Commits

Author SHA1 Message Date
Muhammad Atif Ali b88d50a7c9
Merge branch 'main' into atif/multi-gateway 7 months ago
Muhammad Atif Ali cd6aa274f1 fix tests 7 months ago
Muhammad Atif Ali 2f51d70fb7 always use latest and update default versions to 2024.3 7 months ago
Muhammad Atif Ali dbf3c47f45
Merge branch 'main' into atif/multi-gateway 7 months ago
Muhammad Atif Ali d45f2e6ad1 Update JetBrains Gateway module to v1.0.24 8 months ago
Muhammad Atif Ali 70020d8b8c Support multiple default IDEs in JetBrains Gateway 8 months ago
Muhammad Atif Ali 937ffcd47b Update slug format for JetBrains Gateway apps
This change improves URL uniqueness by appending a lowercase IDE
identifier to the slug, ensuring distinct slugs for each default IDE.
8 months ago
Muhammad Atif Ali 5bc2aa4aa0 Fix JetBrains Gateway tests for multiple IDEs
- Allow creation of links with multiple IDEs.
- Ensure outputs handle arrays for identifying multiple IDEs.
- Update runTerraformApply to handle array values as JSON strings.
8 months ago
Muhammad Atif Ali 4452630a7e Support multiple default IDEs in JetBrains Gateway 8 months ago
Muhammad Atif Ali 27e3faf31c feat: enable multiple IDE buttons in JetBrains
Add support for specifying a list of default IDEs to be displayed on
the Workspace page. This allows users to see multiple IDE options
simultaneously. Ensure no duplicates are included and validate
provided IDE codes against allowed set. Adjust logic to dynamically
render IDE buttons based on specified defaults, improving flexibility
in user interface setup.
8 months ago

@ -14,12 +14,12 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"] jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
default = "GO" default = ["GO"]
} }
``` ```
@ -32,27 +32,27 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
default = "GO" default = ["GO"]
} }
``` ```
### Use the latest release version ### Use the fixed version
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
default = "GO" default = ["GO"]
latest = true latest = false # current version is 2024.3
} }
``` ```
@ -61,12 +61,12 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
default = "GO" default = ["GO"]
latest = true latest = true
channel = "eap" channel = "eap"
} }
@ -79,14 +79,29 @@ Due to the highest priority of the `ide_download_link` parameter in the `(jetbra
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23" version = "1.0.24"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"] jetbrains_ides = ["GO", "WS"]
releases_base_link = "https://releases.internal.site/" releases_base_link = "https://releases.internal.site/"
download_base_link = "https://download.internal.site/" download_base_link = "https://download.internal.site/"
default = "GO" default = ["GO"]
}
```
### Add multiple IDEs
**Note:** This removes the choice of IDE from the user.
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.24"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
default = ["GO", "WS"]
} }
``` ```

@ -16,14 +16,13 @@ describe("jetbrains-gateway", async () => {
it("should create a link with the default values", async () => { it("should create a link with the default values", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
// These are all required.
agent_id: "foo", agent_id: "foo",
agent_name: "foo", agent_name: "foo",
folder: "/home/coder", folder: "/home/coder",
}); });
expect(state.outputs.url.value).toBe( expect(state.outputs.url.value).toEqual([
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=241.14494.240&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.1.tar.gz", "jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
); ]);
const coder_app = state.resources.find( const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "gateway", (res) => res.type === "coder_app" && res.name === "gateway",
@ -34,13 +33,31 @@ describe("jetbrains-gateway", async () => {
expect(coder_app?.instances[0].attributes.order).toBeNull(); expect(coder_app?.instances[0].attributes.order).toBeNull();
}); });
it("default to first ide", async () => { it("default to first IDE", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
agent_name: "foo",
folder: "/home/foo",
jetbrains_ides: ["IU", "PY"],
});
expect(state.outputs.identifier.value).toEqual(["IU"]);
expect(state.outputs.url.value).toEqual([
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
]);
});
it("should create multiple IDEs", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",
agent_name: "foo", agent_name: "foo",
folder: "/home/foo", folder: "/home/foo",
jetbrains_ides: '["IU", "GO", "PY"]', default: ["GO", "IU", "PY"],
}); });
expect(state.outputs.identifier.value).toBe("IU"); expect(state.outputs.identifier.value).toEqual(["GO", "IU", "PY"]);
expect(state.outputs.url.value).toEqual([
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=GO&ide_build_number=243.21565.208&ide_download_link=https://download.jetbrains.com/go/goland-2024.3.tar.gz",
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/foo&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=PY&ide_build_number=243.21565.199&ide_download_link=https://download.jetbrains.com/python/pycharm-professional-2024.3.tar.gz",
]);
}); });
}); });

@ -39,9 +39,23 @@ variable "folder" {
} }
variable "default" { variable "default" {
default = "" default = []
type = string type = list(string)
description = "Default IDE" description = "List of default IDEs to be added to the Workspace page."
# check if the list is unique
validation {
condition = length(var.default) == length(toset(var.default))
error_message = "The default must not contain duplicates."
}
# check if default are valid jetbrains_ides
validation {
condition = (
alltrue([
for code in var.default : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"], code)
])
)
error_message = "The default must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"])}."
}
} }
variable "order" { variable "order" {
@ -59,7 +73,7 @@ variable "coder_parameter_order" {
variable "latest" { variable "latest" {
type = bool type = bool
description = "Whether to fetch the latest version of the IDE." description = "Whether to fetch the latest version of the IDE."
default = false default = true
} }
variable "channel" { variable "channel" {
@ -80,36 +94,36 @@ variable "jetbrains_ide_versions" {
description = "The set of versions for each jetbrains IDE" description = "The set of versions for each jetbrains IDE"
default = { default = {
"IU" = { "IU" = {
build_number = "241.14494.240" build_number = "243.21565.193"
version = "2024.1" version = "2024.3"
} }
"PS" = { "PS" = {
build_number = "241.14494.237" build_number = "243.21565.202"
version = "2024.1" version = "2024.3"
} }
"WS" = { "WS" = {
build_number = "241.14494.235" build_number = "243.21565.180"
version = "2024.1" version = "2024.3"
} }
"PY" = { "PY" = {
build_number = "241.14494.241" build_number = "243.21565.199"
version = "2024.1" version = "2024.3"
} }
"CL" = { "CL" = {
build_number = "241.14494.288" build_number = "243.21565.238"
version = "2024.1" version = "2024.1"
} }
"GO" = { "GO" = {
build_number = "241.14494.238" build_number = "243.21565.208"
version = "2024.1" version = "2024.3"
} }
"RM" = { "RM" = {
build_number = "241.14494.234" build_number = "243.21565.197"
version = "2024.1" version = "2024.3"
} }
"RD" = { "RD" = {
build_number = "241.14494.307" build_number = "243.21565.191"
version = "2024.1" version = "2024.3"
} }
} }
validation { validation {
@ -124,7 +138,7 @@ variable "jetbrains_ide_versions" {
variable "jetbrains_ides" { variable "jetbrains_ides" {
type = list(string) type = list(string)
description = "The list of IDE product codes." description = "The list of IDE product codes to be shown to the user. Does not apply when there are multiple defaults."
default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"] default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"]
validation { validation {
condition = ( condition = (
@ -239,23 +253,42 @@ locals {
} }
} }
icon = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].icon identifier = try([data.coder_parameter.jetbrains_ide[0].value], var.default)
json_data = var.latest ? jsondecode(data.http.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].response_body) : {} list_json_data = var.latest ? [
key = var.latest ? keys(local.json_data)[0] : "" for ide in local.identifier : jsondecode(data.http.jetbrains_ide_versions[ide].response_body)
display_name = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].name ] : []
identifier = data.coder_parameter.jetbrains_ide.value list_key = var.latest ? [
download_link = var.latest ? local.json_data[local.key][0].downloads.linux.link : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link for j in local.list_json_data : keys(j)[0]
build_number = var.latest ? local.json_data[local.key][0].build : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].build_number ] : []
version = var.latest ? local.json_data[local.key][0].version : var.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].version download_links = length(local.list_key) > 0 ? [
for i, j in local.list_json_data : j[local.list_key[i]][0].downloads.linux.link
] : [
for ide in local.identifier : local.jetbrains_ides[ide].download_link
]
build_numbers = length(local.list_key) > 0 ? [
for i, j in local.list_json_data : j[local.list_key[i]][0].build
] : [
for ide in local.identifier : local.jetbrains_ides[ide].build_number
]
versions = length(local.list_key) > 0 ? [
for i, j in local.list_json_data : j[local.list_key[i]][0].version
] : [
for ide in local.identifier : local.jetbrains_ides[ide].version
]
display_names = [for key in keys(coder_app.gateway) : coder_app.gateway[key].display_name]
icons = [for key in keys(coder_app.gateway) : coder_app.gateway[key].icon]
urls = [for key in keys(coder_app.gateway) : coder_app.gateway[key].url]
} }
data "coder_parameter" "jetbrains_ide" { data "coder_parameter" "jetbrains_ide" {
# remove the coder_parameter if there are multiple default
count = length(var.default) > 1 ? 0 : 1
type = "string" type = "string"
name = "jetbrains_ide" name = "jetbrains_ide"
display_name = "JetBrains IDE" display_name = "JetBrains IDE"
icon = "/icon/gateway.svg" icon = "/icon/gateway.svg"
mutable = true mutable = true
default = var.default == "" ? var.jetbrains_ides[0] : var.default default = length(var.default) > 0 ? var.default[0] : var.jetbrains_ides[0]
order = var.coder_parameter_order order = var.coder_parameter_order
dynamic "option" { dynamic "option" {
@ -272,10 +305,11 @@ data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {} data "coder_workspace_owner" "me" {}
resource "coder_app" "gateway" { resource "coder_app" "gateway" {
for_each = length(var.default) > 1 ? toset(var.default) : toset([data.coder_parameter.jetbrains_ide[0].value])
agent_id = var.agent_id agent_id = var.agent_id
slug = var.slug slug = "${var.slug}-${lower(each.value)}"
display_name = local.display_name display_name = local.jetbrains_ides[each.value].name
icon = local.icon icon = local.jetbrains_ides[each.value].icon
external = true external = true
order = var.order order = var.order
url = join("", [ url = join("", [
@ -292,38 +326,45 @@ resource "coder_app" "gateway" {
"&token=", "&token=",
"$SESSION_TOKEN", "$SESSION_TOKEN",
"&ide_product_code=", "&ide_product_code=",
data.coder_parameter.jetbrains_ide.value, each.value,
"&ide_build_number=", "&ide_build_number=",
local.build_number, local.jetbrains_ides[each.value].build_number,
"&ide_download_link=", "&ide_download_link=",
local.download_link, local.jetbrains_ides[each.value].download_link,
]) ])
} }
output "identifier" { output "identifier" {
value = local.identifier value = local.identifier
description = "The product code of the JetBrains IDE."
} }
output "display_name" { output "display_name" {
value = local.display_name value = [for key in keys(coder_app.gateway) : coder_app.gateway[key].display_name]
description = "The display name of the JetBrains IDE."
} }
output "icon" { output "icon" {
value = local.icon value = [for key in keys(coder_app.gateway) : coder_app.gateway[key].icon]
description = "The icon of the JetBrains IDE."
} }
output "download_link" { output "download_link" {
value = local.download_link value = local.download_links
description = "The download link of the JetBrains IDE."
} }
output "build_number" { output "build_number" {
value = local.build_number value = local.build_numbers
description = "The build number of the JetBrains IDE."
} }
output "version" { output "version" {
value = local.version value = local.versions
description = "The version of the JetBrains IDE."
} }
output "url" { output "url" {
value = coder_app.gateway.url value = [for key in keys(coder_app.gateway) : coder_app.gateway[key].url]
description = "The URL to connect to the JetBrains IDE."
} }

@ -200,7 +200,8 @@ export const runTerraformApply = async <TVars extends TerraformVariables>(
const combinedEnv = env === undefined ? {} : { ...env }; const combinedEnv = env === undefined ? {} : { ...env };
for (const [key, value] of Object.entries(vars)) { for (const [key, value] of Object.entries(vars)) {
combinedEnv[`TF_VAR_${key}`] = String(value); // Convert arrays to JSON strings
combinedEnv[`TF_VAR_${key}`] = Array.isArray(value) ? JSON.stringify(value) : String(value);
} }
const proc = spawn( const proc = spawn(

Loading…
Cancel
Save