Skip to content

Commit 66e470f

Browse files
authored
fix: don't create new api keys each time we do workspace polling (#568)
* fix: don't create new api keys each time we do workspace polling The default behavior for `coder login --token <token>` is to: - use the provided token temporarily to authenticate the login process - generate a new session token and stores that for future CLI use - the original token provided is not stored or reused The Coder `Recent projects` view polls every 5 seconds for workspaces from multiple Coder deployment. The polling process also involves a call to the `cli.login`. The cli is later used start workspaces if user clicks on a project for which the workspace is stopped. Instead of generating a new token each time we login we can use the `coder login --use-token-as-session --token <token>` which: - uses the provided token directly as the session token - stores the original token for future CLI commands - no new token is generated * refactor: only login the cli when starting workspaces The Coder `Recent projects` view polls every 5 seconds for workspaces from multiple Coder deployment. The polling process also involves a call to the `cli.login`. The cli is later used to start workspaces if a user clicks on a project for which the workspace is stopped. The login can be called on demand, only when a "recent" project is stopped and the user wants to start it. This commit reduces a lot of overhead associated with spawning cli commands every 5 seconds. * chore: next version is 2.22.2
1 parent 35f4ef9 commit 66e470f

File tree

4 files changed

+69
-45
lines changed

4 files changed

+69
-45
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
## Unreleased
66

7+
### Fixed
8+
9+
- api keys are no longer created each time workspaces are polled
10+
711
## 2.22.1 - 2025-07-30
812

913
### Added

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pluginGroup=com.coder.gateway
55
artifactName=coder-gateway
66
pluginName=Coder
77
# SemVer format -> https://semver.org
8-
pluginVersion=2.22.1
8+
pluginVersion=2.22.2
99
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
# for insight into build numbers and IntelliJ Platform versions.
1111
pluginSinceBuild=243.26574

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ class CoderCLIManager(
288288
return exec(
289289
"login",
290290
deploymentURL.toString(),
291+
"--use-token-as-session",
291292
"--token",
292293
token,
293294
"--global-config",

src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package com.coder.gateway.views
55
import com.coder.gateway.CoderGatewayBundle
66
import com.coder.gateway.CoderGatewayConstants
77
import com.coder.gateway.CoderRemoteConnectionHandle
8-
import com.coder.gateway.cli.CoderCLIManager
98
import com.coder.gateway.cli.ensureCLI
109
import com.coder.gateway.icons.CoderIcons
1110
import com.coder.gateway.models.WorkspaceAgentListModel
@@ -75,8 +74,6 @@ data class DeploymentInfo(
7574
var items: List<WorkspaceAgentListModel>? = null,
7675
// Null if there have not been any errors yet.
7776
var error: String? = null,
78-
// Null if unable to ensure the CLI is downloaded.
79-
var cli: CoderCLIManager? = null,
8077
)
8178

8279
class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: (Component) -> Unit) :
@@ -178,13 +175,18 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
178175
val me = deployment?.client?.me?.username
179176
val workspaceWithAgent = deployment?.items?.firstOrNull {
180177
it.workspace.ownerName + "/" + it.workspace.name == workspaceName ||
181-
(it.workspace.ownerName == me && it.workspace.name == workspaceName)
178+
(it.workspace.ownerName == me && it.workspace.name == workspaceName)
182179
}
183180
val status =
184181
if (deploymentError != null) {
185182
Triple(UIUtil.getErrorForeground(), deploymentError, UIUtil.getBalloonErrorIcon())
186183
} else if (workspaceWithAgent != null) {
187-
val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent.workspace.latestBuild.status)
184+
val inLoadingState = listOf(
185+
WorkspaceStatus.STARTING,
186+
WorkspaceStatus.CANCELING,
187+
WorkspaceStatus.DELETING,
188+
WorkspaceStatus.STOPPING
189+
).contains(workspaceWithAgent.workspace.latestBuild.status)
188190

189191
Triple(
190192
workspaceWithAgent.status.statusColor(),
@@ -196,7 +198,11 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
196198
},
197199
)
198200
} else {
199-
Triple(UIUtil.getContextHelpForeground(), "Querying workspace status...", AnimatedIcon.Default())
201+
Triple(
202+
UIUtil.getContextHelpForeground(),
203+
"Querying workspace status...",
204+
AnimatedIcon.Default()
205+
)
200206
}
201207
val gap =
202208
if (top) {
@@ -216,7 +222,13 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
216222
label("").resizableColumn().align(AlignX.FILL)
217223
}.topGap(gap)
218224

219-
val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status)
225+
val enableLinks = listOf(
226+
WorkspaceStatus.STOPPED,
227+
WorkspaceStatus.CANCELED,
228+
WorkspaceStatus.FAILED,
229+
WorkspaceStatus.STARTING,
230+
WorkspaceStatus.RUNNING
231+
).contains(workspaceWithAgent?.workspace?.latestBuild?.status)
220232

221233
// We only display an API error on the first workspace rather than duplicating it on each workspace.
222234
if (deploymentError == null || showError) {
@@ -236,9 +248,29 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
236248
if (enableLinks) {
237249
cell(
238250
ActionLink(workspaceProjectIDE.projectPathDisplay) {
239-
withoutNull(deployment?.cli, workspaceWithAgent?.workspace) { cli, workspace ->
251+
withoutNull(
252+
deployment?.client,
253+
workspaceWithAgent?.workspace
254+
) { client, workspace ->
240255
CoderRemoteConnectionHandle().connect {
241-
if (listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED).contains(workspace.latestBuild.status)) {
256+
if (listOf(
257+
WorkspaceStatus.STOPPED,
258+
WorkspaceStatus.CANCELED,
259+
WorkspaceStatus.FAILED
260+
).contains(workspace.latestBuild.status)
261+
) {
262+
val cli = ensureCLI(
263+
deploymentURL.toURL(),
264+
client.buildInfo().version,
265+
settings,
266+
)
267+
// We only need to log the cli in if we have token-based auth.
268+
// Otherwise, we assume it is set up in the same way the plugin
269+
// is with mTLS.
270+
if (client.token != null) {
271+
cli.login(client.token)
272+
}
273+
242274
cli.startWorkspace(workspace.ownerName, workspace.name)
243275
}
244276
workspaceProjectIDE
@@ -289,33 +321,34 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
289321
* name, or just `workspace`, if the connection predates when we added owner
290322
* information, in which case it belongs to the current user.
291323
*/
292-
private fun getConnectionsByDeployment(filter: Boolean): Map<String, Map<String, List<WorkspaceProjectIDE>>> = recentConnectionsService.getAllRecentConnections()
293-
// Validate and parse connections.
294-
.mapNotNull {
295-
try {
296-
it.toWorkspaceProjectIDE()
297-
} catch (e: Exception) {
298-
logger.warn("Removing invalid recent connection $it", e)
299-
recentConnectionsService.removeConnection(it)
300-
null
324+
private fun getConnectionsByDeployment(filter: Boolean): Map<String, Map<String, List<WorkspaceProjectIDE>>> =
325+
recentConnectionsService.getAllRecentConnections()
326+
// Validate and parse connections.
327+
.mapNotNull {
328+
try {
329+
it.toWorkspaceProjectIDE()
330+
} catch (e: Exception) {
331+
logger.warn("Removing invalid recent connection $it", e)
332+
recentConnectionsService.removeConnection(it)
333+
null
334+
}
335+
}
336+
.filter { !filter || matchesFilter(it) }
337+
// Group by the deployment.
338+
.groupBy { it.deploymentURL.toString() }
339+
// Group the connections in each deployment by workspace.
340+
.mapValues { (_, connections) ->
341+
connections
342+
.groupBy { it.name.split(".", limit = 2).first() }
301343
}
302-
}
303-
.filter { !filter || matchesFilter(it) }
304-
// Group by the deployment.
305-
.groupBy { it.deploymentURL.toString() }
306-
// Group the connections in each deployment by workspace.
307-
.mapValues { (_, connections) ->
308-
connections
309-
.groupBy { it.name.split(".", limit = 2).first() }
310-
}
311344

312345
/**
313346
* Return true if the connection matches the current filter.
314347
*/
315348
private fun matchesFilter(connection: WorkspaceProjectIDE): Boolean = filterString.let {
316349
it.isNullOrBlank() ||
317-
connection.hostname.lowercase(Locale.getDefault()).contains(it) ||
318-
connection.projectPath.lowercase(Locale.getDefault()).contains(it)
350+
connection.hostname.lowercase(Locale.getDefault()).contains(it) ||
351+
connection.projectPath.lowercase(Locale.getDefault()).contains(it)
319352
}
320353

321354
/**
@@ -362,19 +395,6 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
362395
throw Exception("Unable to make request; token was not found in CLI config.")
363396
}
364397

365-
val cli = ensureCLI(
366-
deploymentURL.toURL(),
367-
client.buildInfo().version,
368-
settings,
369-
)
370-
371-
// We only need to log the cli in if we have token-based auth.
372-
// Otherwise, we assume it is set up in the same way the plugin
373-
// is with mTLS.
374-
if (client.token != null) {
375-
cli.login(client.token)
376-
}
377-
378398
// This is purely to populate the current user, which is
379399
// used to match workspaces that were not recorded with owner
380400
// information.
@@ -386,7 +406,7 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
386406
connectionsByWorkspace.forEach { (name, connections) ->
387407
if (items.firstOrNull {
388408
it.workspace.ownerName + "/" + it.workspace.name == name ||
389-
(it.workspace.ownerName == me && it.workspace.name == name)
409+
(it.workspace.ownerName == me && it.workspace.name == name)
390410
} == null
391411
) {
392412
logger.info("Removing recent connections for deleted workspace $name (found ${connections.size})")
@@ -395,7 +415,6 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
395415
}
396416

397417
deployment.client = client
398-
deployment.cli = cli
399418
deployment.items = items
400419
deployment.error = null
401420
} catch (e: Exception) {

0 commit comments

Comments
 (0)