Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions registry/coder/modules/jetbrains/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This module adds JetBrains IDE buttons to launch IDEs directly from the dashboar
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
# tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button." # Optional
Expand All @@ -40,7 +40,7 @@ When `default` contains IDE codes, those IDEs are created directly without user
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
default = ["PY", "IU"] # Pre-configure GoLand and IntelliJ IDEA
Expand All @@ -53,7 +53,7 @@ module "jetbrains" {
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
# Show parameter with limited options
Expand All @@ -67,7 +67,7 @@ module "jetbrains" {
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
default = ["IU", "PY"]
Expand All @@ -82,7 +82,7 @@ module "jetbrains" {
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/workspace/project"

Expand All @@ -108,7 +108,7 @@ module "jetbrains" {
module "jetbrains_pycharm" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/workspace/project"

Expand All @@ -128,14 +128,34 @@ Add helpful tooltip text that appears when users hover over the IDE app buttons:
module "jetbrains" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains/coder"
version = "1.1.1"
version = "1.2.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
default = ["IU", "PY"]
tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button."
}
```

### Accessing the IDE Metadata

You can now reference the output `ide_metadata` as a map.

```tf
# Add metadata to the container showing the installed IDEs and their build versions.
resource "coder_metadata" "container_info" {
count = data.coder_workspace.me.start_count
resource_id = one(docker_container.workspace).id

dynamic "item" {
for_each = length(module.jetbrains) > 0 ? one(module.jetbrains).ide_metadata : {}
content {
key = item.value.build
value = "${item.value.name} [${item.key}]"
}
}
}
```

## Behavior

### Parameter vs Direct Apps
Expand Down
134 changes: 134 additions & 0 deletions registry/coder/modules/jetbrains/jetbrains.tftest.hcl
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
variables {
# Default IDE config, mirrored from main.tf for test assertions.
# If main.tf defaults change, update this map to match.
expected_ide_config = {
"CL" = { name = "CLion", icon = "/icon/clion.svg", build = "251.26927.39" },
"GO" = { name = "GoLand", icon = "/icon/goland.svg", build = "251.26927.50" },
"IU" = { name = "IntelliJ IDEA", icon = "/icon/intellij.svg", build = "251.26927.53" },
"PS" = { name = "PhpStorm", icon = "/icon/phpstorm.svg", build = "251.26927.60" },
"PY" = { name = "PyCharm", icon = "/icon/pycharm.svg", build = "251.26927.74" },
"RD" = { name = "Rider", icon = "/icon/rider.svg", build = "251.26927.67" },
"RM" = { name = "RubyMine", icon = "/icon/rubymine.svg", build = "251.26927.47" },
"RR" = { name = "RustRover", icon = "/icon/rustrover.svg", build = "251.26927.79" },
"WS" = { name = "WebStorm", icon = "/icon/webstorm.svg", build = "251.26927.40" }
}
}

run "validate_test_config_matches_defaults" {
command = plan

variables {
# Provide minimal vars to allow plan to read module variables
agent_id = "foo"
folder = "/home/coder"
}

assert {
condition = length(var.ide_config) == length(var.expected_ide_config)
error_message = "Test configuration mismatch: 'var.ide_config' in main.tf has ${length(var.ide_config)} items, but 'var.expected_ide_config' in the test file has ${length(var.expected_ide_config)} items. Please update the test file's global variables block."
}

assert {
# Check that all keys in the test local are present in the module's default
condition = alltrue([
for key in keys(var.expected_ide_config) :
can(var.ide_config[key])
])
error_message = "Test configuration mismatch: Keys in 'var.expected_ide_config' are out of sync with 'var.ide_config' defaults. Please update the test file's global variables block."
}

assert {
# Check if all build numbers in the test local match the module's defaults
# This relies on the previous two assertions passing (same length, same keys)
condition = alltrue([
for key, config in var.expected_ide_config :
var.ide_config[key].build == config.build
])
error_message = "Test configuration mismatch: One or more build numbers in 'var.expected_ide_config' do not match the defaults in 'var.ide_config'. Please update the test file's locals block."
}
}

run "requires_agent_and_folder" {
command = plan

Expand Down Expand Up @@ -160,3 +210,87 @@ run "tooltip_null_when_not_provided" {
error_message = "Expected coder_app tooltip to be null when not provided"
}
}

run "output_empty_when_default_empty" {
command = plan

variables {
agent_id = "foo"
folder = "/home/coder"
# var.default is empty
}

assert {
condition = length(output.ide_metadata) == 0
error_message = "Expected ide_metadata output to be empty when var.default is not set"
}
}

run "output_single_ide_uses_fallback_build" {
command = plan

variables {
agent_id = "foo"
folder = "/home/coder"
default = ["GO"]
# Force HTTP data source to fail to test fallback logic
releases_base_link = "https://coder.com"
}

assert {
condition = length(output.ide_metadata) == 1
error_message = "Expected ide_metadata output to have 1 item"
}

assert {
condition = can(output.ide_metadata["GO"])
error_message = "Expected ide_metadata output to have key 'GO'"
}

assert {
condition = output.ide_metadata["GO"].name == var.expected_ide_config["GO"].name
error_message = "Expected ide_metadata['GO'].name to be '${var.expected_ide_config["GO"].name}'"
}

assert {
condition = output.ide_metadata["GO"].build == var.expected_ide_config["GO"].build
error_message = "Expected ide_metadata['GO'].build to use the fallback '${var.expected_ide_config["GO"].build}'"
}

assert {
condition = output.ide_metadata["GO"].icon == var.expected_ide_config["GO"].icon
error_message = "Expected ide_metadata['GO'].icon to be '${var.expected_ide_config["GO"].icon}'"
}
}

run "output_multiple_ides" {
command = plan

variables {
agent_id = "foo"
folder = "/home/coder"
default = ["IU", "PY"]
# Force HTTP data source to fail to test fallback logic
releases_base_link = "https://coder.com"
}

assert {
condition = length(output.ide_metadata) == 2
error_message = "Expected ide_metadata output to have 2 items"
}

assert {
condition = can(output.ide_metadata["IU"]) && can(output.ide_metadata["PY"])
error_message = "Expected ide_metadata output to have keys 'IU' and 'PY'"
}

assert {
condition = output.ide_metadata["PY"].name == var.expected_ide_config["PY"].name
error_message = "Expected ide_metadata['PY'].name to be '${var.expected_ide_config["PY"].name}'"
}

assert {
condition = output.ide_metadata["PY"].build == var.expected_ide_config["PY"].build
error_message = "Expected ide_metadata['PY'].build to be the fallback '${var.expected_ide_config["PY"].build}'"
}
}
11 changes: 10 additions & 1 deletion registry/coder/modules/jetbrains/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,13 @@ resource "coder_app" "jetbrains" {
local.options_metadata[each.key].build,
var.agent_name != null ? "&agent_name=${var.agent_name}" : "",
])
}
}

output "ide_metadata" {
description = "A map of the metadata for each selected JetBrains IDE."
value = {
# We iterate directly over the selected_ides map.
# 'key' will be the IDE key (e.g., "IC", "PY")
for key, val in local.selected_ides : key => local.options_metadata[key]
}
}