diff --git a/.icons/fleet.svg b/.icons/fleet.svg new file mode 100644 index 0000000..ba910eb --- /dev/null +++ b/.icons/fleet.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/registry/coder/modules/fleet-ide/README.md b/registry/coder/modules/fleet-ide/README.md new file mode 100644 index 0000000..9cec46b --- /dev/null +++ b/registry/coder/modules/fleet-ide/README.md @@ -0,0 +1,86 @@ +--- +display_name: Fleet IDE +description: Add a one-click button to launch JetBrains Fleet IDE to connect to your workspace. +icon: ../../../../.icons/jetbrains.svg +maintainer_github: coder +verified: false +tags: [ide, jetbrains, fleet] +--- + +# Fleet IDE + +This module adds a Fleet IDE button to your Coder workspace that opens the workspace in JetBrains Fleet using SSH remote development. + +JetBrains Fleet is a next-generation IDE that supports collaborative development and distributed architectures. It connects to your Coder workspace via SSH, providing a seamless remote development experience. + +```tf +module "fleet_ide" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/fleet-ide/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + +![Fleet IDE](../.images/fleet-ide.png) + +## Requirements + +- JetBrains Fleet must be installed locally on your development machine +- Download Fleet from: https://www.jetbrains.com/fleet/ + +## Examples + +### Basic usage + +```tf +module "fleet_ide" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/fleet-ide/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + +### Open a specific folder + +```tf +module "fleet_ide" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/fleet-ide/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" +} +``` + +### Customize app name and grouping + +```tf +module "fleet_ide" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/fleet-ide/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + display_name = "Fleet" + group = "JetBrains IDEs" + order = 1 +} +``` + +## How it works + +1. The module creates an external app link in your Coder workspace +2. When clicked, it generates a `fleet://` URI that instructs Fleet to connect via SSH +3. Fleet connects to your workspace using the SSH credentials provided by Coder +4. You can then develop remotely with full Fleet IDE capabilities + +## SSH Connection + +Fleet uses SSH to connect to your workspace. The connection URL format is: + +``` +fleet://fleet.ssh/?pwd=&forceNewHost=true +``` + +The `forceNewHost=true` parameter ensures Fleet establishes a fresh connection each time. diff --git a/registry/coder/modules/fleet-ide/main.test.ts b/registry/coder/modules/fleet-ide/main.test.ts new file mode 100644 index 0000000..4034a19 --- /dev/null +++ b/registry/coder/modules/fleet-ide/main.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("fleet-ide", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("default output", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + expect(state.outputs.fleet_url.value).toBe( + "fleet://fleet.ssh/https://mydeployment.coder.com?forceNewHost=true", + ); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "fleet", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.order).toBeNull(); + }); + + it("adds folder", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/foo/bar", + }); + expect(state.outputs.fleet_url.value).toBe( + "fleet://fleet.ssh/https://mydeployment.coder.com?pwd=/foo/bar&forceNewHost=true", + ); + }); + + it("custom display name and slug", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + display_name: "My Fleet", + slug: "my-fleet", + }); + expect(state.outputs.fleet_url.value).toBe( + "fleet://fleet.ssh/https://mydeployment.coder.com?forceNewHost=true", + ); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "fleet", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances[0].attributes.display_name).toBe("My Fleet"); + expect(coder_app?.instances[0].attributes.slug).toBe("my-fleet"); + }); + + it("expect order to be set", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + order: "22", + }); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "fleet", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.order).toBe(22); + }); + + it("expect group to be set", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + group: "JetBrains IDEs", + }); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "fleet", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.group).toBe("JetBrains IDEs"); + }); +}); \ No newline at end of file diff --git a/registry/coder/modules/fleet-ide/main.tf b/registry/coder/modules/fleet-ide/main.tf new file mode 100644 index 0000000..1635887 --- /dev/null +++ b/registry/coder/modules/fleet-ide/main.tf @@ -0,0 +1,70 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "folder" { + type = string + description = "The folder to open in Fleet IDE." + 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 +} + +variable "group" { + type = string + description = "The name of a group that this app belongs to." + default = null +} + +variable "slug" { + type = string + description = "The slug of the app." + default = "fleet" +} + +variable "display_name" { + type = string + description = "The display name of the app." + default = "Fleet IDE" +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_app" "fleet" { + agent_id = var.agent_id + external = true + icon = "/icon/fleet.svg" + slug = var.slug + display_name = var.display_name + order = var.order + group = var.group + url = join("", [ + "fleet://fleet.ssh/", + data.coder_workspace.me.access_url, + "?", + var.folder != "" ? join("", ["pwd=", var.folder, "&"]) : "", + "forceNewHost=true" + ]) +} + +output "fleet_url" { + value = coder_app.fleet.url + description = "Fleet IDE connection URL." +}