diff --git a/registry/coder/modules/jetbrains/README.md b/registry/coder/modules/jetbrains/README.md index 9d08e6456..7b55232c5 100644 --- a/registry/coder/modules/jetbrains/README.md +++ b/registry/coder/modules/jetbrains/README.md @@ -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 @@ -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 @@ -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 @@ -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"] @@ -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" @@ -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" @@ -128,7 +128,7 @@ 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"] @@ -136,6 +136,26 @@ module "jetbrains" { } ``` +### 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 diff --git a/registry/coder/modules/jetbrains/jetbrains.tftest.hcl b/registry/coder/modules/jetbrains/jetbrains.tftest.hcl index 7676c34f7..b0e4d8d2a 100644 --- a/registry/coder/modules/jetbrains/jetbrains.tftest.hcl +++ b/registry/coder/modules/jetbrains/jetbrains.tftest.hcl @@ -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 @@ -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}'" + } +} diff --git a/registry/coder/modules/jetbrains/main.tf b/registry/coder/modules/jetbrains/main.tf index 8f0e0ac72..51f7c8168 100644 --- a/registry/coder/modules/jetbrains/main.tf +++ b/registry/coder/modules/jetbrains/main.tf @@ -257,4 +257,13 @@ resource "coder_app" "jetbrains" { local.options_metadata[each.key].build, var.agent_name != null ? "&agent_name=${var.agent_name}" : "", ]) -} \ No newline at end of file +} + +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] + } +}