Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
### Added

- include a Coder support bundle for the selected workspace and agent only when using the environment's _Collect logs_
action in Toolbox
action in Toolbox; requires Coder server and CLI 2.10.0 or newer. Toolbox logs and eligible local SSH logs remain
available when a Coder support bundle cannot be generated
- include active and recent local CLI SSH logs in workspace log collection. For CLI 2.4.0 and newer the plugin now
passes an explicit `--log-dir` to the SSH ProxyCommand (the configured directory, `CODER_SSH_LOG_DIR`, or
`ssh-logs/` in the deployment's plugin data directory) and collects and cleans up logs from that same directory
- add a flight recorder that keeps recent plugin messages below Toolbox's log level in memory for connection diagnostics

### Changed

Expand All @@ -18,8 +20,7 @@
- remove stored credentials on explicit logout, and remove old plaintext session files after successful keyring login
- bound credential and header commands to 60 seconds while preserving unbounded workspace starts and complete progress
lines

- upgraded the Toolbox plugin API, dropping support for Toolbox versions older than 3.7.2
- upgraded the Toolbox plugin API, dropping support for Toolbox versions older than 3.9.0
- display live workspace start output from the Coder CLI and poll build status and provisioner output while updating,
restarting, or stopping workspaces

Expand Down
170 changes: 155 additions & 15 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
toolbox-plugin-api = "1.13.87111"
toolbox-plugin-api = "1.15.88839"
kotlin = "2.3.10"
coroutines = "1.10.2"
serialization = "1.9.0"
Expand Down
12 changes: 11 additions & 1 deletion src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ class CoderRemoteEnvironment(

private fun refreshAvailableActions() {
val actions = mutableListOf<ActionDescription>()
context.logger.debug("Refreshing available actions for workspace $id with status: $environmentStatus")
if (environmentStatus.canStop() && agent != null) {
actions.add(Action(context, "Open web terminal") {
context.logger.debug("Launching web terminal for $id...")
Expand Down Expand Up @@ -240,6 +239,7 @@ class CoderRemoteEnvironment(
Action(context, "Update and restart") {
withProgress("Updating and restarting workspace…") {
context.logger.debug(currentSessionId(), "Updating and re-starting $id...")
context.logger.flush("workspace_restarting $id", currentSessionId())
val build = client.updateWorkspace(workspace)
applyWorkspaceSnapshot(workspace.copy(latestBuild = build), agent)
workspaceRefreshTrigger.trySend(true)
Expand All @@ -252,6 +252,7 @@ class CoderRemoteEnvironment(
withProgress("Stopping workspace…") {
tryStopSshConnection()
context.logger.debug(currentSessionId(), "Stopping $id...")
context.logger.flush("workspace_stopping $id", currentSessionId())
val build = client.stopWorkspace(workspace)
applyWorkspaceSnapshot(workspace.copy(latestBuild = build), agent)
workspaceRefreshTrigger.trySend(true)
Expand Down Expand Up @@ -279,6 +280,7 @@ class CoderRemoteEnvironment(
if (confirmation == workspace.name) {
withProgress("Deleting workspace…") {
context.logger.debug(currentSessionId(), "Deleting $id...")
context.logger.flush("workspace_deleting $id", currentSessionId())
deleteWorkspace()
}
}
Expand All @@ -301,6 +303,7 @@ class CoderRemoteEnvironment(
"The SSH connection to workspace $name could not be dropped in time, " +
"going to stop the workspace while the SSH connection is live"
context.logger.warn(currentSessionId(), message)
context.logger.flush("ssh_disconnect_timeout $id", currentSessionId())
}
}
}
Expand Down Expand Up @@ -386,6 +389,7 @@ class CoderRemoteEnvironment(
}
removedSessionId?.let { sessionId ->
context.logger.info(sessionId, "Removed Toolbox SSH session for $id")
context.logger.flush("ssh_session_removed $id", sessionId)
}
}

Expand All @@ -412,6 +416,7 @@ class CoderRemoteEnvironment(
"Toolbox is disconnecting SSH from $id $disconnectKind.$stateInference " +
"Latest known Coder state: $latestCoderState",
)
context.logger.flush("ssh_disconnected $id", sessionId)
context.logger.info(sessionId, "Stopping the network metrics poll job for $id")
pollJob?.cancel()
pollJob = null
Expand All @@ -425,6 +430,7 @@ class CoderRemoteEnvironment(
SessionIdRegistry.removeSession(workspace.name, it.name)
}?.let {
context.logger.info(it, "Removed Toolbox SSH session for $id after manual disconnect")
context.logger.flush("ssh_session_removed_after_manual_disconnect $id", it)
}
}
}
Expand Down Expand Up @@ -479,6 +485,7 @@ class CoderRemoteEnvironment(
val previousEnvironmentStatus = environmentStatus
val previousWorkspace = workspace
val previousAgent = agent
val wasReachable = state.value.isReachable
val sessionId = currentSessionId()

environmentStatus = newState
Expand All @@ -499,6 +506,9 @@ class CoderRemoteEnvironment(
"agent lifecycle state: ${previousAgent?.lifecycleState} -> ${agent?.lifecycleState}, " +
"login before ready: ${previousAgent?.loginBeforeReady} -> ${agent?.loginBeforeReady}"
context.logger.info(sessionId, message)
if (wasReachable && !state.value.isReachable) {
context.logger.flush("environment_status_changed $id", sessionId)
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class CoderRemoteProvider(
"Your Coder session has expired. Please re-authenticate and try again.",
ex
)
context.logger.flush("coder_session_expired")
break
}
context.logger.error(sessionIds, ex, "workspace polling error encountered")
Expand Down Expand Up @@ -359,6 +360,7 @@ class CoderRemoteProvider(
val activeClient = client
val activeCli = cli
context.logger.info(sessionIds, "Logging out ${activeClient?.me?.username}...")
context.logger.flush("logout")
try {
pollJob?.cancelAndJoin()
activeClient?.close()
Expand All @@ -376,6 +378,7 @@ class CoderRemoteProvider(
"Sign in and try logging out again to remove it.",
ex,
)
context.logger.flush("cli_logout_failed")
} finally {
close()
}
Expand Down Expand Up @@ -422,6 +425,7 @@ class CoderRemoteProvider(
accountDropdownField.visibility.update { false }
router.clear()
context.logger.info(sessionIds, "Coder plugin is now closed")
context.logger.flush("provider_closed")
}

private fun softClose() {
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ data class CoderToolboxContext(
val secrets: CoderSecretsStore,
val proxySettings: ToolboxProxySettings,
) {
val logger: CoderLogger = CoderLogger(underlyingLogger, ui, cs, i18n)
val logger: CoderLogger =
underlyingLogger as? CoderLogger ?: CoderLogger(underlyingLogger, ui, cs, i18n, settingsStore.readOnly())

val connectionMonitoringService: ConnectionMonitoringService = ConnectionMonitoringService(this)

/**
Expand Down
12 changes: 8 additions & 4 deletions src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.coder.toolbox

import com.coder.toolbox.diagnostics.CoderLogger
import com.coder.toolbox.settings.Environment
import com.coder.toolbox.store.CoderSecretsStore
import com.coder.toolbox.store.CoderSettingsStore
Expand Down Expand Up @@ -28,9 +29,12 @@ class CoderToolboxExtension : RemoteDevExtension {
// All services must be passed in here and threaded as necessary.
override fun createRemoteProviderPluginInstance(serviceLocator: ServiceLocator): RemoteProvider {
val ui = serviceLocator.getService<ToolboxUi>()
val logger = serviceLocator.getService(Logger::class.java)
val cs = serviceLocator.getService<CoroutineScope>()
val i18n = serviceLocator.getService<LocalizableStringFactory>()
val delegate = serviceLocator.getService<Logger>()
val settingsStore =
CoderSettingsStore(serviceLocator.getService<PluginSettingsStore>(), Environment(), delegate)
val logger = CoderLogger(delegate, ui, cs, i18n, settingsStore.readOnly())
return CoderRemoteProvider(
CoderToolboxContext(
ui,
Expand All @@ -41,12 +45,12 @@ class CoderToolboxExtension : RemoteDevExtension {
serviceLocator.getService<ClientHelper>(),
serviceLocator.getService<LocalDesktopManager>(),
cs,
serviceLocator.getService<Logger>(),
logger,
i18n,
CoderSettingsStore(serviceLocator.getService<PluginSettingsStore>(), Environment(), logger),
settingsStore,
CoderSecretsStore(serviceLocator.getService<PluginSecretStore>()),
serviceLocator.getService<ToolboxProxySettings>(),
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ class SshCommandProcessHandle(private val ctx: CoderToolboxContext) {
while (stack.isNotEmpty()) {
val processHandle = stack.removeLast()
val cmdLine = processHandle.info().commandLine().getOrNull()
ctx.logger.debug(sessionId, "SSH command PID: ${processHandle.pid()} Command: $cmdLine")
if (cmdLine != null && cmdLine.isSshCommandFor(ws, agent)) {
ctx.logger.debug(
sessionId,
"SSH command with PID: ${processHandle.pid()} and Command: $cmdLine matches ${ws.name}.${agent.name}"
"SSH command with PID: ${processHandle.pid()} matches ${ws.name}.${agent.name}"
)
return processHandle.pid()
} else {
Expand Down
Loading
Loading