Skip to content

Commit e69ffc0

Browse files
committed
Limit list item model usage within list
Everywhere else we use the workspace and agent models as-is. Also the list model itself is refactored so it contains just the two models plus extra properties for the column values. I think overall this is more straightforward.
1 parent 8875bdd commit e69ffc0

14 files changed

+280
-381
lines changed

src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
package com.coder.gateway
44

5-
import com.coder.gateway.models.TokenSource
6-
import com.coder.gateway.models.WorkspaceAgentModel
75
import com.coder.gateway.cli.CoderCLIManager
6+
import com.coder.gateway.cli.ensureCLI
7+
import com.coder.gateway.models.TokenSource
8+
import com.coder.gateway.models.WorkspaceAndAgentStatus
89
import com.coder.gateway.sdk.BaseCoderRestClient
910
import com.coder.gateway.sdk.CoderRestClient
10-
import com.coder.gateway.cli.ensureCLI
1111
import com.coder.gateway.sdk.ex.AuthenticationResponseException
12-
import com.coder.gateway.util.toURL
1312
import com.coder.gateway.sdk.v2.models.Workspace
13+
import com.coder.gateway.sdk.v2.models.WorkspaceAgent
1414
import com.coder.gateway.sdk.v2.models.WorkspaceStatus
15-
import com.coder.gateway.sdk.v2.models.toAgentModels
1615
import com.coder.gateway.services.CoderSettingsService
16+
import com.coder.gateway.util.toURL
1717
import com.coder.gateway.util.withPath
1818
import com.intellij.openapi.components.service
1919
import com.intellij.openapi.diagnostic.Logger
@@ -73,12 +73,13 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
7373

7474
// TODO: Show a dropdown and ask for an agent if missing.
7575
val agent = getMatchingAgent(parameters, workspace)
76+
val status = WorkspaceAndAgentStatus.from(workspace, agent)
7677

77-
if (agent.agentStatus.pending()) {
78+
if (status.pending()) {
7879
// TODO: Wait for the agent to be ready.
79-
throw IllegalArgumentException("The agent \"${agent.name}\" is ${agent.agentStatus.toString().lowercase()}; please wait then try again")
80-
} else if (!agent.agentStatus.ready()) {
81-
throw IllegalArgumentException("The agent \"${agent.name}\" is ${agent.agentStatus.toString().lowercase()}; unable to connect")
80+
throw IllegalArgumentException("The agent \"${agent.name}\" is ${status.toString().lowercase()}; please wait then try again")
81+
} else if (!status.ready()) {
82+
throw IllegalArgumentException("The agent \"${agent.name}\" is ${status.toString().lowercase()}; unable to connect")
8283
}
8384

8485
val cli = ensureCLI(
@@ -92,7 +93,7 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
9293
cli.login(client.token)
9394

9495
indicator.text = "Configuring Coder CLI..."
95-
cli.configSsh(client.agents(workspaces).map { it.name })
96+
cli.configSsh(client.agentNames())
9697

9798
// TODO: Ask for these if missing. Maybe we can reuse the second
9899
// step of the wizard? Could also be nice if we automatically used
@@ -194,49 +195,42 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
194195

195196
companion object {
196197
val logger = Logger.getInstance(CoderGatewayConnectionProvider::class.java.simpleName)
198+
}
199+
}
197200

198-
/**
199-
* Return the agent matching the provided agent ID or name in the
200-
* parameters. The name is ignored if the ID is set. If neither was
201-
* supplied and the workspace has only one agent, return that.
202-
* Otherwise throw an error.
203-
*
204-
* @throws [MissingArgumentException, IllegalArgumentException]
205-
*/
206-
@JvmStatic
207-
fun getMatchingAgent(parameters: Map<String, String?>, workspace: Workspace): WorkspaceAgentModel {
208-
// A WorkspaceAgentModel will still be returned if there are no
209-
// agents; in this case it represents the workspace instead.
210-
// TODO: Seems confusing for something with "agent" in the name to
211-
// potentially not actually be an agent; can we replace
212-
// WorkspaceAgentModel with the original structs from the API?
213-
val agents = workspace.toAgentModels()
214-
if (agents.isEmpty() || (agents.size == 1 && agents.first().agentID == null)) {
215-
throw IllegalArgumentException("The workspace \"${workspace.name}\" has no agents")
216-
}
217-
218-
// If the agent is missing and the workspace has only one, use that.
219-
// Prefer the ID over the name if both are set.
220-
val agent = if (!parameters[AGENT_ID].isNullOrBlank())
221-
agents.firstOrNull { it.agentID.toString() == parameters[AGENT_ID] }
222-
else if (!parameters[AGENT_NAME].isNullOrBlank())
223-
agents.firstOrNull { it.name == "${workspace.name}.${parameters[AGENT_NAME]}"}
224-
else if (agents.size == 1) agents.first()
225-
else null
226-
227-
if (agent == null) {
228-
if (!parameters[AGENT_ID].isNullOrBlank()) {
229-
throw IllegalArgumentException("The workspace \"${workspace.name}\" does not have an agent with ID \"${parameters[AGENT_ID]}\"")
230-
} else if (!parameters[AGENT_NAME].isNullOrBlank()){
231-
throw IllegalArgumentException("The workspace \"${workspace.name}\"does not have an agent named \"${parameters[AGENT_NAME]}\"")
232-
} else {
233-
throw MissingArgumentException("Unable to determine which agent to connect to; one of \"$AGENT_NAME\" or \"$AGENT_ID\" must be set because the workspace \"${workspace.name}\" has more than one agent")
234-
}
235-
}
201+
/**
202+
* Return the agent matching the provided agent ID or name in the parameters.
203+
* The name is ignored if the ID is set. If neither was supplied and the
204+
* workspace has only one agent, return that. Otherwise throw an error.
205+
*
206+
* @throws [MissingArgumentException, IllegalArgumentException]
207+
*/
208+
fun getMatchingAgent(parameters: Map<String, String?>, workspace: Workspace): WorkspaceAgent {
209+
val agents = workspace.latestBuild.resources.filter { it.agents != null }.flatMap { it.agents!! }
210+
if (agents.isEmpty()) {
211+
throw IllegalArgumentException("The workspace \"${workspace.name}\" has no agents")
212+
}
236213

237-
return agent
214+
// If the agent is missing and the workspace has only one, use that.
215+
// Prefer the ID over the name if both are set.
216+
val agent = if (!parameters[AGENT_ID].isNullOrBlank())
217+
agents.firstOrNull { it.id.toString() == parameters[AGENT_ID] }
218+
else if (!parameters[AGENT_NAME].isNullOrBlank())
219+
agents.firstOrNull { it.name == parameters[AGENT_NAME]}
220+
else if (agents.size == 1) agents.first()
221+
else null
222+
223+
if (agent == null) {
224+
if (!parameters[AGENT_ID].isNullOrBlank()) {
225+
throw IllegalArgumentException("The workspace \"${workspace.name}\" does not have an agent with ID \"${parameters[AGENT_ID]}\"")
226+
} else if (!parameters[AGENT_NAME].isNullOrBlank()){
227+
throw IllegalArgumentException("The workspace \"${workspace.name}\"does not have an agent named \"${parameters[AGENT_NAME]}\"")
228+
} else {
229+
throw MissingArgumentException("Unable to determine which agent to connect to; one of \"$AGENT_NAME\" or \"$AGENT_ID\" must be set because the workspace \"${workspace.name}\" has more than one agent")
238230
}
239231
}
232+
233+
return agent
240234
}
241235

242236
class MissingArgumentException(message: String) : IllegalArgumentException(message)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ enum class TokenSource {
1010
data class CoderWorkspacesWizardModel(
1111
var coderURL: String = "https://coder.example.com",
1212
var token: Pair<String, TokenSource>? = null,
13-
var selectedWorkspace: WorkspaceAgentModel? = null,
13+
var selectedListItem: WorkspaceAgentListModel? = null,
1414
var useExistingToken: Boolean = false,
1515
var configDirectory: String = "",
1616
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 javax.swing.Icon
6+
7+
// This represents a single row in the flattened agent list. It is either an
8+
// agent with its associated workspace or a workspace with no agents, in which
9+
// case it acts as a placeholder for performing actions on the workspace but
10+
// cannot be connected to.
11+
data class WorkspaceAgentListModel(
12+
val workspace: Workspace,
13+
14+
// If this is missing, assume the workspace is off or has no agents.
15+
val agent: WorkspaceAgent? = null,
16+
17+
// The icon to display on the row.
18+
var icon: Icon? = null,
19+
20+
// The combined status of the workspace and agent to display on the row.
21+
val status: WorkspaceAndAgentStatus = WorkspaceAndAgentStatus.from(workspace, agent),
22+
23+
// The combined `workspace.agent` name to display on the row.
24+
val name: String = if (agent != null) "${workspace.name}.${agent.name}" else workspace.name
25+
)

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

Lines changed: 0 additions & 58 deletions
This file was deleted.

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

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/main/kotlin/com/coder/gateway/sdk/BaseCoderRestClient.kt

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

33
import com.coder.gateway.icons.CoderIcons
44
import com.coder.gateway.icons.toRetinaAwareIcon
5-
import com.coder.gateway.models.WorkspaceAgentModel
65
import com.coder.gateway.sdk.convertors.ArchConverter
76
import com.coder.gateway.sdk.convertors.InstantConverter
87
import com.coder.gateway.sdk.convertors.OSConverter
@@ -18,7 +17,6 @@ import com.coder.gateway.sdk.v2.models.Workspace
1817
import com.coder.gateway.sdk.v2.models.WorkspaceBuild
1918
import com.coder.gateway.sdk.v2.models.WorkspaceResource
2019
import com.coder.gateway.sdk.v2.models.WorkspaceTransition
21-
import com.coder.gateway.sdk.v2.models.toAgentModels
2220
import com.coder.gateway.services.CoderSettingsState
2321
import com.coder.gateway.settings.CoderSettings
2422
import com.coder.gateway.util.Arch
@@ -154,13 +152,14 @@ open class BaseCoderRestClient(
154152
}
155153

156154
/**
157-
* Retrieves agents for the specified workspaces, including those that are
158-
* off.
155+
* Retrieves all the agent names for all workspaces, including those that
156+
* are off. Meant to be used when configuring SSH.
159157
*/
160-
fun agents(workspaces: List<Workspace>): List<WorkspaceAgentModel> {
161-
return workspaces.flatMap {
162-
val resources = resources(it)
163-
it.toAgentModels(resources)
158+
fun agentNames(): List<String> {
159+
return workspaces().flatMap { ws ->
160+
resources(ws).filter { it.agents != null }.flatMap { it.agents!! }.map {
161+
"${ws.name}.${it.name}"
162+
}
164163
}
165164
}
166165

@@ -194,27 +193,27 @@ open class BaseCoderRestClient(
194193
return templateResponse.body()!!
195194
}
196195

197-
fun startWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild {
196+
fun startWorkspace(workspace: Workspace): WorkspaceBuild {
198197
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.START, null, null, null, null)
199-
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
198+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
200199
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
201-
throw WorkspaceResponseException(error("start workspace $workspaceName", buildResponse))
200+
throw WorkspaceResponseException(error("start workspace ${workspace.name}", buildResponse))
202201
}
203202

204203
return buildResponse.body()!!
205204
}
206205

207-
fun stopWorkspace(workspaceID: UUID, workspaceName: String): WorkspaceBuild {
206+
fun stopWorkspace(workspace: Workspace): WorkspaceBuild {
208207
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.STOP, null, null, null, null)
209-
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
208+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
210209
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
211-
throw WorkspaceResponseException(error("stop workspace $workspaceName", buildResponse))
210+
throw WorkspaceResponseException(error("stop workspace ${workspace.name}", buildResponse))
212211
}
213212

214213
return buildResponse.body()!!
215214
}
216215

217-
fun updateWorkspace(workspaceID: UUID, workspaceName: String, lastWorkspaceTransition: WorkspaceTransition, templateID: UUID): WorkspaceBuild {
216+
fun updateWorkspace(workspace: Workspace): WorkspaceBuild {
218217
// Best practice is to STOP a workspace before doing an update if it is
219218
// started.
220219
// 1. If the update changes parameters, the old template might be needed
@@ -223,17 +222,17 @@ open class BaseCoderRestClient(
223222
// template authors are not diligent about making sure the agent gets
224223
// restarted with this information when we do two START builds in a
225224
// row.
226-
if (lastWorkspaceTransition == WorkspaceTransition.START) {
227-
stopWorkspace(workspaceID, workspaceName)
225+
if (workspace.latestBuild.transition == WorkspaceTransition.START) {
226+
stopWorkspace(workspace)
228227
}
229228

230-
val template = template(templateID)
229+
val template = template(workspace.templateID)
231230

232231
val buildRequest =
233232
CreateWorkspaceBuildRequest(template.activeVersionID, WorkspaceTransition.START, null, null, null, null)
234-
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
233+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
235234
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
236-
throw WorkspaceResponseException(error("update workspace $workspaceName", buildResponse))
235+
throw WorkspaceResponseException(error("update workspace ${workspace.name}", buildResponse))
237236
}
238237

239238
return buildResponse.body()!!

0 commit comments

Comments
 (0)