Skip to content

Commit c3c501e

Browse files
authored
Merge pull request #156 from coder/improve-ide-resolving-resiliency
Improve IDE resolving resiliency
2 parents 8042497 + 3ec4e10 commit c3c501e

File tree

6 files changed

+95
-56
lines changed

6 files changed

+95
-56
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
## Unreleased
66

7+
### Fixed
8+
- improved resiliency and error handling when resolving installed IDE's
9+
710
## 2.1.5 - 2023-01-24
811

912
### Fixed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pluginGroup=com.coder.gateway
44
pluginName=coder-gateway
55
# SemVer format -> https://semver.org
6-
pluginVersion=2.1.5
6+
pluginVersion=2.1.6
77
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
88
# for insight into build numbers and IntelliJ Platform versions.
99
pluginSinceBuild=222.3739.54

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

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

55
import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService
66
import com.intellij.openapi.components.service
7+
import com.intellij.openapi.diagnostic.Logger
78
import com.intellij.openapi.rd.util.launchUnderBackgroundProgress
89
import com.jetbrains.gateway.api.ConnectionRequestor
910
import com.jetbrains.gateway.api.GatewayConnectionHandle
@@ -22,6 +23,7 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
2223
val clientLifetime = LifetimeDefinition()
2324
clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) {
2425
val context = SshMultistagePanelContext(parameters.toHostDeployInputs())
26+
logger.info("Deploying and starting IDE with $context")
2527
launch {
2628
@Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle(
2729
clientLifetime, context, Duration.ofMinutes(10)
@@ -37,4 +39,8 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider {
3739
override fun isApplicable(parameters: Map<String, String>): Boolean {
3840
return parameters.areCoderType()
3941
}
42+
43+
companion object {
44+
val logger = Logger.getInstance(CoderGatewayConnectionProvider::class.java.simpleName)
45+
}
4046
}

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

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.coder.gateway.views.steps
33
import com.coder.gateway.CoderGatewayBundle
44
import com.coder.gateway.icons.CoderIcons
55
import com.coder.gateway.models.CoderWorkspacesWizardModel
6+
import com.coder.gateway.models.WorkspaceAgentModel
67
import com.coder.gateway.sdk.Arch
78
import com.coder.gateway.sdk.CoderRestClientService
89
import com.coder.gateway.sdk.OS
@@ -38,10 +39,13 @@ import com.jetbrains.gateway.ssh.HighLevelHostAccessor
3839
import com.jetbrains.gateway.ssh.IdeStatus
3940
import com.jetbrains.gateway.ssh.IdeWithStatus
4041
import com.jetbrains.gateway.ssh.IntelliJPlatformProduct
42+
import kotlinx.coroutines.CancellationException
4143
import kotlinx.coroutines.CoroutineScope
4244
import kotlinx.coroutines.Dispatchers
45+
import kotlinx.coroutines.Job
4346
import kotlinx.coroutines.async
4447
import kotlinx.coroutines.cancel
48+
import kotlinx.coroutines.cancelAndJoin
4549
import kotlinx.coroutines.launch
4650
import kotlinx.coroutines.withContext
4751
import java.awt.Component
@@ -67,6 +71,8 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
6771
private lateinit var tfProject: JBTextField
6872
private lateinit var terminalLink: LazyBrowserLink
6973

74+
private lateinit var ideResolvingJob: Job
75+
7076
override val component = panel {
7177
indent {
7278
row {
@@ -107,6 +113,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
107113
override val nextActionText = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.next.text")
108114

109115
override fun onInit(wizardModel: CoderWorkspacesWizardModel) {
116+
ideComboBoxModel.removeAllElements()
110117
wizard = wizardModel
111118
val selectedWorkspace = wizardModel.selectedWorkspace
112119
if (selectedWorkspace == null) {
@@ -118,61 +125,73 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
118125
titleLabel.text = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", selectedWorkspace.name)
119126
terminalLink.url = "${coderClient.coderURL}/@${coderClient.me.username}/${selectedWorkspace.name}/terminal"
120127

121-
cs.launch {
122-
logger.info("Retrieving available IDE's for ${selectedWorkspace.name} workspace...")
123-
val hostAccessor = HighLevelHostAccessor.create(
124-
RemoteCredentialsHolder().apply {
125-
setHost("coder.${selectedWorkspace.name}")
126-
userName = "coder"
127-
authType = AuthType.OPEN_SSH
128-
},
129-
true
130-
)
131-
val workspaceOS = if (selectedWorkspace.agentOS != null && selectedWorkspace.agentArch != null) toDeployedOS(selectedWorkspace.agentOS, selectedWorkspace.agentArch) else withContext(Dispatchers.IO) {
132-
try {
133-
hostAccessor.guessOs()
134-
} catch (e: Exception) {
135-
logger.error("Could not resolve any IDE for workspace ${selectedWorkspace.name}. Reason: $e")
136-
null
137-
}
138-
}
139-
if (workspaceOS == null) {
140-
disableNextAction()
141-
cbIDE.renderer = object : ColoredListCellRenderer<IdeWithStatus>() {
142-
override fun customizeCellRenderer(list: JList<out IdeWithStatus>, value: IdeWithStatus?, index: Int, isSelected: Boolean, cellHasFocus: Boolean) {
143-
background = UIUtil.getListBackground(isSelected, cellHasFocus)
144-
icon = UIUtil.getBalloonErrorIcon()
145-
append(CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.ide.error.text", selectedWorkspace.name))
128+
ideResolvingJob = cs.launch {
129+
try {
130+
retrieveIDES(selectedWorkspace)
131+
} catch (e: Exception) {
132+
when (e) {
133+
is InterruptedException -> Unit
134+
is CancellationException -> Unit
135+
else -> {
136+
logger.error("Could not resolve any IDE for workspace ${selectedWorkspace.name}. Reason: $e")
137+
withContext(Dispatchers.Main) {
138+
disableNextAction()
139+
cbIDE.renderer = object : ColoredListCellRenderer<IdeWithStatus>() {
140+
override fun customizeCellRenderer(list: JList<out IdeWithStatus>, value: IdeWithStatus?, index: Int, isSelected: Boolean, cellHasFocus: Boolean) {
141+
background = UIUtil.getListBackground(isSelected, cellHasFocus)
142+
icon = UIUtil.getBalloonErrorIcon()
143+
append(CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.ide.error.text", selectedWorkspace.name))
144+
}
145+
}
146+
}
146147
}
147148
}
148-
} else {
149-
logger.info("Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS")
150-
val installedIdesJob = async(Dispatchers.IO) {
151-
hostAccessor.getInstalledIDEs().map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.ALREADY_INSTALLED, null, ide.pathToIde, ide.presentableVersion, ide.remoteDevType) }
152-
}
153-
val idesWithStatusJob = async(Dispatchers.IO) {
154-
IntelliJPlatformProduct.values()
155-
.filter { it.showInGateway }
156-
.flatMap { CachingProductsJsonWrapper.getInstance().getAvailableIdes(it, workspaceOS) }
157-
.map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.DOWNLOAD, ide.download, null, ide.presentableVersion, ide.remoteDevType) }
158-
}
149+
}
150+
}
151+
}
159152

160-
val installedIdes = installedIdesJob.await()
161-
val idesWithStatus = idesWithStatusJob.await()
162-
if (installedIdes.isEmpty()) {
163-
logger.info("No IDE is installed in workspace ${selectedWorkspace.name}")
164-
} else {
165-
ideComboBoxModel.addAll(installedIdes)
166-
cbIDE.selectedIndex = 0
167-
}
153+
private suspend fun retrieveIDES(selectedWorkspace: WorkspaceAgentModel) {
154+
logger.info("Retrieving available IDE's for ${selectedWorkspace.name} workspace...")
155+
val hostAccessor = HighLevelHostAccessor.create(
156+
RemoteCredentialsHolder().apply {
157+
setHost("coder.${selectedWorkspace.name}")
158+
userName = "coder"
159+
authType = AuthType.OPEN_SSH
160+
},
161+
true
162+
)
163+
val workspaceOS = if (selectedWorkspace.agentOS != null && selectedWorkspace.agentArch != null) toDeployedOS(selectedWorkspace.agentOS, selectedWorkspace.agentArch) else withContext(Dispatchers.IO) {
164+
hostAccessor.guessOs()
165+
}
168166

169-
if (idesWithStatus.isEmpty()) {
170-
logger.warn("Could not resolve any IDE for workspace ${selectedWorkspace.name}, probably $workspaceOS is not supported by Gateway")
171-
} else {
167+
logger.info("Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS")
168+
val installedIdesJob = cs.async(Dispatchers.IO) {
169+
hostAccessor.getInstalledIDEs().map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.ALREADY_INSTALLED, null, ide.pathToIde, ide.presentableVersion, ide.remoteDevType) }
170+
}
171+
val idesWithStatusJob = cs.async(Dispatchers.IO) {
172+
IntelliJPlatformProduct.values()
173+
.filter { it.showInGateway }
174+
.flatMap { CachingProductsJsonWrapper.getInstance().getAvailableIdes(it, workspaceOS) }
175+
.map { ide -> IdeWithStatus(ide.product, ide.buildNumber, IdeStatus.DOWNLOAD, ide.download, null, ide.presentableVersion, ide.remoteDevType) }
176+
}
172177

173-
ideComboBoxModel.addAll(idesWithStatus)
174-
cbIDE.selectedIndex = 0
175-
}
178+
val installedIdes = installedIdesJob.await()
179+
val idesWithStatus = idesWithStatusJob.await()
180+
if (installedIdes.isEmpty()) {
181+
logger.info("No IDE is installed in workspace ${selectedWorkspace.name}")
182+
} else {
183+
withContext(Dispatchers.Main) {
184+
ideComboBoxModel.addAll(installedIdes)
185+
cbIDE.selectedIndex = 0
186+
}
187+
}
188+
189+
if (idesWithStatus.isEmpty()) {
190+
logger.warn("Could not resolve any IDE for workspace ${selectedWorkspace.name}, probably $workspaceOS is not supported by Gateway")
191+
} else {
192+
withContext(Dispatchers.Main) {
193+
ideComboBoxModel.addAll(idesWithStatus)
194+
cbIDE.selectedIndex = 0
176195
}
177196
}
178197
}
@@ -201,6 +220,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
201220

202221
override fun onNext(wizardModel: CoderWorkspacesWizardModel): Boolean {
203222
val selectedIDE = cbIDE.selectedItem ?: return false
223+
logger.info("Going to launch the IDE")
204224
cs.launch {
205225
GatewayUI.getInstance().connect(
206226
selectedIDE
@@ -213,12 +233,16 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
213233
return true
214234
}
215235

216-
override fun dispose() {
217-
cs.cancel()
236+
override fun onPrevious() {
237+
super.onPrevious()
238+
logger.info("Going back to Workspace view")
239+
cs.launch {
240+
ideResolvingJob.cancelAndJoin()
241+
}
218242
}
219243

220-
companion object {
221-
val logger = Logger.getInstance(CoderLocateRemoteProjectStepView::class.java.simpleName)
244+
override fun dispose() {
245+
cs.cancel()
222246
}
223247

224248
private class IDEComboBox(model: ComboBoxModel<IdeWithStatus>) : ComboBox<IdeWithStatus>(model) {
@@ -258,4 +282,8 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
258282
}
259283
}
260284
}
285+
286+
companion object {
287+
val logger = Logger.getInstance(CoderLocateRemoteProjectStepView::class.java.simpleName)
288+
}
261289
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
589589

590590
override fun onPrevious() {
591591
super.onPrevious()
592+
logger.info("Going back to the main view")
592593
poller?.cancel()
593594
}
594595

@@ -621,6 +622,7 @@ class CoderWorkspacesStepView(val enableNextButtonCallback: (Boolean) -> Unit) :
621622
if (workspace != null) {
622623
wizardModel.selectedWorkspace = workspace
623624
poller?.cancel()
625+
logger.info("Opening IDE and Project Location window for ${workspace.name}")
624626
return true
625627
}
626628
return false

src/main/resources/messages/CoderGatewayBundle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ gateway.connector.view.coder.workspaces.unsupported.os.info=Gateway supports onl
2020
gateway.connector.view.coder.workspaces.invalid.coder.version=Could not parse Coder version {0}. Coder Gateway plugin might not be compatible with this version. <a href='https://coder.com/docs/coder-oss/latest/ides/gateway#creating-a-new-jetbrains-gateway-connection'>Connect to a Coder workspace manually</a>
2121
gateway.connector.view.coder.workspaces.unsupported.coder.version=Coder version {0} might not be compatible with this plugin version. <a href='https://coder.com/docs/coder-oss/latest/ides/gateway#creating-a-new-jetbrains-gateway-connection'>Connect to a Coder workspace manually</a>
2222
gateway.connector.view.coder.remoteproject.loading.text=Retrieving products...
23-
gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE for workspace {0} because an error was encountered
23+
gateway.connector.view.coder.remoteproject.ide.error.text=Could not retrieve any IDE for workspace {0} because an error was encountered. Please check the logs for more details!
2424
gateway.connector.view.coder.remoteproject.next.text=Start IDE and connect
2525
gateway.connector.view.coder.remoteproject.choose.text=Choose IDE and project for workspace {0}
2626
gateway.connector.recentconnections.title=Recent Coder Workspaces

0 commit comments

Comments
 (0)