Skip to content

Commit 8e5b9e7

Browse files
committed
Create deploy inputs class
This is to replace the map. Provides more type safety, and gives us a place to do some additional deploy steps in a future commit. Additionally, make connections with same workspace, project, and IDE match, since if there are two for the same IDE but one has a download link and one has a path we should combine them, for example.
1 parent 0baac21 commit 8e5b9e7

15 files changed

+322
-275
lines changed

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

+54-46
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,25 @@ package com.coder.gateway
44

55
import com.coder.gateway.cli.CoderCLIManager
66
import com.coder.gateway.cli.ensureCLI
7+
import com.coder.gateway.models.AGENT_ID
8+
import com.coder.gateway.models.AGENT_NAME
9+
import com.coder.gateway.models.TOKEN
710
import com.coder.gateway.models.TokenSource
11+
import com.coder.gateway.models.URL
12+
import com.coder.gateway.models.WORKSPACE
813
import com.coder.gateway.models.WorkspaceAndAgentStatus
14+
import com.coder.gateway.models.WorkspaceProjectIDE
15+
import com.coder.gateway.models.agentID
16+
import com.coder.gateway.models.agentName
17+
import com.coder.gateway.models.folder
18+
import com.coder.gateway.models.ideBuildNumber
19+
import com.coder.gateway.models.ideDownloadLink
20+
import com.coder.gateway.models.idePathOnHost
21+
import com.coder.gateway.models.ideProductCode
22+
import com.coder.gateway.models.isCoder
23+
import com.coder.gateway.models.token
24+
import com.coder.gateway.models.url
25+
import com.coder.gateway.models.workspace
926
import com.coder.gateway.sdk.CoderRestClient
1027
import com.coder.gateway.sdk.ex.AuthenticationResponseException
1128
import com.coder.gateway.sdk.v2.models.Workspace
@@ -15,7 +32,7 @@ import com.coder.gateway.services.CoderRestClientService
1532
import com.coder.gateway.services.CoderSettingsService
1633
import com.coder.gateway.util.toURL
1734
import com.coder.gateway.util.withPath
18-
import com.coder.gateway.views.steps.CoderWorkspaceStepView
35+
import com.coder.gateway.views.steps.CoderWorkspaceIDEStepView
1936
import com.coder.gateway.views.steps.CoderWorkspacesStepSelection
2037
import com.intellij.openapi.application.ApplicationManager
2138
import com.intellij.openapi.components.service
@@ -26,31 +43,17 @@ import com.intellij.util.ui.JBUI
2643
import com.jetbrains.gateway.api.ConnectionRequestor
2744
import com.jetbrains.gateway.api.GatewayConnectionHandle
2845
import com.jetbrains.gateway.api.GatewayConnectionProvider
29-
import java.net.URL
3046
import javax.swing.JComponent
3147
import javax.swing.border.Border
3248

33-
// In addition to `type`, these are the keys that we support in our Gateway
34-
// links.
35-
private const val URL = "url"
36-
private const val TOKEN = "token"
37-
private const val WORKSPACE = "workspace"
38-
private const val AGENT_NAME = "agent"
39-
private const val AGENT_ID = "agent_id"
40-
private const val FOLDER = "folder"
41-
private const val IDE_DOWNLOAD_LINK = "ide_download_link"
42-
private const val IDE_PRODUCT_CODE = "ide_product_code"
43-
private const val IDE_BUILD_NUMBER = "ide_build_number"
44-
private const val IDE_PATH_ON_HOST = "ide_path_on_host"
45-
4649
/**
4750
* A dialog wrapper around CoderWorkspaceStepView.
4851
*/
4952
class CoderWorkspaceStepDialog(
5053
name: String,
5154
private val state: CoderWorkspacesStepSelection,
5255
) : DialogWrapper(true) {
53-
private val view = CoderWorkspaceStepView(showTitle = false)
56+
private val view = CoderWorkspaceIDEStepView(showTitle = false)
5457

5558
init {
5659
init()
@@ -65,7 +68,7 @@ class CoderWorkspaceStepDialog(
6568
view.dispose()
6669
}
6770

68-
fun showAndGetData(): Map<String, String>? {
71+
fun showAndGetData(): WorkspaceProjectIDE? {
6972
if (showAndGet()) {
7073
return view.data()
7174
}
@@ -98,16 +101,16 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
98101
CoderRemoteConnectionHandle().connect{ indicator ->
99102
logger.debug("Launched Coder connection provider", parameters)
100103

101-
val deploymentURL = parameters[URL]
104+
val deploymentURL = parameters.url()
102105
?: CoderRemoteConnectionHandle.ask("Enter the full URL of your Coder deployment")
103106
if (deploymentURL.isNullOrBlank()) {
104107
throw IllegalArgumentException("Query parameter \"$URL\" is missing")
105108
}
106109

107-
val (client, username) = authenticate(deploymentURL.toURL(), parameters[TOKEN])
110+
val (client, username) = authenticate(deploymentURL, parameters.token())
108111

109112
// TODO: If the workspace is missing we could launch the wizard.
110-
val workspaceName = parameters[WORKSPACE] ?: throw IllegalArgumentException("Query parameter \"$WORKSPACE\" is missing")
113+
val workspaceName = parameters.workspace() ?: throw IllegalArgumentException("Query parameter \"$WORKSPACE\" is missing")
111114

112115
val workspaces = client.workspaces()
113116
val workspace = workspaces.firstOrNull{ it.name == workspaceName } ?: throw IllegalArgumentException("The workspace $workspaceName does not exist")
@@ -150,13 +153,13 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
150153
cli.configSsh(client.agentNames(workspaces))
151154

152155
val name = "${workspace.name}.${agent.name}"
153-
val openDialog = parameters[IDE_PRODUCT_CODE].isNullOrBlank() ||
154-
parameters[IDE_BUILD_NUMBER].isNullOrBlank() ||
155-
(parameters[IDE_PATH_ON_HOST].isNullOrBlank() && parameters[IDE_DOWNLOAD_LINK].isNullOrBlank()) ||
156-
parameters[FOLDER].isNullOrBlank()
156+
val openDialog = parameters.ideProductCode().isNullOrBlank() ||
157+
parameters.ideBuildNumber().isNullOrBlank() ||
158+
(parameters.idePathOnHost().isNullOrBlank() && parameters.ideDownloadLink().isNullOrBlank()) ||
159+
parameters.folder().isNullOrBlank()
157160

158161
if (openDialog) {
159-
var data: Map<String, String>? = null
162+
var data: WorkspaceProjectIDE? = null
160163
ApplicationManager.getApplication().invokeAndWait {
161164
val dialog = CoderWorkspaceStepDialog(name,
162165
CoderWorkspacesStepSelection(agent, workspace, cli, client, workspaces))
@@ -167,13 +170,18 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
167170
// Check that both the domain and the redirected domain are
168171
// allowlisted. If not, check with the user whether to proceed.
169172
verifyDownloadLink(parameters)
170-
171-
parameters
172-
.withWorkspaceHostname(CoderCLIManager.getHostName(deploymentURL.toURL(), name))
173-
.withProjectPath(parameters[FOLDER]!!)
174-
.withWebTerminalLink(client.url.withPath("/@$username/$workspace.name/terminal").toString())
175-
.withConfigDirectory(cli.coderConfigPath.toString())
176-
.withName(name)
173+
WorkspaceProjectIDE.fromInputs(
174+
name = name,
175+
hostname = CoderCLIManager.getHostName(deploymentURL.toURL(), name),
176+
projectPath = parameters.folder(),
177+
ideProductCode = parameters.ideProductCode(),
178+
ideBuildNumber = parameters.ideBuildNumber(),
179+
webTerminalLink = client.url.withPath("/@$username/$workspace.name/terminal").toString(),
180+
configDirectory = cli.coderConfigPath.toString(),
181+
idePathOnHost = parameters.idePathOnHost(),
182+
downloadSource = parameters.ideDownloadLink(),
183+
lastOpened = null, // Have not opened yet.
184+
)
177185
}
178186
}
179187
return null
@@ -183,22 +191,22 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
183191
* Return an authenticated Coder CLI and the user's name, asking for the
184192
* token as long as it continues to result in an authentication failure.
185193
*/
186-
private fun authenticate(deploymentURL: URL, queryToken: String?, lastToken: Pair<String, TokenSource>? = null): Pair<CoderRestClient, String> {
194+
private fun authenticate(deploymentURL: String, queryToken: String?, lastToken: Pair<String, TokenSource>? = null): Pair<CoderRestClient, String> {
187195
// Use the token from the query, unless we already tried that.
188196
val isRetry = lastToken != null
189197
val token = if (!queryToken.isNullOrBlank() && !isRetry)
190198
Pair(queryToken, TokenSource.QUERY)
191199
else CoderRemoteConnectionHandle.askToken(
192-
deploymentURL,
200+
deploymentURL.toURL(),
193201
lastToken,
194202
isRetry,
195203
useExisting = true,
196204
settings,
197205
)
198206
if (token == null) { // User aborted.
199-
throw IllegalArgumentException("Unable to connect to $deploymentURL, $TOKEN is missing")
207+
throw IllegalArgumentException("Unable to connect to $deploymentURL, query parameter \"$TOKEN\" is missing")
200208
}
201-
val client = CoderRestClientService(deploymentURL, token.first)
209+
val client = CoderRestClientService(deploymentURL.toURL(), token.first)
202210
return try {
203211
Pair(client, client.me().username)
204212
} catch (ex: AuthenticationResponseException) {
@@ -210,7 +218,7 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
210218
* Check that the link is allowlisted. If not, confirm with the user.
211219
*/
212220
private fun verifyDownloadLink(parameters: Map<String, String>) {
213-
val link = parameters[IDE_DOWNLOAD_LINK]
221+
val link = parameters.ideDownloadLink()
214222
if (link.isNullOrBlank()) {
215223
return // Nothing to verify
216224
}
@@ -244,7 +252,7 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
244252
}
245253

246254
override fun isApplicable(parameters: Map<String, String>): Boolean {
247-
return parameters.areCoderType()
255+
return parameters.isCoder()
248256
}
249257

250258
companion object {
@@ -267,18 +275,18 @@ fun getMatchingAgent(parameters: Map<String, String?>, workspace: Workspace): Wo
267275

268276
// If the agent is missing and the workspace has only one, use that.
269277
// Prefer the ID over the name if both are set.
270-
val agent = if (!parameters[AGENT_ID].isNullOrBlank())
271-
agents.firstOrNull { it.id.toString() == parameters[AGENT_ID] }
272-
else if (!parameters[AGENT_NAME].isNullOrBlank())
273-
agents.firstOrNull { it.name == parameters[AGENT_NAME]}
278+
val agent = if (!parameters.agentID().isNullOrBlank())
279+
agents.firstOrNull { it.id.toString() == parameters.agentID() }
280+
else if (!parameters.agentName().isNullOrBlank())
281+
agents.firstOrNull { it.name == parameters.agentName()}
274282
else if (agents.size == 1) agents.first()
275283
else null
276284

277285
if (agent == null) {
278-
if (!parameters[AGENT_ID].isNullOrBlank()) {
279-
throw IllegalArgumentException("The workspace \"${workspace.name}\" does not have an agent with ID \"${parameters[AGENT_ID]}\"")
280-
} else if (!parameters[AGENT_NAME].isNullOrBlank()){
281-
throw IllegalArgumentException("The workspace \"${workspace.name}\"does not have an agent named \"${parameters[AGENT_NAME]}\"")
286+
if (!parameters.agentID().isNullOrBlank()) {
287+
throw IllegalArgumentException("The workspace \"${workspace.name}\" does not have an agent with ID \"${parameters.agentID()}\"")
288+
} else if (!parameters.agentName().isNullOrBlank()){
289+
throw IllegalArgumentException("The workspace \"${workspace.name}\"does not have an agent named \"${parameters.agentName()}\"")
282290
} else {
283291
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")
284292
}

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
package com.coder.gateway
44

55
import com.coder.gateway.models.TokenSource
6+
import com.coder.gateway.models.WorkspaceProjectIDE
7+
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
8+
import com.coder.gateway.settings.CoderSettings
69
import com.coder.gateway.util.humanizeDuration
710
import com.coder.gateway.util.isCancellation
811
import com.coder.gateway.util.isWorkerTimeout
912
import com.coder.gateway.util.suspendingRetryWithExponentialBackOff
10-
import com.coder.gateway.util.toURL
1113
import com.coder.gateway.util.withPath
12-
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
13-
import com.coder.gateway.settings.CoderSettings
1414
import com.intellij.ide.BrowserUtil
1515
import com.intellij.openapi.application.ApplicationManager
1616
import com.intellij.openapi.application.ModalityState
@@ -46,7 +46,7 @@ import javax.net.ssl.SSLHandshakeException
4646
class CoderRemoteConnectionHandle {
4747
private val recentConnectionsService = service<CoderRecentWorkspaceConnectionsService>()
4848

49-
fun connect(getParameters: (indicator: ProgressIndicator) -> Map<String, String>) {
49+
fun connect(getParameters: (indicator: ProgressIndicator) -> WorkspaceProjectIDE) {
5050
val clientLifetime = LifetimeDefinition()
5151
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title")) {
5252
try {

0 commit comments

Comments
 (0)