Skip to content

Commit 0a00b0f

Browse files
committed
fix: include workspace owner name in the proxy command
- latest Coder versions won't accept the SSH connection if proxy command does not include the workspace owner name - for wildcard configuration the ssh config stays the same but the actual hostname provided to the Toolbox will include the workspace owner name - resolves #94
1 parent 8122bbe commit 0a00b0f

30 files changed

+306
-136
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
- connections to the workspace are no longer established automatically after agent started with error.
1212

13+
### Fixed
14+
15+
- SSH connection will no longer fail with newer Coder deployments due to misconfiguration of hostname and proxy command.
16+
1317
## 0.1.5 - 2025-04-14
1418

1519
### Fixed

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

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class CoderRemoteEnvironment(
4848

4949
override val actionsList: MutableStateFlow<List<ActionDescription>> = MutableStateFlow(getAvailableActions())
5050

51+
fun asPairOfWorkspaceAndAgent(): Pair<Workspace, WorkspaceAgent> = Pair(workspace, agent)
52+
5153
private fun getAvailableActions(): List<ActionDescription> {
5254
val actions = mutableListOf(
5355
Action(context.i18n.ptrl("Open web terminal")) {

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class CoderRemoteProvider(
109109
// Reconfigure if environments changed.
110110
if (lastEnvironments.size != resolvedEnvironments.size || lastEnvironments != resolvedEnvironments) {
111111
context.logger.info("Workspaces have changed, reconfiguring CLI: $resolvedEnvironments")
112-
cli.configSsh(resolvedEnvironments.map { it.name }.toSet())
112+
cli.configSsh(resolvedEnvironments.map { it.asPairOfWorkspaceAndAgent() }.toSet())
113113
}
114114

115115
environments.update {
@@ -149,7 +149,7 @@ class CoderRemoteProvider(
149149
triggerSshConfig.onReceive { shouldTrigger ->
150150
if (shouldTrigger) {
151151
context.logger.trace("workspace poller waked up because it should reconfigure the ssh configurations")
152-
cli.configSsh(lastEnvironments.map { it.name }.toSet())
152+
cli.configSsh(lastEnvironments.map { it.asPairOfWorkspaceAndAgent() }.toSet())
153153
}
154154
}
155155
}

src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt

+32-24
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,11 @@ class CoderCLIManager(
223223
* This can take supported features for testing purposes only.
224224
*/
225225
fun configSsh(
226-
workspaceNames: Set<String>,
226+
wsWithAgents: Set<Pair<Workspace, WorkspaceAgent>>,
227227
feats: Features = features,
228228
) {
229229
logger.info("Configuring SSH config at ${settings.sshConfigPath}")
230-
writeSSHConfig(modifySSHConfig(readSSHConfig(), workspaceNames, feats))
230+
writeSSHConfig(modifySSHConfig(readSSHConfig(), wsWithAgents, feats))
231231
}
232232

233233
/**
@@ -249,13 +249,13 @@ class CoderCLIManager(
249249
*/
250250
private fun modifySSHConfig(
251251
contents: String?,
252-
workspaceNames: Set<String>,
252+
wsWithAgents: Set<Pair<Workspace, WorkspaceAgent>>,
253253
feats: Features,
254254
): String? {
255255
val host = deploymentURL.safeHost()
256256
val startBlock = "# --- START CODER JETBRAINS TOOLBOX $host"
257257
val endBlock = "# --- END CODER JETBRAINS TOOLBOX $host"
258-
val isRemoving = workspaceNames.isEmpty()
258+
val isRemoving = wsWithAgents.isEmpty()
259259
val baseArgs =
260260
listOfNotNull(
261261
escape(localBinaryPath.toString()),
@@ -304,34 +304,39 @@ class CoderCLIManager(
304304
.plus("\n\n")
305305
.plus(
306306
"""
307-
Host ${getHostnamePrefix(deploymentURL)}-bg--*
307+
Host ${getBackgroundHostnamePrefix(deploymentURL)}--*
308308
ProxyCommand ${backgroundProxyArgs.joinToString(" ")} --ssh-host-prefix ${
309-
getHostnamePrefix(
309+
getBackgroundHostnamePrefix(
310310
deploymentURL
311311
)
312-
}-bg-- %h
312+
}-- %h
313313
""".trimIndent()
314314
.plus("\n" + options.prependIndent(" "))
315315
.plus(extraConfig),
316316
).replace("\n", System.lineSeparator()) +
317317
System.lineSeparator() + endBlock
318318
} else {
319-
workspaceNames.joinToString(
319+
wsWithAgents.joinToString(
320320
System.lineSeparator(),
321321
startBlock + System.lineSeparator(),
322322
System.lineSeparator() + endBlock,
323323
transform = {
324324
"""
325-
Host ${getHostName(deploymentURL, it)}
326-
ProxyCommand ${proxyArgs.joinToString(" ")} $it
325+
Host ${getHostname(deploymentURL, it.workspace(), it.agent())}
326+
ProxyCommand ${proxyArgs.joinToString(" ")} ${getWsByOwner(it.workspace(), it.agent())}
327327
""".trimIndent()
328328
.plus("\n" + options.prependIndent(" "))
329329
.plus(extraConfig)
330330
.plus("\n")
331331
.plus(
332332
"""
333-
Host ${getBackgroundHostName(deploymentURL, it)}
334-
ProxyCommand ${backgroundProxyArgs.joinToString(" ")} $it
333+
Host ${getBackgroundHostname(deploymentURL, it.workspace(), it.agent())}
334+
ProxyCommand ${backgroundProxyArgs.joinToString(" ")} ${
335+
getWsByOwner(
336+
it.workspace(),
337+
it.agent()
338+
)
339+
}
335340
""".trimIndent()
336341
.plus("\n" + options.prependIndent(" "))
337342
.plus(extraConfig),
@@ -511,20 +516,23 @@ class CoderCLIManager(
511516

512517
fun getHostnamePrefix(url: URL): String = "coder-jetbrains-toolbox-${url.safeHost()}"
513518

514-
fun getWildcardHostname(url: URL, workspace: Workspace, agent: WorkspaceAgent): String =
515-
"${getHostnamePrefix(url)}-bg--${workspace.name}.${agent.name}"
519+
fun getBackgroundHostnamePrefix(url: URL): String = "coder-jetbrains-toolbox-${url.safeHost()}-bg"
520+
521+
fun getWildcardHostname(url: URL, ws: Workspace, agent: WorkspaceAgent): String =
522+
"${getHostnamePrefix(url)}--${ws.ownerName}--${ws.name}.${agent.name}"
523+
524+
fun getHostname(url: URL, ws: Workspace, agent: WorkspaceAgent): String {
525+
return "coder-jetbrains-toolbox--${ws.ownerName}--${ws.name}.${agent.name}--${url.safeHost()}"
526+
}
527+
528+
fun getBackgroundHostname(url: URL, ws: Workspace, agent: WorkspaceAgent): String {
529+
return "${getHostname(url, ws, agent)}--bg"
530+
}
516531

517-
fun getHostname(url: URL, workspace: Workspace, agent: WorkspaceAgent) =
518-
getHostName(url, "${workspace.name}.${agent.name}")
532+
fun getWsByOwner(ws: Workspace, agent: WorkspaceAgent): String = "${ws.ownerName}/${ws.name}.${agent.name}"
519533

520-
fun getHostName(
521-
url: URL,
522-
workspaceName: String,
523-
): String = "coder-jetbrains-toolbox-$workspaceName--${url.safeHost()}"
534+
fun Pair<Workspace, WorkspaceAgent>.workspace() = this.first
524535

525-
fun getBackgroundHostName(
526-
url: URL,
527-
workspaceName: String,
528-
): String = getHostName(url, workspaceName) + "--bg"
536+
fun Pair<Workspace, WorkspaceAgent>.agent() = this.second
529537
}
530538
}

src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt

+8-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import com.coder.toolbox.sdk.v2.models.CreateWorkspaceBuildRequest
1313
import com.coder.toolbox.sdk.v2.models.Template
1414
import com.coder.toolbox.sdk.v2.models.User
1515
import com.coder.toolbox.sdk.v2.models.Workspace
16+
import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
1617
import com.coder.toolbox.sdk.v2.models.WorkspaceBuild
1718
import com.coder.toolbox.sdk.v2.models.WorkspaceResource
19+
import com.coder.toolbox.sdk.v2.models.WorkspaceStatus
1820
import com.coder.toolbox.sdk.v2.models.WorkspaceTransition
1921
import com.coder.toolbox.util.CoderHostnameVerifier
2022
import com.coder.toolbox.util.coderSocketFactory
@@ -193,12 +195,15 @@ open class CoderRestClient(
193195
* Retrieves all the agent names for all workspaces, including those that
194196
* are off. Meant to be used when configuring SSH.
195197
*/
196-
suspend fun agentNames(workspaces: List<Workspace>): Set<String> {
198+
suspend fun withAgents(workspaces: List<Workspace>): Set<Pair<Workspace, WorkspaceAgent>> {
197199
// It is possible for there to be resources with duplicate names so we
198200
// need to use a set.
199201
return workspaces.flatMap { ws ->
200-
resources(ws).filter { it.agents != null }.flatMap { it.agents!! }.map {
201-
"${ws.name}.${it.name}"
202+
when (ws.latestBuild.status) {
203+
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
204+
else -> resources(ws)
205+
}.filter { it.agents != null }.flatMap { it.agents!! }.map {
206+
ws to it
202207
}
203208
}.toSet()
204209
}

src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ open class CoderProtocolHandler(
162162
}
163163

164164
context.logger.info("Configuring Coder CLI...")
165-
cli.configSsh(restClient.agentNames(workspaces))
165+
cli.configSsh(restClient.withAgents(workspaces))
166166

167167
if (shouldWaitForAutoLogin) {
168168
isInitialized.waitForTrue()

0 commit comments

Comments
 (0)