Skip to content

Commit ccbb835

Browse files
authored
fix: hostname and proxy command generation (#95)
- latest Coder versions won't accept the SSH connections 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 For wildcard config: - the ssh config hostname follows the `coder-jetbrains-toolbox-dev.coder.com--*` pattern - the proxy command will have a similar host prefix - the hostname provided to Toolbox follows the `coder-jetbrains-toolbox-dev.coder.com--${ws.ownerName}--${ws.name}.${agent.name}` pattern For non wildcard config: - the ssh config hostname follows the `coder-jetbrains-toolbox--${ws.ownerName}--${ws.name}.${agent.name}--dev.coder.com` pattern - the proxy command will have the username and hostname in the ${ws.ownerName}/${ws.name}.${agent.name} format - the hostname provided to Toolbox follows the `coder-jetbrains-toolbox-dev.coder.com--${ws.ownerName}--${ws.name}.${agent.name}` pattern - resolves #94
1 parent d5fddf4 commit ccbb835

31 files changed

+318
-152
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.coder.toolbox
22

33
import com.coder.toolbox.browser.BrowserUtil
4+
import com.coder.toolbox.cli.CoderCLIManager
45
import com.coder.toolbox.models.WorkspaceAndAgentStatus
56
import com.coder.toolbox.sdk.CoderRestClient
67
import com.coder.toolbox.sdk.ex.APIResponseException
@@ -35,6 +36,7 @@ import kotlin.time.Duration.Companion.seconds
3536
class CoderRemoteEnvironment(
3637
private val context: CoderToolboxContext,
3738
private val client: CoderRestClient,
39+
private val cli: CoderCLIManager,
3840
private var workspace: Workspace,
3941
private var agent: WorkspaceAgent,
4042
) : RemoteProviderEnvironment("${workspace.name}.${agent.name}"), BeforeConnectionHook, AfterDisconnectHook {
@@ -48,6 +50,8 @@ class CoderRemoteEnvironment(
4850

4951
override val actionsList: MutableStateFlow<List<ActionDescription>> = MutableStateFlow(getAvailableActions())
5052

53+
fun asPairOfWorkspaceAndAgent(): Pair<Workspace, WorkspaceAgent> = Pair(workspace, agent)
54+
5155
private fun getAvailableActions(): List<ActionDescription> {
5256
val actions = mutableListOf(
5357
Action(context.i18n.ptrl("Open web terminal")) {
@@ -151,8 +155,8 @@ class CoderRemoteEnvironment(
151155
*/
152156
override suspend
153157
fun getContentsView(): EnvironmentContentsView = EnvironmentView(
154-
context.settingsStore.readOnly(),
155158
client.url,
159+
cli,
156160
workspace,
157161
agent
158162
)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class CoderRemoteProvider(
9191
it.name
9292
}?.map { agent ->
9393
// If we have an environment already, update that.
94-
val env = CoderRemoteEnvironment(context, client, ws, agent)
94+
val env = CoderRemoteEnvironment(context, client, cli, ws, agent)
9595
lastEnvironments.firstOrNull { it == env }?.let {
9696
it.update(ws, agent)
9797
it
@@ -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

Lines changed: 35 additions & 25 deletions
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),
@@ -506,25 +511,30 @@ class CoderCLIManager(
506511
}
507512
}
508513

514+
fun getHostname(url: URL, ws: Workspace, agent: WorkspaceAgent): String {
515+
return if (settings.isSshWildcardConfigEnabled && features.wildcardSsh) {
516+
"${getHostnamePrefix(url)}--${ws.ownerName}--${ws.name}.${agent.name}"
517+
} else {
518+
"coder-jetbrains-toolbox--${ws.ownerName}--${ws.name}.${agent.name}--${url.safeHost()}"
519+
}
520+
}
521+
522+
fun getBackgroundHostname(url: URL, ws: Workspace, agent: WorkspaceAgent): String {
523+
return "${getHostname(url, ws, agent)}--bg"
524+
}
525+
509526
companion object {
510527
private val tokenRegex = "--token [^ ]+".toRegex()
511528

512-
fun getHostnamePrefix(url: URL): String = "coder-jetbrains-toolbox-${url.safeHost()}"
529+
private fun getHostnamePrefix(url: URL): String = "coder-jetbrains-toolbox-${url.safeHost()}"
513530

514-
fun getWildcardHostname(url: URL, workspace: Workspace, agent: WorkspaceAgent): String =
515-
"${getHostnamePrefix(url)}-bg--${workspace.name}.${agent.name}"
531+
private fun getBackgroundHostnamePrefix(url: URL): String = "coder-jetbrains-toolbox-${url.safeHost()}-bg"
516532

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

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

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

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

Lines changed: 9 additions & 5 deletions
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
@@ -190,15 +192,17 @@ open class CoderRestClient(
190192
}
191193

192194
/**
193-
* Retrieves all the agent names for all workspaces, including those that
194-
* are off. Meant to be used when configuring SSH.
195+
* Maps the list of workspaces to the associated agents.
195196
*/
196-
suspend fun agentNames(workspaces: List<Workspace>): Set<String> {
197+
suspend fun groupByAgents(workspaces: List<Workspace>): Set<Pair<Workspace, WorkspaceAgent>> {
197198
// It is possible for there to be resources with duplicate names so we
198199
// need to use a set.
199200
return workspaces.flatMap { ws ->
200-
resources(ws).filter { it.agents != null }.flatMap { it.agents!! }.map {
201-
"${ws.name}.${it.name}"
201+
when (ws.latestBuild.status) {
202+
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
203+
else -> resources(ws)
204+
}.filter { it.agents != null }.flatMap { it.agents!! }.map {
205+
ws to it
202206
}
203207
}.toSet()
204208
}

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

Lines changed: 1 addition & 1 deletion
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.groupByAgents(workspaces))
166166

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

src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.coder.toolbox.views
33
import com.coder.toolbox.cli.CoderCLIManager
44
import com.coder.toolbox.sdk.v2.models.Workspace
55
import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
6-
import com.coder.toolbox.settings.ReadOnlyCoderSettings
76
import com.jetbrains.toolbox.api.remoteDev.environments.SshEnvironmentContentsView
87
import com.jetbrains.toolbox.api.remoteDev.ssh.SshConnectionInfo
98
import java.net.URL
@@ -17,16 +16,16 @@ import java.net.URL
1716
* SSH must be configured before this will work.
1817
*/
1918
class EnvironmentView(
20-
private val settings: ReadOnlyCoderSettings,
2119
private val url: URL,
20+
private val cli: CoderCLIManager,
2221
private val workspace: Workspace,
2322
private val agent: WorkspaceAgent,
2423
) : SshEnvironmentContentsView {
2524
override suspend fun getConnectionInfo(): SshConnectionInfo = object : SshConnectionInfo {
2625
/**
2726
* The host name generated by the cli manager for this workspace.
2827
*/
29-
override val host: String = resolveHost()
28+
override val host: String = cli.getHostname(url, workspace, agent)
3029

3130
/**
3231
* The port is ignored by the Coder proxy command.
@@ -36,12 +35,6 @@ class EnvironmentView(
3635
/**
3736
* The username is ignored by the Coder proxy command.
3837
*/
39-
override val userName: String? = "coder"
40-
38+
override val userName: String? = null
4139
}
42-
43-
private fun resolveHost(): String =
44-
if (settings.isSshWildcardConfigEnabled)
45-
CoderCLIManager.getWildcardHostname(url, workspace, agent)
46-
else CoderCLIManager.getHostname(url, workspace, agent)
47-
}
40+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy