Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/kotlin/com/coder/gateway/CoderGatewayConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
60 changes: 46 additions & 14 deletions src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -160,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.
*/
Expand Down Expand Up @@ -412,18 +426,15 @@ class CoderRemoteConnectionHandle {
) {
if (setupCommand.isNotBlank()) {
indicator.text = "Running setup command..."
try {
processSetupCommand(ignoreSetupFailure) {
exec(workspace, setupCommand)
} catch (ex: Exception) {
if (!ignoreSetupFailure) {
throw ex
}
}
} 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.
Expand Down Expand Up @@ -523,5 +534,26 @@ class CoderRemoteConnectionHandle {

companion object {
val logger = Logger.getInstance(CoderRemoteConnectionHandle::class.java.simpleName)
@Throws(CoderSetupCommandException::class)
fun processSetupCommand(
ignoreSetupFailure: Boolean,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a comment on this PR just a note to self about how we're doing this in general: don't love us drilling down this Boolean flag for ignoring errors.

execCommand: () -> String
) {
try {
val errorText = execCommand
.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 CoderSetupCommandException(errorText)
}
} catch (ex: Exception) {
if (!ignoreSetupFailure) {
throw CoderSetupCommandException(ex.message ?: "Unknown error", ex)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.coder.gateway

class CoderSetupCommandException : Exception {

constructor(message: String) : super(message)
constructor(message: String, cause: Throwable) : super(message, cause)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a more simpler approach:

class CoderSetupCommandException(message: String, cause: Throwable? = null) : Exception(message, cause)

}
1 change: 1 addition & 0 deletions src/main/resources/messages/CoderGatewayBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 \
Expand Down
48 changes: 48 additions & 0 deletions src/test/kotlin/com/coder/gateway/util/SetupCommandTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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

internal class SetupCommandTest {

@Test
fun executionErrors() {
assertEquals(
"Execution error",
assertThrows<CoderSetupCommandException> {
processSetupCommand(false) { throw Exception("Execution error") }
}.message
)
processSetupCommand(true) { throw Exception("Execution error") }
}

@Test
fun setupScriptError() {
assertEquals(
"Your IDE is expired, please update",
assertThrows<CoderSetupCommandException> {
processSetupCommand(false) {
"""
execution line 1
execution line 2
CODER_SETUP_ERRORYour IDE is expired, please update
execution line 3
"""
}
}.message
)

processSetupCommand(true) {
"""
execution line 1
execution line 2
CODER_SETUP_ERRORYour IDE is expired, please update
execution line 3
"""
}

}
}
Loading