Skip to content

Commit bc0ce3f

Browse files
committed
Break connection code out into separate class
The connect entrypoint is used both for Gateway links (jetbrains-gateway://) and internally in our own code at the end of the wizard and for recent connections, but this will be two separate sets of parameters (internally we have the CLI set up and pass around the hostname while the links will only have the workspace ID or name). So break out the code into a separate class that we can call internally which will let us dedicate the connect entrypoint to handle the Gateway links. There we will set up the CLI and gather the required parameters before calling the now-broken-out code.
1 parent a727aa8 commit bc0ce3f

File tree

4 files changed

+100
-79
lines changed

4 files changed

+100
-79
lines changed

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

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,90 +2,15 @@
22

33
package com.coder.gateway
44

5-
import com.coder.gateway.sdk.humanizeDuration
6-
import com.coder.gateway.sdk.isCancellation
7-
import com.coder.gateway.sdk.isWorkerTimeout
8-
import com.coder.gateway.sdk.suspendingRetryWithExponentialBackOff
9-
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
10-
import com.intellij.openapi.application.ApplicationManager
11-
import com.intellij.openapi.components.service
125
import com.intellij.openapi.diagnostic.Logger
13-
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
14-
import com.intellij.openapi.ui.Messages
156
import com.jetbrains.gateway.api.ConnectionRequestor
167
import com.jetbrains.gateway.api.GatewayConnectionHandle
178
import com.jetbrains.gateway.api.GatewayConnectionProvider
18-
import com.jetbrains.gateway.api.GatewayUI
19-
import com.jetbrains.gateway.ssh.SshDeployFlowUtil
20-
import com.jetbrains.gateway.ssh.SshMultistagePanelContext
21-
import com.jetbrains.gateway.ssh.deploy.DeployException
22-
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
23-
import kotlinx.coroutines.launch
24-
import net.schmizz.sshj.common.SSHException
25-
import net.schmizz.sshj.connection.ConnectionException
26-
import java.time.Duration
27-
import java.util.concurrent.TimeoutException
289

2910
class CoderGatewayConnectionProvider : GatewayConnectionProvider {
30-
private val recentConnectionsService = service<CoderRecentWorkspaceConnectionsService>()
31-
3211
override suspend fun connect(parameters: Map<String, String>, requestor: ConnectionRequestor): GatewayConnectionHandle? {
33-
val clientLifetime = LifetimeDefinition()
34-
// TODO: If this fails determine if it is an auth error and if so prompt
35-
// for a new token, configure the CLI, then try again.
36-
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
37-
try {
38-
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting")
39-
val context = suspendingRetryWithExponentialBackOff(
40-
action = { attempt ->
41-
logger.info("Connecting... (attempt $attempt")
42-
if (attempt > 1) {
43-
// indicator.text is the text above the progress bar.
44-
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting.retry", attempt)
45-
}
46-
SshMultistagePanelContext(parameters.toHostDeployInputs())
47-
},
48-
retryIf = {
49-
it is ConnectionException || it is TimeoutException
50-
|| it is SSHException || it is DeployException
51-
},
52-
onException = { attempt, nextMs, e ->
53-
logger.error("Failed to connect (attempt $attempt; will retry in $nextMs ms)")
54-
// indicator.text2 is the text below the progress bar.
55-
indicator.text2 =
56-
if (isWorkerTimeout(e)) "Failed to upload worker binary...it may have timed out"
57-
else e.message ?: CoderGatewayBundle.message("gateway.connector.no-details")
58-
},
59-
onCountdown = { remainingMs ->
60-
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting.failed.retry", humanizeDuration(remainingMs))
61-
},
62-
)
63-
launch {
64-
logger.info("Deploying and starting IDE with $context")
65-
// At this point JetBrains takes over with their own UI.
66-
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
67-
clientLifetime, context, Duration.ofMinutes(10)
68-
)
69-
}
70-
} catch (e: Exception) {
71-
if (isCancellation(e)) {
72-
logger.info("Connection canceled due to ${e.javaClass}")
73-
} else {
74-
logger.info("Failed to connect (will not retry)", e)
75-
// The dialog will close once we return so write the error
76-
// out into a new dialog.
77-
ApplicationManager.getApplication().invokeAndWait {
78-
Messages.showMessageDialog(
79-
e.message ?: CoderGatewayBundle.message("gateway.connector.no-details"),
80-
CoderGatewayBundle.message("gateway.connector.coder.connection.failed"),
81-
Messages.getErrorIcon())
82-
}
83-
}
84-
}
85-
}
86-
87-
recentConnectionsService.addRecentConnection(parameters.toRecentWorkspaceConnection())
88-
GatewayUI.getInstance().reset()
12+
logger.debug("Launched Coder connection provider", parameters)
13+
CoderRemoteConnectionHandle().connect(parameters)
8914
return null
9015
}
9116

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
@file:Suppress("DialogTitleCapitalization")
2+
3+
package com.coder.gateway
4+
5+
import com.coder.gateway.sdk.humanizeDuration
6+
import com.coder.gateway.sdk.isCancellation
7+
import com.coder.gateway.sdk.isWorkerTimeout
8+
import com.coder.gateway.sdk.suspendingRetryWithExponentialBackOff
9+
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
10+
import com.intellij.openapi.application.ApplicationManager
11+
import com.intellij.openapi.components.service
12+
import com.intellij.openapi.diagnostic.Logger
13+
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
14+
import com.intellij.openapi.ui.Messages
15+
import com.jetbrains.gateway.ssh.SshDeployFlowUtil
16+
import com.jetbrains.gateway.ssh.SshMultistagePanelContext
17+
import com.jetbrains.gateway.ssh.deploy.DeployException
18+
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
19+
import kotlinx.coroutines.launch
20+
import net.schmizz.sshj.common.SSHException
21+
import net.schmizz.sshj.connection.ConnectionException
22+
import java.time.Duration
23+
import java.util.concurrent.TimeoutException
24+
25+
// CoderRemoteConnection uses the provided workspace SSH parameters to launch an
26+
// IDE against the workspace. If successful the connection is added to recent
27+
// connections.
28+
class CoderRemoteConnectionHandle {
29+
private val recentConnectionsService = service<CoderRecentWorkspaceConnectionsService>()
30+
31+
suspend fun connect(parameters: Map<String, String>) {
32+
logger.debug("Creating connection handle", parameters)
33+
val clientLifetime = LifetimeDefinition()
34+
// TODO: If this fails determine if it is an auth error and if so prompt
35+
// for a new token, configure the CLI, then try again.
36+
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
37+
try {
38+
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting")
39+
val context = suspendingRetryWithExponentialBackOff(
40+
action = { attempt ->
41+
logger.info("Connecting... (attempt $attempt")
42+
if (attempt > 1) {
43+
// indicator.text is the text above the progress bar.
44+
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting.retry", attempt)
45+
}
46+
SshMultistagePanelContext(parameters.toHostDeployInputs())
47+
},
48+
retryIf = {
49+
it is ConnectionException || it is TimeoutException
50+
|| it is SSHException || it is DeployException
51+
},
52+
onException = { attempt, nextMs, e ->
53+
logger.error("Failed to connect (attempt $attempt; will retry in $nextMs ms)")
54+
// indicator.text2 is the text below the progress bar.
55+
indicator.text2 =
56+
if (isWorkerTimeout(e)) "Failed to upload worker binary...it may have timed out"
57+
else e.message ?: CoderGatewayBundle.message("gateway.connector.no-details")
58+
},
59+
onCountdown = { remainingMs ->
60+
indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting.failed.retry", humanizeDuration(remainingMs))
61+
},
62+
)
63+
launch {
64+
logger.info("Deploying and starting IDE with $context")
65+
// At this point JetBrains takes over with their own UI.
66+
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
67+
clientLifetime, context, Duration.ofMinutes(10)
68+
)
69+
}
70+
recentConnectionsService.addRecentConnection(parameters.toRecentWorkspaceConnection())
71+
} catch (e: Exception) {
72+
if (isCancellation(e)) {
73+
logger.info("Connection canceled due to ${e.javaClass}")
74+
} else {
75+
logger.info("Failed to connect (will not retry)", e)
76+
// The dialog will close once we return so write the error
77+
// out into a new dialog.
78+
ApplicationManager.getApplication().invokeAndWait {
79+
Messages.showMessageDialog(
80+
e.message ?: CoderGatewayBundle.message("gateway.connector.no-details"),
81+
CoderGatewayBundle.message("gateway.connector.coder.connection.failed"),
82+
Messages.getErrorIcon())
83+
}
84+
}
85+
}
86+
}
87+
}
88+
89+
companion object {
90+
val logger = Logger.getInstance(CoderRemoteConnectionHandle::class.java.simpleName)
91+
}
92+
}

src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.coder.gateway.views
44

55
import com.coder.gateway.CoderGatewayBundle
66
import com.coder.gateway.CoderGatewayConstants
7+
import com.coder.gateway.CoderRemoteConnectionHandle
78
import com.coder.gateway.icons.CoderIcons
89
import com.coder.gateway.models.RecentWorkspaceConnection
910
import com.coder.gateway.models.WorkspaceAgentModel
@@ -215,7 +216,8 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback:
215216
icon(product.icon)
216217
cell(ActionLink(connectionDetails.projectPath!!) {
217218
cs.launch {
218-
GatewayUI.getInstance().connect(connectionDetails.toWorkspaceParams())
219+
CoderRemoteConnectionHandle().connect(connectionDetails.toWorkspaceParams())
220+
GatewayUI.getInstance().reset()
219221
}
220222
})
221223
label("").resizableColumn().align(AlignX.FILL)

src/main/kotlin/com/coder/gateway/views/steps/CoderLocateRemoteProjectStepView.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.coder.gateway.views.steps
22

33
import com.coder.gateway.CoderGatewayBundle
4+
import com.coder.gateway.CoderRemoteConnectionHandle
45
import com.coder.gateway.icons.CoderIcons
56
import com.coder.gateway.models.CoderWorkspacesWizardModel
67
import com.coder.gateway.models.WorkspaceAgentModel
@@ -338,7 +339,7 @@ class CoderLocateRemoteProjectStepView(private val setNextButtonEnabled: (Boolea
338339
return false
339340
}
340341
cs.launch {
341-
GatewayUI.getInstance().connect(
342+
CoderRemoteConnectionHandle().connect(
342343
selectedIDE
343344
.toWorkspaceParams()
344345
.withWorkspaceHostname(CoderCLIManager.getHostName(deploymentURL, selectedWorkspace))
@@ -347,6 +348,7 @@ class CoderLocateRemoteProjectStepView(private val setNextButtonEnabled: (Boolea
347348
.withConfigDirectory(wizardModel.configDirectory)
348349
.withName(selectedWorkspace.name)
349350
)
351+
GatewayUI.getInstance().reset()
350352
}
351353
return true
352354
}

0 commit comments

Comments
 (0)