Skip to content

Commit b040ad1

Browse files
feat(jetbrains-fleet): add Fleet IDE module for JetBrains integration (#176)
## Description Introduces module to launch workspace in fleet --- ## Type of Change - [X] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other --- ## Module Information **Path:** `registry/coder/modules/fleet-ide` **New version:** `v1.0.0` **Breaking change:** [ ] Yes [X] No --- ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [ ] Changes tested locally --------- Co-authored-by: Atif Ali <[email protected]> Co-authored-by: Muhammad Atif Ali <[email protected]>
1 parent b742900 commit b040ad1

File tree

4 files changed

+322
-0
lines changed

4 files changed

+322
-0
lines changed

.icons/fleet.svg

Lines changed: 60 additions & 0 deletions
Loading
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
display_name: JetBrains Fleet
3+
description: Add a one-click button to launch JetBrains Fleet to connect to your workspace.
4+
icon: ../../../../.icons/jetbrains.svg
5+
verified: true
6+
tags: [ide, jetbrains, fleet]
7+
---
8+
9+
# Jetbrains Fleet
10+
11+
This module adds a Jetbrains Fleet button to your Coder workspace that opens the workspace in JetBrains Fleet using SSH remote development.
12+
13+
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.
14+
15+
```tf
16+
module "jetbrains_fleet" {
17+
count = data.coder_workspace.me.start_count
18+
source = "registry.coder.com/coder/jetbrains-fleet/coder"
19+
version = "1.0.0"
20+
agent_id = coder_agent.example.id
21+
}
22+
```
23+
24+
## Requirements
25+
26+
- JetBrains Fleet must be installed locally on your development machine
27+
- Download Fleet from: https://www.jetbrains.com/fleet/
28+
29+
> [!IMPORTANT]
30+
> Fleet needs you to either have Coder CLI installed with `coder config-ssh` run or [Coder Desktop](https://coder.com/docs/user-guides/desktop).
31+
32+
## Examples
33+
34+
### Basic usage
35+
36+
```tf
37+
module "jetbrains_fleet" {
38+
count = data.coder_workspace.me.start_count
39+
source = "registry.coder.com/coder/jetbrains-fleet/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 "jetbrains_fleet" {
49+
count = data.coder_workspace.me.start_count
50+
source = "registry.coder.com/coder/jetbrains-fleet/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 "jetbrains_fleet" {
61+
count = data.coder_workspace.me.start_count
62+
source = "registry.coder.com/coder/jetbrains-fleet/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+
### With custom agent name
72+
73+
```tf
74+
module "jetbrains_fleet" {
75+
count = data.coder_workspace.me.start_count
76+
source = "registry.coder.com/coder/jetbrains-fleet/coder"
77+
version = "1.0.0"
78+
agent_id = coder_agent.example.id
79+
agent_name = coder_agent.example.name
80+
}
81+
```
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { describe, expect, it } from "bun:test";
2+
import {
3+
runTerraformApply,
4+
runTerraformInit,
5+
testRequiredVariables,
6+
} from "~test";
7+
8+
describe("jetbrains-fleet", 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/default.coder",
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/default.coder?pwd=/foo/bar",
39+
);
40+
});
41+
42+
it("adds agent_name to hostname", async () => {
43+
const state = await runTerraformApply(import.meta.dir, {
44+
agent_id: "foo",
45+
agent_name: "myagent",
46+
});
47+
expect(state.outputs.fleet_url.value).toBe(
48+
"fleet://fleet.ssh/myagent.default.default.coder",
49+
);
50+
});
51+
52+
it("custom display name and slug", async () => {
53+
const state = await runTerraformApply(import.meta.dir, {
54+
agent_id: "foo",
55+
display_name: "My Fleet",
56+
slug: "my-fleet",
57+
});
58+
expect(state.outputs.fleet_url.value).toBe(
59+
"fleet://fleet.ssh/default.coder",
60+
);
61+
62+
const coder_app = state.resources.find(
63+
(res) => res.type === "coder_app" && res.name === "fleet",
64+
);
65+
66+
expect(coder_app).not.toBeNull();
67+
expect(coder_app?.instances[0].attributes.display_name).toBe("My Fleet");
68+
expect(coder_app?.instances[0].attributes.slug).toBe("my-fleet");
69+
});
70+
71+
it("expect order to be set", async () => {
72+
const state = await runTerraformApply(import.meta.dir, {
73+
agent_id: "foo",
74+
order: "22",
75+
});
76+
77+
const coder_app = state.resources.find(
78+
(res) => res.type === "coder_app" && res.name === "fleet",
79+
);
80+
81+
expect(coder_app).not.toBeNull();
82+
expect(coder_app?.instances.length).toBe(1);
83+
expect(coder_app?.instances[0].attributes.order).toBe(22);
84+
});
85+
86+
it("expect group to be set", async () => {
87+
const state = await runTerraformApply(import.meta.dir, {
88+
agent_id: "foo",
89+
group: "JetBrains IDEs",
90+
});
91+
92+
const coder_app = state.resources.find(
93+
(res) => res.type === "coder_app" && res.name === "fleet",
94+
);
95+
96+
expect(coder_app).not.toBeNull();
97+
expect(coder_app?.instances.length).toBe(1);
98+
expect(coder_app?.instances[0].attributes.group).toBe("JetBrains IDEs");
99+
});
100+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 "agent_name" {
18+
type = string
19+
description = "The name of the agent"
20+
default = ""
21+
}
22+
23+
variable "folder" {
24+
type = string
25+
description = "The folder to open in Fleet IDE."
26+
default = ""
27+
}
28+
29+
variable "order" {
30+
type = number
31+
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)."
32+
default = null
33+
}
34+
35+
variable "group" {
36+
type = string
37+
description = "The name of a group that this app belongs to."
38+
default = null
39+
}
40+
41+
variable "slug" {
42+
type = string
43+
description = "The slug of the app."
44+
default = "fleet"
45+
}
46+
47+
variable "display_name" {
48+
type = string
49+
description = "The display name of the app."
50+
default = "JetBrains Fleet"
51+
}
52+
53+
data "coder_workspace" "me" {}
54+
data "coder_workspace_owner" "me" {}
55+
56+
locals {
57+
workspace_name = lower(data.coder_workspace.me.name)
58+
owner_name = lower(data.coder_workspace_owner.me.name)
59+
agent_name = lower(var.agent_name)
60+
hostname = var.agent_name != "" ? "${local.agent_name}.${local.workspace_name}.${local.owner_name}.coder" : "${local.workspace_name}.coder"
61+
}
62+
63+
resource "coder_app" "fleet" {
64+
agent_id = var.agent_id
65+
external = true
66+
icon = "/icon/fleet.svg"
67+
slug = var.slug
68+
display_name = var.display_name
69+
order = var.order
70+
group = var.group
71+
url = join("", [
72+
"fleet://fleet.ssh/",
73+
local.hostname,
74+
var.folder != "" ? join("", ["?pwd=", var.folder]) : ""
75+
])
76+
}
77+
78+
output "fleet_url" {
79+
value = coder_app.fleet.url
80+
description = "Fleet IDE connection URL."
81+
}

0 commit comments

Comments
 (0)