Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!(jetbrains-gateway): enable multiple IDE buttons #339

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
39 changes: 27 additions & 12 deletions jetbrains-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +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.23"
version = "1.0.24"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any way to make this ver 1.1.0?
This will immediately break templates using the latest version (version = ">= 1.0.0").
We can fix this by doing version = "~> 1.0.0", which will only update 1.0.x versions, but we don't currently follow semver so that sucks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holding it untill we have #157

agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
default = "GO"
default = ["GO"]
}
```

Expand All @@ -32,27 +32,27 @@ module "jetbrains_gateway" {
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23"
version = "1.0.24"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
default = ["GO"]
}
```

### Use the latest release version
### Use the fixed version

```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23"
version = "1.0.24"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
latest = true
default = ["GO"]
latest = false # current version is 2024.3
}
```

Expand All @@ -61,12 +61,12 @@ module "jetbrains_gateway" {
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23"
version = "1.0.24"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
default = ["GO"]
latest = true
channel = "eap"
}
Expand All @@ -79,14 +79,29 @@ Due to the highest priority of the `ide_download_link` parameter in the `(jetbra
```tf
module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.23"
version = "1.0.24"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
releases_base_link = "https://releases.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"]
}
```

Expand Down
31 changes: 24 additions & 7 deletions jetbrains-gateway/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ describe("jetbrains-gateway", async () => {

it("should create a link with the default values", async () => {
const state = await runTerraformApply(import.meta.dir, {
// These are all required.
agent_id: "foo",
agent_name: "foo",
folder: "/home/coder",
});
expect(state.outputs.url.value).toBe(
"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",
);
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=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
]);

const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "gateway",
Expand All @@ -34,13 +33,31 @@ describe("jetbrains-gateway", async () => {
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, {
agent_id: "foo",
agent_name: "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",
]);
});
});
127 changes: 84 additions & 43 deletions jetbrains-gateway/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,23 @@ variable "folder" {
}

variable "default" {
default = ""
type = string
description = "Default IDE"
default = []
type = list(string)
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" {
Expand All @@ -59,7 +73,7 @@ variable "coder_parameter_order" {
variable "latest" {
type = bool
description = "Whether to fetch the latest version of the IDE."
default = false
default = true
}

variable "channel" {
Expand All @@ -80,36 +94,36 @@ variable "jetbrains_ide_versions" {
description = "The set of versions for each jetbrains IDE"
default = {
"IU" = {
build_number = "241.14494.240"
version = "2024.1"
build_number = "243.21565.193"
version = "2024.3"
}
"PS" = {
build_number = "241.14494.237"
version = "2024.1"
build_number = "243.21565.202"
version = "2024.3"
}
"WS" = {
build_number = "241.14494.235"
version = "2024.1"
build_number = "243.21565.180"
version = "2024.3"
}
"PY" = {
build_number = "241.14494.241"
version = "2024.1"
build_number = "243.21565.199"
version = "2024.3"
}
"CL" = {
build_number = "241.14494.288"
build_number = "243.21565.238"
version = "2024.1"
}
"GO" = {
build_number = "241.14494.238"
version = "2024.1"
build_number = "243.21565.208"
version = "2024.3"
}
"RM" = {
build_number = "241.14494.234"
version = "2024.1"
build_number = "243.21565.197"
version = "2024.3"
}
"RD" = {
build_number = "241.14494.307"
version = "2024.1"
build_number = "243.21565.191"
version = "2024.3"
}
}
validation {
Expand All @@ -124,7 +138,7 @@ variable "jetbrains_ide_versions" {

variable "jetbrains_ides" {
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"]
validation {
condition = (
Expand Down Expand Up @@ -239,23 +253,42 @@ locals {
}
}

icon = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].icon
json_data = var.latest ? jsondecode(data.http.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].response_body) : {}
key = var.latest ? keys(local.json_data)[0] : ""
display_name = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].name
identifier = data.coder_parameter.jetbrains_ide.value
download_link = var.latest ? local.json_data[local.key][0].downloads.linux.link : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link
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
identifier = try([data.coder_parameter.jetbrains_ide[0].value], var.default)
list_json_data = var.latest ? [
for ide in local.identifier : jsondecode(data.http.jetbrains_ide_versions[ide].response_body)
] : []
list_key = var.latest ? [
for j in local.list_json_data : keys(j)[0]
] : []
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" {
# remove the coder_parameter if there are multiple default
count = length(var.default) > 1 ? 0 : 1
type = "string"
name = "jetbrains_ide"
display_name = "JetBrains IDE"
icon = "/icon/gateway.svg"
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

dynamic "option" {
Expand All @@ -272,10 +305,11 @@ data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

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
slug = var.slug
display_name = local.display_name
icon = local.icon
slug = "${var.slug}-${lower(each.value)}"
display_name = local.jetbrains_ides[each.value].name
icon = local.jetbrains_ides[each.value].icon
external = true
order = var.order
url = join("", [
Expand All @@ -292,38 +326,45 @@ resource "coder_app" "gateway" {
"&token=",
"$SESSION_TOKEN",
"&ide_product_code=",
data.coder_parameter.jetbrains_ide.value,
each.value,
"&ide_build_number=",
local.build_number,
local.jetbrains_ides[each.value].build_number,
"&ide_download_link=",
local.download_link,
local.jetbrains_ides[each.value].download_link,
])
}

output "identifier" {
value = local.identifier
value = local.identifier
description = "The product code of the JetBrains IDE."
}

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" {
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" {
value = local.download_link
value = local.download_links
description = "The download link of the JetBrains IDE."
}

output "build_number" {
value = local.build_number
value = local.build_numbers
description = "The build number of the JetBrains IDE."
}

output "version" {
value = local.version
value = local.versions
description = "The version of the JetBrains IDE."
}

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."
}
3 changes: 2 additions & 1 deletion test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ export const runTerraformApply = async <TVars extends TerraformVariables>(

const combinedEnv = env === undefined ? {} : { ...env };
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);
}
Comment on lines +203 to 205
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed to support list(string) inputs for tests.


const proc = spawn(
Expand Down
Loading