Skip to content

Commit ae25328

Browse files
feat(fleet-ide): add Fleet IDE module for JetBrains integration
1 parent da67cd3 commit ae25328

File tree

4 files changed

+306
-0
lines changed

4 files changed

+306
-0
lines changed

.icons/fleet.svg

Lines changed: 60 additions & 0 deletions
Loading
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
display_name: Fleet IDE
3+
description: Add a one-click button to launch JetBrains Fleet IDE to connect to your workspace.
4+
icon: ../../../../.icons/jetbrains.svg
5+
maintainer_github: coder
6+
verified: false
7+
tags: [ide, jetbrains, fleet]
8+
---
9+
10+
# Fleet IDE
11+
12+
This module adds a Fleet IDE button to your Coder workspace that opens the workspace in JetBrains Fleet using SSH remote development.
13+
14+
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.
15+
16+
```tf
17+
module "fleet_ide" {
18+
count = data.coder_workspace.me.start_count
19+
source = "registry.coder.com/coder/fleet-ide/coder"
20+
version = "1.0.0"
21+
agent_id = coder_agent.example.id
22+
}
23+
```
24+
25+
![Fleet IDE](../.images/fleet-ide.png)
26+
27+
## Requirements
28+
29+
- JetBrains Fleet must be installed locally on your development machine
30+
- Download Fleet from: https://www.jetbrains.com/fleet/
31+
32+
## Examples
33+
34+
### Basic usage
35+
36+
```tf
37+
module "fleet_ide" {
38+
count = data.coder_workspace.me.start_count
39+
source = "registry.coder.com/coder/fleet-ide/coder"
40+
version = "1.0.0"
41+
agent_id = coder_agent.example.id
42+
}
43+
```
44+
45+
### Open a specific folder
46+
47+
```tf
48+
module "fleet_ide" {
49+
count = data.coder_workspace.me.start_count
50+
source = "registry.coder.com/coder/fleet-ide/coder"
51+
version = "1.0.0"
52+
agent_id = coder_agent.example.id
53+
folder = "/home/coder/project"
54+
}
55+
```
56+
57+
### Customize app name and grouping
58+
59+
```tf
60+
module "fleet_ide" {
61+
count = data.coder_workspace.me.start_count
62+
source = "registry.coder.com/coder/fleet-ide/coder"
63+
version = "1.0.0"
64+
agent_id = coder_agent.example.id
65+
display_name = "Fleet"
66+
group = "JetBrains IDEs"
67+
order = 1
68+
}
69+
```
70+
71+
## How it works
72+
73+
1. The module creates an external app link in your Coder workspace
74+
2. When clicked, it generates a `fleet://` URI that instructs Fleet to connect via SSH
75+
3. Fleet connects to your workspace using the SSH credentials provided by Coder
76+
4. You can then develop remotely with full Fleet IDE capabilities
77+
78+
## SSH Connection
79+
80+
Fleet uses SSH to connect to your workspace. The connection URL format is:
81+
82+
```
83+
fleet://fleet.ssh/<workspace-url>?pwd=<folder>&forceNewHost=true
84+
```
85+
86+
The `forceNewHost=true` parameter ensures Fleet establishes a fresh connection each time.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, expect, it } from "bun:test";
2+
import {
3+
runTerraformApply,
4+
runTerraformInit,
5+
testRequiredVariables,
6+
} from "~test";
7+
8+
describe("fleet-ide", async () => {
9+
await runTerraformInit(import.meta.dir);
10+
11+
testRequiredVariables(import.meta.dir, {
12+
agent_id: "foo",
13+
});
14+
15+
it("default output", async () => {
16+
const state = await runTerraformApply(import.meta.dir, {
17+
agent_id: "foo",
18+
});
19+
expect(state.outputs.fleet_url.value).toBe(
20+
"fleet://fleet.ssh/https://mydeployment.coder.com?forceNewHost=true",
21+
);
22+
23+
const coder_app = state.resources.find(
24+
(res) => res.type === "coder_app" && res.name === "fleet",
25+
);
26+
27+
expect(coder_app).not.toBeNull();
28+
expect(coder_app?.instances.length).toBe(1);
29+
expect(coder_app?.instances[0].attributes.order).toBeNull();
30+
});
31+
32+
it("adds folder", async () => {
33+
const state = await runTerraformApply(import.meta.dir, {
34+
agent_id: "foo",
35+
folder: "/foo/bar",
36+
});
37+
expect(state.outputs.fleet_url.value).toBe(
38+
"fleet://fleet.ssh/https://mydeployment.coder.com?pwd=/foo/bar&forceNewHost=true",
39+
);
40+
});
41+
42+
it("custom display name and slug", async () => {
43+
const state = await runTerraformApply(import.meta.dir, {
44+
agent_id: "foo",
45+
display_name: "My Fleet",
46+
slug: "my-fleet",
47+
});
48+
expect(state.outputs.fleet_url.value).toBe(
49+
"fleet://fleet.ssh/https://mydeployment.coder.com?forceNewHost=true",
50+
);
51+
52+
const coder_app = state.resources.find(
53+
(res) => res.type === "coder_app" && res.name === "fleet",
54+
);
55+
56+
expect(coder_app).not.toBeNull();
57+
expect(coder_app?.instances[0].attributes.display_name).toBe("My Fleet");
58+
expect(coder_app?.instances[0].attributes.slug).toBe("my-fleet");
59+
});
60+
61+
it("expect order to be set", async () => {
62+
const state = await runTerraformApply(import.meta.dir, {
63+
agent_id: "foo",
64+
order: "22",
65+
});
66+
67+
const coder_app = state.resources.find(
68+
(res) => res.type === "coder_app" && res.name === "fleet",
69+
);
70+
71+
expect(coder_app).not.toBeNull();
72+
expect(coder_app?.instances.length).toBe(1);
73+
expect(coder_app?.instances[0].attributes.order).toBe(22);
74+
});
75+
76+
it("expect group to be set", async () => {
77+
const state = await runTerraformApply(import.meta.dir, {
78+
agent_id: "foo",
79+
group: "JetBrains IDEs",
80+
});
81+
82+
const coder_app = state.resources.find(
83+
(res) => res.type === "coder_app" && res.name === "fleet",
84+
);
85+
86+
expect(coder_app).not.toBeNull();
87+
expect(coder_app?.instances.length).toBe(1);
88+
expect(coder_app?.instances[0].attributes.group).toBe("JetBrains IDEs");
89+
});
90+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 2.5"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
variable "folder" {
18+
type = string
19+
description = "The folder to open in Fleet IDE."
20+
default = ""
21+
}
22+
23+
variable "order" {
24+
type = number
25+
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)."
26+
default = null
27+
}
28+
29+
variable "group" {
30+
type = string
31+
description = "The name of a group that this app belongs to."
32+
default = null
33+
}
34+
35+
variable "slug" {
36+
type = string
37+
description = "The slug of the app."
38+
default = "fleet"
39+
}
40+
41+
variable "display_name" {
42+
type = string
43+
description = "The display name of the app."
44+
default = "Fleet IDE"
45+
}
46+
47+
data "coder_workspace" "me" {}
48+
data "coder_workspace_owner" "me" {}
49+
50+
resource "coder_app" "fleet" {
51+
agent_id = var.agent_id
52+
external = true
53+
icon = "/icon/fleet.svg"
54+
slug = var.slug
55+
display_name = var.display_name
56+
order = var.order
57+
group = var.group
58+
url = join("", [
59+
"fleet://fleet.ssh/",
60+
data.coder_workspace.me.access_url,
61+
"?",
62+
var.folder != "" ? join("", ["pwd=", var.folder, "&"]) : "",
63+
"forceNewHost=true"
64+
])
65+
}
66+
67+
output "fleet_url" {
68+
value = coder_app.fleet.url
69+
description = "Fleet IDE connection URL."
70+
}

0 commit comments

Comments
 (0)