From 88134ba979be2597cccda885451b6bf612d1b977 Mon Sep 17 00:00:00 2001 From: Kirill Kalishev Date: Tue, 18 Feb 2025 17:31:21 -0500 Subject: [PATCH 1/5] setup script can communicate an error message to the end user --- .../coder/gateway/CoderGatewayConstants.kt | 1 + .../gateway/CoderRemoteConnectionHandle.kt | 33 ++++++++++--- .../coder/gateway/util/SetupCommandTest.kt | 47 +++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt diff --git a/src/main/kotlin/com/coder/gateway/CoderGatewayConstants.kt b/src/main/kotlin/com/coder/gateway/CoderGatewayConstants.kt index 6344aca68..1defb91d8 100644 --- a/src/main/kotlin/com/coder/gateway/CoderGatewayConstants.kt +++ b/src/main/kotlin/com/coder/gateway/CoderGatewayConstants.kt @@ -3,4 +3,5 @@ package com.coder.gateway object CoderGatewayConstants { const val GATEWAY_CONNECTOR_ID = "Coder.Gateway.Connector" const val GATEWAY_RECENT_CONNECTIONS_ID = "Coder.Gateway.Recent.Connections" + const val GATEWAY_SETUP_COMMAND_ERROR = "CODER_SETUP_ERROR" } diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 102b73fcc..b55f138d8 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -2,6 +2,7 @@ package com.coder.gateway +import com.coder.gateway.CoderGatewayConstants.GATEWAY_SETUP_COMMAND_ERROR import com.coder.gateway.cli.CoderCLIManager import com.coder.gateway.models.WorkspaceProjectIDE import com.coder.gateway.models.toIdeWithStatus @@ -412,18 +413,16 @@ class CoderRemoteConnectionHandle { ) { if (setupCommand.isNotBlank()) { indicator.text = "Running setup command..." - try { - exec(workspace, setupCommand) - } catch (ex: Exception) { - if (!ignoreSetupFailure) { - throw ex - } - } + processSetupCommand( + { exec(workspace, setupCommand) }, + ignoreSetupFailure + ) } else { logger.info("No setup command to run on ${workspace.hostname}") } } + /** * Execute a command in the IDE's bin directory. * This exists since the accessor does not provide a generic exec. @@ -523,5 +522,25 @@ class CoderRemoteConnectionHandle { companion object { val logger = Logger.getInstance(CoderRemoteConnectionHandle::class.java.simpleName) + fun processSetupCommand( + output: () -> String, + ignoreSetupFailure: Boolean + ) { + try { + val errorText = output + .invoke() + .lines() + .firstOrNull { it.contains(GATEWAY_SETUP_COMMAND_ERROR) } + ?.let { it.substring(it.indexOf(GATEWAY_SETUP_COMMAND_ERROR) + GATEWAY_SETUP_COMMAND_ERROR.length).trim() } + + if (!errorText.isNullOrBlank()) { + throw Exception(errorText) + } + } catch (ex: Exception) { + if (!ignoreSetupFailure) { + throw ex + } + } + } } } diff --git a/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt b/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt new file mode 100644 index 000000000..80cce97e7 --- /dev/null +++ b/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt @@ -0,0 +1,47 @@ +package com.coder.gateway.util + +import com.coder.gateway.CoderRemoteConnectionHandle.Companion.processSetupCommand +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +internal class SetupCommandTest { + + @Test + fun executionErrors() { + assertEquals( + "Execution error", + assertThrows { + processSetupCommand({ throw Exception("Execution error") }, false) + }.message + ) + processSetupCommand({ throw Exception("Execution error") }, true) + } + + @Test + fun setupScriptError() { + assertEquals( + "Your IDE is expired, please update", + assertThrows { + processSetupCommand({ + """ + execution line 1 + execution line 2 + CODER_SETUP_ERRORYour IDE is expired, please update + execution line 3 + """ + }, false) + }.message + ) + + processSetupCommand({ + """ + execution line 1 + execution line 2 + CODER_SETUP_ERRORYour IDE is expired, please update + execution line 3 + """ + }, true) + + } +} \ No newline at end of file From 55870c48a1d5b87aab289f10e6cd194b8bd84496 Mon Sep 17 00:00:00 2001 From: Kirill Kalishev Date: Tue, 18 Feb 2025 19:15:01 -0500 Subject: [PATCH 2/5] review fixes --- .../coder/gateway/CoderRemoteConnectionHandle.kt | 13 ++++++------- .../com/coder/gateway/util/SetupCommandTest.kt | 14 +++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index b55f138d8..26b87336a 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -413,10 +413,9 @@ class CoderRemoteConnectionHandle { ) { if (setupCommand.isNotBlank()) { indicator.text = "Running setup command..." - processSetupCommand( - { exec(workspace, setupCommand) }, - ignoreSetupFailure - ) + processSetupCommand(ignoreSetupFailure) { + exec(workspace, setupCommand) + } } else { logger.info("No setup command to run on ${workspace.hostname}") } @@ -523,11 +522,11 @@ class CoderRemoteConnectionHandle { companion object { val logger = Logger.getInstance(CoderRemoteConnectionHandle::class.java.simpleName) fun processSetupCommand( - output: () -> String, - ignoreSetupFailure: Boolean + ignoreSetupFailure: Boolean, + execCommand: () -> String ) { try { - val errorText = output + val errorText = execCommand .invoke() .lines() .firstOrNull { it.contains(GATEWAY_SETUP_COMMAND_ERROR) } diff --git a/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt b/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt index 80cce97e7..62f4fa7b8 100644 --- a/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt +++ b/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt @@ -12,10 +12,10 @@ internal class SetupCommandTest { assertEquals( "Execution error", assertThrows { - processSetupCommand({ throw Exception("Execution error") }, false) + processSetupCommand(false) { throw Exception("Execution error") } }.message ) - processSetupCommand({ throw Exception("Execution error") }, true) + processSetupCommand(true) { throw Exception("Execution error") } } @Test @@ -23,25 +23,25 @@ internal class SetupCommandTest { assertEquals( "Your IDE is expired, please update", assertThrows { - processSetupCommand({ - """ + processSetupCommand(false) { + """ execution line 1 execution line 2 CODER_SETUP_ERRORYour IDE is expired, please update execution line 3 """ - }, false) + } }.message ) - processSetupCommand({ + processSetupCommand(true) { """ execution line 1 execution line 2 CODER_SETUP_ERRORYour IDE is expired, please update execution line 3 """ - }, true) + } } } \ No newline at end of file From eb5e2ac7b34939785c2e6900d6859de0da74aa16 Mon Sep 17 00:00:00 2001 From: Kirill Kalishev Date: Tue, 18 Feb 2025 19:37:06 -0500 Subject: [PATCH 3/5] custom exception class for setup command --- .../gateway/CoderRemoteConnectionHandle.kt | 36 +++++++++++++------ .../gateway/CoderSetupCommandException.kt | 7 ++++ .../messages/CoderGatewayBundle.properties | 1 + .../coder/gateway/util/SetupCommandTest.kt | 5 +-- 4 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/com/coder/gateway/CoderSetupCommandException.kt diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 26b87336a..790a2cd3a 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -161,25 +161,38 @@ class CoderRemoteConnectionHandle { ) logger.info("Adding ${parameters.ideName} for ${parameters.hostname}:${parameters.projectPath} to recent connections") recentConnectionsService.addRecentConnection(parameters.toRecentWorkspaceConnection()) + } catch (e: CoderSetupCommandException) { + logger.error("Failed to run setup command", e) + showConnectionErrorMessage( + e.message ?: "Unknown error", + "gateway.connector.coder.setup-command.failed", + ) } catch (e: Exception) { if (isCancellation(e)) { logger.info("Connection canceled due to ${e.javaClass.simpleName}") } else { logger.error("Failed to connect (will not retry)", e) - // The dialog will close once we return so write the error - // out into a new dialog. - ApplicationManager.getApplication().invokeAndWait { - Messages.showMessageDialog( - e.message ?: e.javaClass.simpleName ?: "Aborted", - CoderGatewayBundle.message("gateway.connector.coder.connection.failed"), - Messages.getErrorIcon(), - ) - } + showConnectionErrorMessage( + e.message ?: e.javaClass.simpleName ?: "Aborted", + "gateway.connector.coder.connection.failed" + ) } } } } + // The dialog will close once we return so write the error + // out into a new dialog. + private fun showConnectionErrorMessage(message: String, titleKey: String) { + ApplicationManager.getApplication().invokeAndWait { + Messages.showMessageDialog( + message, + CoderGatewayBundle.message(titleKey), + Messages.getErrorIcon(), + ) + } + } + /** * Return a new (non-EAP) IDE if we should update. */ @@ -521,6 +534,7 @@ class CoderRemoteConnectionHandle { companion object { val logger = Logger.getInstance(CoderRemoteConnectionHandle::class.java.simpleName) + @Throws(CoderSetupCommandException::class) fun processSetupCommand( ignoreSetupFailure: Boolean, execCommand: () -> String @@ -533,11 +547,11 @@ class CoderRemoteConnectionHandle { ?.let { it.substring(it.indexOf(GATEWAY_SETUP_COMMAND_ERROR) + GATEWAY_SETUP_COMMAND_ERROR.length).trim() } if (!errorText.isNullOrBlank()) { - throw Exception(errorText) + throw CoderSetupCommandException(errorText) } } catch (ex: Exception) { if (!ignoreSetupFailure) { - throw ex + throw CoderSetupCommandException(ex.message ?: "Unknown error", ex) } } } diff --git a/src/main/kotlin/com/coder/gateway/CoderSetupCommandException.kt b/src/main/kotlin/com/coder/gateway/CoderSetupCommandException.kt new file mode 100644 index 000000000..e43d92695 --- /dev/null +++ b/src/main/kotlin/com/coder/gateway/CoderSetupCommandException.kt @@ -0,0 +1,7 @@ +package com.coder.gateway + +class CoderSetupCommandException : Exception { + + constructor(message: String) : super(message) + constructor(message: String, cause: Throwable) : super(message, cause) +} \ No newline at end of file diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties index 4400eb893..b392c3983 100644 --- a/src/main/resources/messages/CoderGatewayBundle.properties +++ b/src/main/resources/messages/CoderGatewayBundle.properties @@ -49,6 +49,7 @@ gateway.connector.coder.connection.provider.title=Connecting to Coder workspace. gateway.connector.coder.connecting=Connecting... gateway.connector.coder.connecting.retry=Connecting (attempt {0})... gateway.connector.coder.connection.failed=Failed to connect +gateway.connector.coder.setup-command.failed=Failed to set up gateway.connector.coder.connecting.failed.retry=Failed to connect...retrying {0} gateway.connector.settings.data-directory.title=Data directory gateway.connector.settings.data-directory.comment=Directories are created \ diff --git a/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt b/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt index 62f4fa7b8..b237925b4 100644 --- a/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt +++ b/src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt @@ -1,6 +1,7 @@ package com.coder.gateway.util import com.coder.gateway.CoderRemoteConnectionHandle.Companion.processSetupCommand +import com.coder.gateway.CoderSetupCommandException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals @@ -11,7 +12,7 @@ internal class SetupCommandTest { fun executionErrors() { assertEquals( "Execution error", - assertThrows { + assertThrows { processSetupCommand(false) { throw Exception("Execution error") } }.message ) @@ -22,7 +23,7 @@ internal class SetupCommandTest { fun setupScriptError() { assertEquals( "Your IDE is expired, please update", - assertThrows { + assertThrows { processSetupCommand(false) { """ execution line 1 From c0b6fea68e5919f1da31d4db538fb225dad416d5 Mon Sep 17 00:00:00 2001 From: Kirill Kalishev Date: Tue, 18 Feb 2025 19:45:17 -0500 Subject: [PATCH 4/5] better title --- src/main/resources/messages/CoderGatewayBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties index b392c3983..f318012e0 100644 --- a/src/main/resources/messages/CoderGatewayBundle.properties +++ b/src/main/resources/messages/CoderGatewayBundle.properties @@ -49,7 +49,7 @@ gateway.connector.coder.connection.provider.title=Connecting to Coder workspace. gateway.connector.coder.connecting=Connecting... gateway.connector.coder.connecting.retry=Connecting (attempt {0})... gateway.connector.coder.connection.failed=Failed to connect -gateway.connector.coder.setup-command.failed=Failed to set up +gateway.connector.coder.setup-command.failed=Failed to set up backend IDE gateway.connector.coder.connecting.failed.retry=Failed to connect...retrying {0} gateway.connector.settings.data-directory.title=Data directory gateway.connector.settings.data-directory.comment=Directories are created \ From acc6a7f519fffbab9213ec02f0c1bf9203dda625 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 20 Feb 2025 16:23:31 -0600 Subject: [PATCH 5/5] changelog update --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14be9e3e..72a54920c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## Unreleased +### Added + +- Added functionality to show setup script error message to the end user. + ### Fixed - Fix bug where wildcard configs would not be written under certain conditions.