Skip to content

Commit d2a3649

Browse files
authored
Check the agent's status (#232)
* Simplify workspace status check Looks like there is already a property that is the composite of the two fields we were checking. * Simplify agent model list We can just check if the list is empty then add the workspace-only "agent" model rather than duplicate the block. * Add lifecycle_state and login_before_ready to agent response * Check agent status Previously we only checked the workspace, now we also check the agent. Should help ensure we only connect when the connection will succeed. * Remove unused expressions * Add tooltips to describe the status * Simplify some borders
1 parent 68952f9 commit d2a3649

File tree

6 files changed

+199
-156
lines changed

6 files changed

+199
-156
lines changed

src/main/kotlin/com/coder/gateway/models/WorkspaceAgentModel.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.coder.gateway.models
22

33
import com.coder.gateway.sdk.Arch
44
import com.coder.gateway.sdk.OS
5+
import com.coder.gateway.sdk.v2.models.WorkspaceStatus
56
import com.coder.gateway.sdk.v2.models.WorkspaceTransition
67
import java.util.UUID
78
import javax.swing.Icon
@@ -15,11 +16,12 @@ data class WorkspaceAgentModel(
1516
val templateIconPath: String,
1617
var templateIcon: Icon?,
1718
val status: WorkspaceVersionStatus,
18-
val agentStatus: WorkspaceAgentStatus,
19+
val workspaceStatus: WorkspaceStatus,
20+
val agentStatus: WorkspaceAndAgentStatus,
1921
val lastBuildTransition: WorkspaceTransition,
2022
val agentOS: OS?,
2123
val agentArch: Arch?,
22-
val homeDirectory: String?
24+
val homeDirectory: String?,
2325
) {
2426
override fun equals(other: Any?): Boolean {
2527
if (this === other) return true

src/main/kotlin/com/coder/gateway/models/WorkspaceAgentStatus.kt

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.coder.gateway.models
2+
3+
import com.coder.gateway.sdk.v2.models.Workspace
4+
import com.coder.gateway.sdk.v2.models.WorkspaceAgent
5+
import com.coder.gateway.sdk.v2.models.WorkspaceAgentLifecycleState
6+
import com.coder.gateway.sdk.v2.models.WorkspaceAgentStatus
7+
import com.coder.gateway.sdk.v2.models.WorkspaceStatus
8+
import com.intellij.ui.JBColor
9+
10+
/**
11+
* WorkspaceAndAgentStatus represents the combined status of a single agent and
12+
* its workspace (or just the workspace if there are no agents).
13+
*/
14+
enum class WorkspaceAndAgentStatus(val label: String, val description: String) {
15+
// Workspace states.
16+
QUEUED("◍ Queued", "The workspace is queueing to start."),
17+
STARTING("⦿ Starting", "The workspace is starting."),
18+
FAILED("ⓧ Failed", "The workspace has failed to start."),
19+
DELETING("⦸ Deleting", "The workspace is being deleted."),
20+
DELETED("⦸ Deleted", "The workspace has been deleted."),
21+
STOPPING("◍ Stopping", "The workspace is stopping."),
22+
STOPPED("◍ Stopped", "The workspace has stopped."),
23+
CANCELING("◍ Canceling action", "The workspace is being canceled."),
24+
CANCELED("◍ Canceled action", "The workspace has been canceled."),
25+
RUNNING("⦿ Running", "The workspace is running, waiting for agents."),
26+
27+
// Agent states.
28+
CONNECTING("⦿ Connecting", "The agent is connecting."),
29+
DISCONNECTED("⦸ Disconnected", "The agent has disconnected."),
30+
TIMEOUT("ⓧ Timeout", "The agent is taking longer than expected to connect."),
31+
AGENT_STARTING("⦿ Starting", "The startup script is running."),
32+
AGENT_STARTING_READY("⦿ Starting", "The startup script is still running but the agent is ready to accept connections."),
33+
CREATED("⦿ Created", "The agent has been created."),
34+
START_ERROR("◍ Started with error", "The agent is ready but the startup script errored."),
35+
START_TIMEOUT("◍ Starting", "The startup script is taking longer than expected."),
36+
START_TIMEOUT_READY("◍ Starting", "The startup script is taking longer than expected but the agent is ready to accept connections."),
37+
SHUTTING_DOWN("◍ Shutting down", "The agent is shutting down."),
38+
SHUTDOWN_ERROR("⦸ Shutdown with error", "The agent shut down but the shutdown script errored."),
39+
SHUTDOWN_TIMEOUT("⦸ Shutting down", "The shutdown script is taking longer than expected."),
40+
OFF("⦸ Off", "The agent has shut down."),
41+
READY("⦿ Ready", "The agent is ready to accept connections.");
42+
43+
fun statusColor(): JBColor = when (this) {
44+
READY, AGENT_STARTING_READY, START_TIMEOUT_READY -> JBColor.GREEN
45+
START_ERROR, START_TIMEOUT, SHUTDOWN_TIMEOUT -> JBColor.YELLOW
46+
FAILED, DISCONNECTED, TIMEOUT, SHUTDOWN_ERROR -> JBColor.RED
47+
else -> if (JBColor.isBright()) JBColor.LIGHT_GRAY else JBColor.DARK_GRAY
48+
}
49+
50+
/**
51+
* Return true if the agent is in a connectable state.
52+
*/
53+
fun ready(): Boolean {
54+
return listOf(READY, START_ERROR, AGENT_STARTING_READY, START_TIMEOUT_READY)
55+
.contains(this)
56+
}
57+
58+
// We want to check that the workspace is `running`, the agent is
59+
// `connected`, and the agent lifecycle state is `ready` to ensure the best
60+
// possible scenario for attempting a connection.
61+
//
62+
// We can also choose to allow `start_error` for the agent lifecycle state;
63+
// this means the startup script did not successfully complete but the agent
64+
// will still accept SSH connections.
65+
//
66+
// Lastly we can also allow connections when the agent lifecycle state is
67+
// `starting` or `start_timeout` if `login_before_ready` is true on the
68+
// workspace response since this bypasses the need to wait for the script.
69+
//
70+
// Note that latest_build.status is derived from latest_build.job.status and
71+
// latest_build.job.transition so there is no need to check those.
72+
companion object {
73+
fun from(workspace: Workspace, agent: WorkspaceAgent? = null) = when (workspace.latestBuild.status) {
74+
WorkspaceStatus.PENDING -> QUEUED
75+
WorkspaceStatus.STARTING -> STARTING
76+
WorkspaceStatus.RUNNING -> when (agent?.status) {
77+
WorkspaceAgentStatus.CONNECTED -> when (agent.lifecycleState) {
78+
WorkspaceAgentLifecycleState.CREATED -> CREATED
79+
WorkspaceAgentLifecycleState.STARTING -> if (agent.loginBeforeReady == true) AGENT_STARTING_READY else AGENT_STARTING
80+
WorkspaceAgentLifecycleState.START_TIMEOUT -> if (agent.loginBeforeReady == true) START_TIMEOUT_READY else START_TIMEOUT
81+
WorkspaceAgentLifecycleState.START_ERROR -> START_ERROR
82+
WorkspaceAgentLifecycleState.READY -> READY
83+
WorkspaceAgentLifecycleState.SHUTTING_DOWN -> SHUTTING_DOWN
84+
WorkspaceAgentLifecycleState.SHUTDOWN_TIMEOUT -> SHUTDOWN_TIMEOUT
85+
WorkspaceAgentLifecycleState.SHUTDOWN_ERROR -> SHUTDOWN_ERROR
86+
WorkspaceAgentLifecycleState.OFF -> OFF
87+
}
88+
89+
WorkspaceAgentStatus.DISCONNECTED -> DISCONNECTED
90+
WorkspaceAgentStatus.TIMEOUT -> TIMEOUT
91+
WorkspaceAgentStatus.CONNECTING -> CONNECTING
92+
else -> RUNNING
93+
}
94+
95+
WorkspaceStatus.STOPPING -> STOPPING
96+
WorkspaceStatus.STOPPED -> STOPPED
97+
WorkspaceStatus.FAILED -> FAILED
98+
WorkspaceStatus.CANCELING -> CANCELING
99+
WorkspaceStatus.CANCELED -> CANCELED
100+
WorkspaceStatus.DELETING -> DELETING
101+
WorkspaceStatus.DELETED -> DELETED
102+
}
103+
104+
fun from(str: String) = WorkspaceAndAgentStatus.values().first { it.label.contains(str, true) }
105+
}
106+
}

src/main/kotlin/com/coder/gateway/sdk/v2/models/WorkspaceAgent.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,30 @@ data class WorkspaceAgent(
2626
@SerializedName("latency") val derpLatency: Map<String, DERPRegion>?,
2727
@SerializedName("connection_timeout_seconds") val connectionTimeoutSeconds: Int,
2828
@SerializedName("troubleshooting_url") val troubleshootingURL: String,
29+
@SerializedName("lifecycle_state") val lifecycleState: WorkspaceAgentLifecycleState,
30+
@SerializedName("login_before_ready") val loginBeforeReady: Boolean?,
2931
)
3032

3133
enum class WorkspaceAgentStatus {
32-
@SerializedName("connecting")
33-
CONNECTING,
34-
35-
@SerializedName("connected")
36-
CONNECTED,
37-
38-
@SerializedName("disconnected")
39-
DISCONNECTED,
34+
@SerializedName("connecting") CONNECTING,
35+
@SerializedName("connected") CONNECTED,
36+
@SerializedName("disconnected") DISCONNECTED,
37+
@SerializedName("timeout") TIMEOUT
38+
}
4039

41-
@SerializedName("timeout")
42-
TIMEOUT
40+
enum class WorkspaceAgentLifecycleState {
41+
@SerializedName("created") CREATED,
42+
@SerializedName("starting") STARTING,
43+
@SerializedName("start_timeout") START_TIMEOUT,
44+
@SerializedName("start_error") START_ERROR,
45+
@SerializedName("ready") READY,
46+
@SerializedName("shutting_down") SHUTTING_DOWN,
47+
@SerializedName("shutdown_timeout") SHUTDOWN_TIMEOUT,
48+
@SerializedName("shutdown_error") SHUTDOWN_ERROR,
49+
@SerializedName("off") OFF,
4350
}
4451

4552
data class DERPRegion(
4653
@SerializedName("preferred") val preferred: Boolean,
47-
@SerializedName("latency_ms") val latencyMillis: Double
54+
@SerializedName("latency_ms") val latencyMillis: Double,
4855
)

0 commit comments

Comments
 (0)