Skip to content

Commit 56ed322

Browse files
committed
impl: wait for workspace to be running
- starts and waits for the workspace to be running before showing the env page - improved error handling
1 parent 86532d4 commit 56ed322

File tree

3 files changed

+61
-20
lines changed

3 files changed

+61
-20
lines changed

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

+13
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ open class CoderRestClient(
169169
return workspacesResponse.body()!!.workspaces
170170
}
171171

172+
/**
173+
* Retrieves a workspace with the provided id.
174+
* @throws [APIResponseException].
175+
*/
176+
fun workspace(workspaceID: UUID): Workspace {
177+
val workspacesResponse = retroRestClient.workspace(workspaceID).execute()
178+
if (!workspacesResponse.isSuccessful) {
179+
throw APIResponseException("retrieve workspace", url, workspacesResponse)
180+
}
181+
182+
return workspacesResponse.body()!!
183+
}
184+
172185
/**
173186
* Retrieves all the agent names for all workspaces, including those that
174187
* are off. Meant to be used when configuring SSH.

src/main/kotlin/com/coder/toolbox/sdk/v2/CoderV2RestFacade.kt

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.coder.toolbox.sdk.v2.models.BuildInfo
44
import com.coder.toolbox.sdk.v2.models.CreateWorkspaceBuildRequest
55
import com.coder.toolbox.sdk.v2.models.Template
66
import com.coder.toolbox.sdk.v2.models.User
7+
import com.coder.toolbox.sdk.v2.models.Workspace
78
import com.coder.toolbox.sdk.v2.models.WorkspaceBuild
89
import com.coder.toolbox.sdk.v2.models.WorkspaceResource
910
import com.coder.toolbox.sdk.v2.models.WorkspacesResponse
@@ -30,6 +31,14 @@ interface CoderV2RestFacade {
3031
@Query("q") searchParams: String,
3132
): Call<WorkspacesResponse>
3233

34+
/**
35+
* Retrieves a workspace with the provided id.
36+
*/
37+
@GET("api/v2/workspaces/{workspaceID}")
38+
fun workspace(
39+
@Path("workspaceID") workspaceID: UUID
40+
): Call<Workspace>
41+
3342
@GET("api/v2/buildinfo")
3443
fun buildInfo(): Call<BuildInfo>
3544

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

+39-20
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ import com.coder.toolbox.sdk.v2.models.Workspace
1010
import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
1111
import com.coder.toolbox.sdk.v2.models.WorkspaceStatus
1212
import com.coder.toolbox.settings.CoderSettings
13+
import kotlinx.coroutines.TimeoutCancellationException
14+
import kotlinx.coroutines.delay
1315
import kotlinx.coroutines.flow.StateFlow
1416
import kotlinx.coroutines.flow.filter
1517
import kotlinx.coroutines.launch
18+
import kotlinx.coroutines.time.withTimeout
1619
import kotlinx.coroutines.yield
1720
import okhttp3.OkHttpClient
1821
import java.net.HttpURLConnection
1922
import java.net.URI
2023
import java.net.URL
24+
import kotlin.time.Duration.Companion.minutes
25+
import kotlin.time.Duration.Companion.seconds
26+
import kotlin.time.toJavaDuration
2127

2228
open class CoderProtocolHandler(
2329
private val context: CoderToolboxContext,
@@ -81,29 +87,27 @@ open class CoderProtocolHandler(
8187

8288
when (workspace.latestBuild.status) {
8389
WorkspaceStatus.PENDING, WorkspaceStatus.STARTING ->
84-
// TODO: Wait for the workspace to turn on.
85-
throw IllegalArgumentException(
86-
"The workspace \"$workspaceName\" is ${
87-
workspace.latestBuild.status.toString().lowercase()
88-
}; please wait then try again",
89-
)
90+
if (restClient.waitForReady(workspace) != true) {
91+
context.logger.error("$workspaceName from $deploymentURL could not be ready on time")
92+
context.ui.showErrorInfoPopup(MissingArgumentException("Can't handle URI because workspace $workspaceName could not be ready on time"))
93+
return
94+
}
9095

9196
WorkspaceStatus.STOPPING, WorkspaceStatus.STOPPED,
92-
WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED,
93-
->
94-
// TODO: Turn on the workspace.
95-
throw IllegalArgumentException(
96-
"The workspace \"$workspaceName\" is ${
97-
workspace.latestBuild.status.toString().lowercase()
98-
}; please start the workspace and try again",
99-
)
97+
WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED -> {
98+
restClient.startWorkspace(workspace)
99+
if (restClient.waitForReady(workspace) != true) {
100+
context.logger.error("$workspaceName from $deploymentURL could not be started on time")
101+
context.ui.showErrorInfoPopup(MissingArgumentException("Can't handle URI because workspace $workspaceName could not be started on time"))
102+
return
103+
}
104+
}
100105

101-
WorkspaceStatus.FAILED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED ->
102-
throw IllegalArgumentException(
103-
"The workspace \"$workspaceName\" is ${
104-
workspace.latestBuild.status.toString().lowercase()
105-
}; unable to connect",
106-
)
106+
WorkspaceStatus.FAILED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> {
107+
context.logger.error("Unable to connect to $workspaceName from $deploymentURL")
108+
context.ui.showErrorInfoPopup(MissingArgumentException("Can't handle URI because because we're unable to connect to workspace $workspaceName"))
109+
return
110+
}
107111

108112
WorkspaceStatus.RUNNING -> Unit // All is well
109113
}
@@ -157,6 +161,21 @@ open class CoderProtocolHandler(
157161
}
158162
}
159163

164+
private suspend fun CoderRestClient.waitForReady(workspace: Workspace): Boolean {
165+
var status = workspace.latestBuild.status
166+
try {
167+
withTimeout(2.minutes.toJavaDuration()) {
168+
while (status != WorkspaceStatus.RUNNING) {
169+
delay(1.seconds)
170+
status = this@waitForReady.workspace(workspace.id).latestBuild.status
171+
}
172+
}
173+
return true
174+
} catch (_: TimeoutCancellationException) {
175+
return false
176+
}
177+
}
178+
160179
private suspend fun askUrl(): String? {
161180
context.ui.showWindow()
162181
context.envPageManager.showPluginEnvironmentsPage(false)

0 commit comments

Comments
 (0)