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 <atif@coder.com> Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
This commit is contained in:
parent
b74290051e
commit
b040ad1b1c
60
.icons/fleet.svg
Normal file
60
.icons/fleet.svg
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="rotate(49.385 -18.029987 36.649084) scale(49.4299)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".026042" stop-color="#8DFDFD"/>
|
||||||
|
<stop offset=".270833" stop-color="#87FBFB"/>
|
||||||
|
<stop offset=".484416" stop-color="#74D6F4"/>
|
||||||
|
<stop offset=".931964" stop-color="#0038FF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="b" cx="0" cy="0" r="1" gradientTransform="rotate(132.274 3.919184 20.864728) scale(23.7857)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0500FF" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#0100FF" stop-opacity=".15"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="rotate(42.678 -19.143042 44.644478) scale(41.8951)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".520394" stop-color="#FF00E5" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#FF00E5" stop-opacity=".65"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(30.00005 -22.00001 19.46596 26.54453 32.3943 42.4)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".777466" stop-color="#001AFF"/>
|
||||||
|
<stop offset="1" stop-color="#8ACEFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="f" cx="0" cy="0" r="1" gradientTransform="matrix(14.91531 -8.80077 11.61873 19.69112 44.057 27.7156)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".71875" stop-color="#FA00FF" stop-opacity="0"/>
|
||||||
|
<stop offset="1" stop-color="#FF00D6" stop-opacity=".44"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="h" cx="0" cy="0" r="1" gradientTransform="rotate(63.435 -9.856848 34.706598) scale(30.4105 69.8305)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0D67A9"/>
|
||||||
|
<stop offset="1" stop-color="#AEDDFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="j" cx="0" cy="0" r="1" gradientTransform="rotate(73.835 -3.838438 33.695644) scale(28.736 56.1739)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#0068C9"/>
|
||||||
|
<stop offset="1" stop-color="#fff"/>
|
||||||
|
</radialGradient>
|
||||||
|
<filter id="g" width="48.3057" height="34.5039" x="8.25781" y="24.2656" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur result="effect1_foregroundBlur_6490_3223" stdDeviation="1"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="i" width="45.7057" height="31.9039" x="9.55781" y="25.5656" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feGaussianBlur result="effect1_foregroundBlur_6490_3223" stdDeviation=".35"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="d" x1="63.9941" x2="37.1941" y1="33.6" y2="34.4" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FD3AF5"/>
|
||||||
|
<stop offset="1" stop-color="#FD3AF5" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#a)" d="M63.9941 32c0 17.6731-14.3268 32-32 32C14.3211 64-.00586 49.6731-.00586 32c0-17.6731 14.32696-32 31.99996-32 8.306 3.75956 16.9952 17.7487 20.6338 22.1202 3.6389 4.3715 13.0573 9.8798 8.4414-1.6751 1.9681 3.1232 2.9248 8.3051 2.9248 11.5549Z"/>
|
||||||
|
<path fill="url(#b)" d="M63.9941 32c0 17.6731-14.3268 32-32 32C14.3211 64-.00586 49.6731-.00586 32c0-17.6731 14.32696-32 31.99996-32 8.306 3.75956 16.9952 17.7487 20.6338 22.1202 3.6389 4.3715 13.0573 9.8798 8.4414-1.6751 1.9681 3.1232 2.9248 8.3051 2.9248 11.5549Z"/>
|
||||||
|
<path fill="url(#c)" d="M63.9941 32c0 17.6731-14.3268 32-32 32C14.3211 64-.00586 49.6731-.00586 32c0-17.6731 14.32696-32 31.99996-32 8.306 3.75956 16.9952 17.7487 20.6338 22.1202 3.6389 4.3715 13.0573 9.8798 8.4414-1.6751 1.9681 3.1232 2.9248 8.3051 2.9248 11.5549Z"/>
|
||||||
|
<path fill="url(#d)" fill-opacity=".3" d="M63.9941 32c0 17.6731-14.3268 32-32 32C14.3211 64-.00586 49.6731-.00586 32c0-17.6731 14.32696-32 31.99996-32 8.306 3.75956 16.9952 17.7487 20.6338 22.1202 3.6389 4.3715 13.0573 9.8798 8.4414-1.6751 1.9681 3.1232 2.9248 8.3051 2.9248 11.5549Z"/>
|
||||||
|
<path fill="url(#e)" d="M61.0886 20.4758c-3.1529-5.3391-9.686-9.2821-17.5378-10.2688 2.2608 2.7375 4.3175 5.5453 6.0172 7.8664 1.2242 1.6711 2.2633 3.0899 3.0601 4.0469 3.6389 4.3711 13.0573 9.8797 8.4414-1.675.0063.0102.0127.0203.0191.0305Z"/>
|
||||||
|
<path fill="url(#f)" d="M61.0886 20.4758c-3.1529-5.3391-9.686-9.2821-17.5378-10.2688 2.2608 2.7375 4.3175 5.5453 6.0172 7.8664 1.2242 1.6711 2.2633 3.0899 3.0601 4.0469 3.6389 4.3711 13.0573 9.8797 8.4414-1.675.0063.0102.0127.0203.0191.0305Z"/>
|
||||||
|
<g filter="url(#g)">
|
||||||
|
<path fill="url(#h)" d="M29.3127 27.0066c12.113-2.5862 23.3196 1.8139 25.0306 9.8279 1.711 8.014-6.7214 16.6071-18.8343 19.1933C23.396 58.614 12.1894 54.214 10.4783 46.2c-1.711-8.014 6.7215-16.6072 18.8344-19.1934Z"/>
|
||||||
|
</g>
|
||||||
|
<g filter="url(#i)">
|
||||||
|
<path fill="url(#j)" fill-opacity=".2" fill-rule="evenodd" d="M48.9867 47.3643c3.1734-3.2337 4.5278-6.8744 3.8174-10.2012-.7102-3.3268-3.4332-6.0967-7.6507-7.7527-4.2039-1.6506-9.7148-2.1025-15.5122-.8645-5.7973 1.2377-10.6433 3.9007-13.8065 7.1243-3.1734 3.2339-4.5276 6.8744-3.8173 10.2012.7103 3.3267 3.4332 6.0968 7.6506 7.7527 4.2039 1.6506 9.7148 2.1024 15.5122.8647 5.7974-1.2377 10.6433-3.9009 13.8065-7.1245Zm-13.4778 8.6636c12.1128-2.5862 20.5452-11.1793 18.8343-19.1933-1.7112-8.0141-12.9177-12.4142-25.0305-9.828-12.1131 2.5862-20.54545 11.1795-18.8344 19.1935 1.711 8.0138 12.9176 12.414 25.0306 9.8278Z" clip-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
81
registry/coder/modules/jetbrains-fleet/README.md
Normal file
81
registry/coder/modules/jetbrains-fleet/README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
display_name: JetBrains Fleet
|
||||||
|
description: Add a one-click button to launch JetBrains Fleet to connect to your workspace.
|
||||||
|
icon: ../../../../.icons/jetbrains.svg
|
||||||
|
verified: true
|
||||||
|
tags: [ide, jetbrains, fleet]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Jetbrains Fleet
|
||||||
|
|
||||||
|
This module adds a Jetbrains Fleet 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 "jetbrains_fleet" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/coder/jetbrains-fleet/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- JetBrains Fleet must be installed locally on your development machine
|
||||||
|
- Download Fleet from: https://www.jetbrains.com/fleet/
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Fleet needs you to either have Coder CLI installed with `coder config-ssh` run or [Coder Desktop](https://coder.com/docs/user-guides/desktop).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic usage
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "jetbrains_fleet" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/coder/jetbrains-fleet/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Open a specific folder
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "jetbrains_fleet" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/coder/jetbrains-fleet/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
folder = "/home/coder/project"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize app name and grouping
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "jetbrains_fleet" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/coder/jetbrains-fleet/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
display_name = "Fleet"
|
||||||
|
group = "JetBrains IDEs"
|
||||||
|
order = 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With custom agent name
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "jetbrains_fleet" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/coder/jetbrains-fleet/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
agent_name = coder_agent.example.name
|
||||||
|
}
|
||||||
|
```
|
||||||
100
registry/coder/modules/jetbrains-fleet/main.test.ts
Normal file
100
registry/coder/modules/jetbrains-fleet/main.test.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import {
|
||||||
|
runTerraformApply,
|
||||||
|
runTerraformInit,
|
||||||
|
testRequiredVariables,
|
||||||
|
} from "~test";
|
||||||
|
|
||||||
|
describe("jetbrains-fleet", 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/default.coder",
|
||||||
|
);
|
||||||
|
|
||||||
|
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/default.coder?pwd=/foo/bar",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds agent_name to hostname", async () => {
|
||||||
|
const state = await runTerraformApply(import.meta.dir, {
|
||||||
|
agent_id: "foo",
|
||||||
|
agent_name: "myagent",
|
||||||
|
});
|
||||||
|
expect(state.outputs.fleet_url.value).toBe(
|
||||||
|
"fleet://fleet.ssh/myagent.default.default.coder",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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/default.coder",
|
||||||
|
);
|
||||||
|
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
});
|
||||||
81
registry/coder/modules/jetbrains-fleet/main.tf
Normal file
81
registry/coder/modules/jetbrains-fleet/main.tf
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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 "agent_name" {
|
||||||
|
type = string
|
||||||
|
description = "The name of the agent"
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "JetBrains Fleet"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "coder_workspace" "me" {}
|
||||||
|
data "coder_workspace_owner" "me" {}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
workspace_name = lower(data.coder_workspace.me.name)
|
||||||
|
owner_name = lower(data.coder_workspace_owner.me.name)
|
||||||
|
agent_name = lower(var.agent_name)
|
||||||
|
hostname = var.agent_name != "" ? "${local.agent_name}.${local.workspace_name}.${local.owner_name}.coder" : "${local.workspace_name}.coder"
|
||||||
|
}
|
||||||
|
|
||||||
|
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/",
|
||||||
|
local.hostname,
|
||||||
|
var.folder != "" ? join("", ["?pwd=", var.folder]) : ""
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
output "fleet_url" {
|
||||||
|
value = coder_app.fleet.url
|
||||||
|
description = "Fleet IDE connection URL."
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user