From cb344ac1e670228c76bc51f316caf08efd1e0aaf Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 3 Oct 2023 10:08:44 -0800 Subject: [PATCH 1/5] Fix missing actionButton import Maybe it was moved or something. Unfortunately this means we have to use a wildcard because in previous versions it was not a separate import. --- .../views/CoderGatewayRecentWorkspaceConnectionsView.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index dcf54969..1c7d0d71 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -29,12 +29,7 @@ import com.intellij.ui.DocumentAdapter import com.intellij.ui.SearchTextField import com.intellij.ui.components.ActionLink import com.intellij.ui.components.JBScrollPane -import com.intellij.ui.dsl.builder.AlignX -import com.intellij.ui.dsl.builder.AlignY -import com.intellij.ui.dsl.builder.BottomGap -import com.intellij.ui.dsl.builder.RightGap -import com.intellij.ui.dsl.builder.TopGap -import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.* import com.intellij.util.io.readText import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI From 77c0f17514551dae0ee0ae2a405e006d086f240d Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 3 Oct 2023 10:29:48 -0800 Subject: [PATCH 2/5] Use global scope to launch I guess `launch` alone is no longer allowed. --- .../kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 3ebee47d..1afe34a8 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -31,6 +31,7 @@ import com.jetbrains.gateway.ssh.SshDeployFlowUtil import com.jetbrains.gateway.ssh.SshMultistagePanelContext import com.jetbrains.gateway.ssh.deploy.DeployException import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.schmizz.sshj.common.SSHException import net.schmizz.sshj.connection.ConnectionException @@ -78,7 +79,7 @@ class CoderRemoteConnectionHandle { indicator.text = CoderGatewayBundle.message("gateway.connector.coder.connecting.failed.retry", humanizeDuration(remainingMs)) }, ) - launch { + GlobalScope.launch { logger.info("Deploying and starting IDE with $context") // At this point JetBrains takes over with their own UI. @Suppress("UnstableApiUsage") SshDeployFlowUtil.fullDeployCycle( From 9716f2fe6fec57efd956179794a84c298df118fe Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 3 Oct 2023 10:25:49 -0800 Subject: [PATCH 3/5] Allow canceling connection Also, getting an error about project being null so just remove it. It is now an overload where the project is not specified at all. Previously it was an optional parameter, so it should still work in all versions. --- .../gateway/CoderRemoteConnectionHandle.kt | 2 +- .../views/steps/CoderWorkspacesStepView.kt | 104 +++++++++--------- .../messages/CoderGatewayBundle.properties | 1 + 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 1afe34a8..6401e4a6 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -50,7 +50,7 @@ class CoderRemoteConnectionHandle { suspend fun connect(getParameters: (indicator: ProgressIndicator) -> Map) { val clientLifetime = LifetimeDefinition() - clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title"), canBeCancelled = true, isIndeterminate = true, project = null) { + clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title")) { try { val parameters = getParameters(indicator) logger.debug("Creating connection handle", parameters) diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index d7c97447..1d7da3ff 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -18,6 +18,7 @@ import com.coder.gateway.sdk.TemplateIconDownloader import com.coder.gateway.sdk.ex.AuthenticationResponseException import com.coder.gateway.sdk.ex.TemplateResponseException import com.coder.gateway.sdk.ex.WorkspaceResponseException +import com.coder.gateway.sdk.isCancellation import com.coder.gateway.sdk.toURL import com.coder.gateway.sdk.v2.models.WorkspaceStatus import com.coder.gateway.sdk.v2.models.toAgentModels @@ -423,12 +424,7 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod tableOfWorkspaces.listTableModel.items = emptyList() // Authenticate and load in a background process with progress. - // TODO: Make this cancelable. - return LifetimeDefinition().launchUnderBackgroundProgress( - CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title"), - canBeCancelled = false, - isIndeterminate = true - ) { + return LifetimeDefinition().launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.cli.downloader.dialog.title")) { try { this.indicator.text = "Authenticating client..." authenticate(deploymentURL, token.first) @@ -456,51 +452,61 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connected", deploymentURL.host)) tfUrlComment?.text = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connected", deploymentURL.host) } catch (e: Exception) { - val reason = e.message ?: CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.no-reason") - val msg = when (e) { - is java.nio.file.AccessDeniedException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.access-denied", e.file) - is UnknownHostException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.unknown-host", e.message ?: deploymentURL.host) - is InvalidExitValueException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.unexpected-exit", e.exitValue) - is AuthenticationResponseException -> { - CoderGatewayBundle.message( - "gateway.connector.view.workspaces.connect.unauthorized", - deploymentURL, - ) - } - is SocketTimeoutException -> { - CoderGatewayBundle.message( - "gateway.connector.view.workspaces.connect.timeout", - deploymentURL, - ) - } - is ResponseException, is ConnectException -> { - CoderGatewayBundle.message( - "gateway.connector.view.workspaces.connect.download-failed", - reason, - ) + if (isCancellation(e)) { + tfUrlComment?.text = CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.comment", + CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text")) + tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message( + "gateway.connector.view.workspaces.connect.canceled", + deploymentURL.host, + )) + logger.info("Connection canceled due to ${e.javaClass.simpleName}") + } else { + val reason = e.message ?: CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.no-reason") + val msg = when (e) { + is java.nio.file.AccessDeniedException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.access-denied", e.file) + is UnknownHostException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.unknown-host", e.message ?: deploymentURL.host) + is InvalidExitValueException -> CoderGatewayBundle.message("gateway.connector.view.workspaces.connect.unexpected-exit", e.exitValue) + is AuthenticationResponseException -> { + CoderGatewayBundle.message( + "gateway.connector.view.workspaces.connect.unauthorized", + deploymentURL, + ) + } + is SocketTimeoutException -> { + CoderGatewayBundle.message( + "gateway.connector.view.workspaces.connect.timeout", + deploymentURL, + ) + } + is ResponseException, is ConnectException -> { + CoderGatewayBundle.message( + "gateway.connector.view.workspaces.connect.download-failed", + reason, + ) + } + is SSLHandshakeException -> { + CoderGatewayBundle.message( + "gateway.connector.view.workspaces.connect.ssl-error", + deploymentURL.host, + reason, + ) + } + else -> reason } - is SSLHandshakeException -> { - CoderGatewayBundle.message( - "gateway.connector.view.workspaces.connect.ssl-error", - deploymentURL.host, - reason, - ) + // It would be nice to place messages directly into the table + // but it does not support wrapping or markup so place it in the + // comment field of the URL input instead. + tfUrlComment?.foreground = UIUtil.getErrorForeground() + tfUrlComment?.text = msg + tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message( + "gateway.connector.view.workspaces.connect.failed", + deploymentURL.host, + )) + logger.error(msg, e) + + if (e is AuthenticationResponseException) { + cs.launch { onAuthFailure?.invoke() } } - else -> reason - } - // It would be nice to place messages directly into the table - // but it does not support wrapping or markup so place it in the - // comment field of the URL input instead. - tfUrlComment?.foreground = UIUtil.getErrorForeground() - tfUrlComment?.text = msg - tableOfWorkspaces.setEmptyState(CoderGatewayBundle.message( - "gateway.connector.view.workspaces.connect.failed", - deploymentURL.host, - )) - logger.error(msg, e) - - if (e is AuthenticationResponseException) { - cs.launch { onAuthFailure?.invoke() } } } } diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties index 9d5af8de..132ef032 100644 --- a/src/main/resources/messages/CoderGatewayBundle.properties +++ b/src/main/resources/messages/CoderGatewayBundle.properties @@ -26,6 +26,7 @@ gateway.connector.view.coder.workspaces.unsupported.os.info=Gateway supports onl gateway.connector.view.coder.workspaces.invalid.coder.version=Could not parse Coder version {0}. Coder Gateway plugin might not be compatible with this version. Connect to a Coder workspace manually gateway.connector.view.coder.workspaces.unsupported.coder.version=Coder version {0} might not be compatible with this plugin version. Connect to a Coder workspace manually gateway.connector.view.workspaces.connect.failed=Connection to {0} failed. See above for details. +gateway.connector.view.workspaces.connect.canceled=Connection to {0} canceled. gateway.connector.view.workspaces.connect.no-reason=No reason was provided. gateway.connector.view.workspaces.connect.access-denied=Access denied to {0}. gateway.connector.view.workspaces.connect.unknown-host=Unknown host {0}. From ad195307aea775f227e1cb3ac10f6c182e03c8d2 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 3 Oct 2023 08:02:06 -0800 Subject: [PATCH 4/5] Bump version to 2.8.0 --- CHANGELOG.md | 1 + gradle.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1fe9ef6..6ba36dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Added - Add a setting for a command to run to get headers that will be set on all requests to the Coder deployment. +- Support for Gateway 2023.3. ## 2.6.0 - 2023-09-06 diff --git a/gradle.properties b/gradle.properties index 855e7178..92b80e02 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ pluginGroup=com.coder.gateway pluginName=coder-gateway # SemVer format -> https://semver.org -pluginVersion=2.7.0 +pluginVersion=2.8.0 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. pluginSinceBuild=223.7571.70 From d67be215fe27957efb3c3c4b309de48b2ab8cf03 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 3 Oct 2023 12:02:20 -0800 Subject: [PATCH 5/5] Update incorrect existing token label This changed to automatically use the token, no confirmation. --- .../coder/gateway/CoderRemoteConnectionHandle.kt | 15 +++++++++------ .../messages/CoderGatewayBundle.properties | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 6401e4a6..c9a19bb4 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -179,13 +179,16 @@ class CoderRemoteConnectionHandle { } /** - * Open a dialog for providing the token. Show any existing token so the - * user can validate it if a previous connection failed. If we are not - * retrying and the user has not checked the existing token box then open a - * browser to the auth page. If the user has checked the existing token box - * then populate the dialog with the token on disk (this will overwrite any + * Open a dialog for providing the token. Show any existing token so + * the user can validate it if a previous connection failed. + * + * If we are not retrying and the user has not checked the existing + * token box then also open a browser to the auth page. + * + * If the user has checked the existing token box then return the token + * on disk immediately and skip the dialog (this will overwrite any * other existing token) unless this is a retry to avoid clobbering the - * token that just failed. Return the token submitted by the user. + * token that just failed. */ @JvmStatic fun askToken( diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties index 132ef032..6e93c557 100644 --- a/src/main/resources/messages/CoderGatewayBundle.properties +++ b/src/main/resources/messages/CoderGatewayBundle.properties @@ -4,7 +4,7 @@ gateway.connector.action.text=Connect to Coder gateway.connector.view.login.documentation.action=Learn more about Coder gateway.connector.view.login.url.label=URL: gateway.connector.view.login.existing-token.label=Use existing token -gateway.connector.view.login.existing-token.tooltip=Checking "{0}" will prevent the browser from being launched for generating a new token after pressing "{1}". Additionally, if a token is already configured for this URL via the CLI it will appear as the default and can be used as-is or replaced. +gateway.connector.view.login.existing-token.tooltip=Checking "{0}" will prevent the browser from being launched for generating a new token after pressing "{1}". Additionally, if a token is already configured for this URL via the CLI it will automatically be used. gateway.connector.view.login.token.dialog=Paste your token here: gateway.connector.view.login.token.label=Session Token: gateway.connector.view.coder.workspaces.header.text=Coder Workspaces