Skip to content

Commit 22b6813

Browse files
authored
fix: handle workspaces that have two resources with same agent name (#243)
I ended up with a workspace that has two build resources that have the same agent. We ended up creating two environments with the same id (workspace name + agent name) This caused issues in Toolbox.
1 parent 0dc95a3 commit 22b6813

File tree

4 files changed

+414
-24
lines changed

4 files changed

+414
-24
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ dependencies {
6565
implementation(libs.retrofit.moshi)
6666
implementation(libs.bundles.bouncycastle)
6767
testImplementation(kotlin("test"))
68+
testImplementation(libs.coroutines.test)
6869
testImplementation(libs.mokk)
6970
testImplementation(libs.bundles.toolbox.plugin.api)
7071
}

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ toolbox-core-api = { module = "com.jetbrains.toolbox:core-api", version.ref = "t
2323
toolbox-ui-api = { module = "com.jetbrains.toolbox:ui-api", version.ref = "toolbox-plugin-api" }
2424
toolbox-remote-dev-api = { module = "com.jetbrains.toolbox:remote-dev-api", version.ref = "toolbox-plugin-api" }
2525
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
26+
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
2627
serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" }
2728
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
2829
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization" }

src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class CoderRemoteProvider(
6363
) : RemoteProvider("Coder") {
6464
// Current polling job.
6565
private var pollJob: Job? = null
66-
private val lastEnvironments = mutableSetOf<CoderRemoteEnvironment>()
66+
internal val lastEnvironments = mutableListOf<CoderRemoteEnvironment>()
6767

6868
private val settings = context.settingsStore.readOnly()
6969

@@ -116,29 +116,7 @@ class CoderRemoteProvider(
116116
while (isActive) {
117117
try {
118118
context.logger.debug("Fetching workspace agents from ${client.url}")
119-
val resolvedEnvironments = client.workspaces().flatMap { ws ->
120-
// Agents are not included in workspaces that are off
121-
// so fetch them separately.
122-
when (ws.latestBuild.status) {
123-
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
124-
else -> emptyList()
125-
}.ifEmpty {
126-
client.resources(ws)
127-
}.flatMap { resource ->
128-
resource.agents?.distinctBy {
129-
// There can be duplicates with coder_agent_instance.
130-
// TODO: Can we just choose one or do they hold
131-
// different information?
132-
it.name
133-
}?.map { agent ->
134-
lastEnvironments.firstOrNull { it.id == "${ws.name}.${agent.name}" }
135-
?.also {
136-
// If we have an environment already, update that.
137-
it.update(ws, agent)
138-
} ?: CoderRemoteEnvironment(context, client, cli, ws, agent)
139-
} ?: emptyList()
140-
}
141-
}.toSet().sortedBy { it.id }
119+
val resolvedEnvironments = resolveWorkspaceEnvironments(client, cli)
142120

143121
// In case we logged out while running the query.
144122
if (!isActive) {
@@ -202,6 +180,42 @@ class CoderRemoteProvider(
202180
}
203181
}
204182

183+
/**
184+
* Resolves workspace agents into remote environments.
185+
*
186+
* For each workspace:
187+
* - If running, uses agents from the latest build resources
188+
* - If not running, fetches resources separately
189+
*
190+
* @return a sorted list of resolved remote environments
191+
*/
192+
internal suspend fun resolveWorkspaceEnvironments(
193+
client: CoderRestClient,
194+
cli: CoderCLIManager,
195+
): List<CoderRemoteEnvironment> {
196+
return client.workspaces().flatMap { ws ->
197+
// Agents are not included in workspaces that are off
198+
// so fetch them separately.
199+
val resources = when (ws.latestBuild.status) {
200+
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
201+
else -> emptyList()
202+
}.ifEmpty {
203+
client.resources(ws)
204+
}
205+
resources
206+
.flatMap { it.agents ?: emptyList() }
207+
.distinctBy { it.name }
208+
.map { agent ->
209+
lastEnvironments.firstOrNull { it.id == "${ws.name}.${agent.name}" }
210+
?.also {
211+
// If we have an environment already, update that.
212+
it.update(ws, agent)
213+
} ?: CoderRemoteEnvironment(context, client, cli, ws, agent)
214+
}
215+
216+
}.sortedBy { it.id }
217+
}
218+
205219
/**
206220
* Stop polling, clear the client and environments, then go back to the
207221
* first page.

0 commit comments

Comments
 (0)