Skip to content

Commit 9c5a700

Browse files
authored
wrote ssh exception handler (#228)
1 parent 95eea9e commit 9c5a700

File tree

3 files changed

+96
-42
lines changed

3 files changed

+96
-42
lines changed

src/main/kotlin/com/vk/admstorm/ssh/SshConnectionService.kt

+54-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import com.intellij.remote.RemoteConnector
1212
import com.intellij.remote.RemoteCredentials
1313
import com.intellij.ssh.ConnectionBuilder
1414
import com.intellij.ssh.ExecBuilder
15-
import com.intellij.ssh.SshException
1615
import com.intellij.ssh.channels.SftpChannel
1716
import com.intellij.ssh.connectionBuilder
1817
import com.jetbrains.plugins.remotesdk.console.SshConfigConnector
@@ -24,9 +23,12 @@ import com.vk.admstorm.transfer.TransferService
2423
import com.vk.admstorm.utils.MySshUtils
2524
import com.vk.admstorm.utils.MyUtils.executeOnPooledThread
2625
import git4idea.util.GitUIUtil.code
26+
import net.schmizz.sshj.connection.channel.OpenFailException
2727
import net.schmizz.sshj.sftp.SFTPClient
28+
import net.schmizz.sshj.transport.TransportException
2829
import java.io.IOException
2930
import java.util.concurrent.TimeUnit
31+
import java.util.concurrent.TimeoutException
3032

3133
/**
3234
* Service responsible for connecting via SSH to the development server.
@@ -144,26 +146,16 @@ class SshConnectionService(private var myProject: Project) : Disposable {
144146

145147
override fun run(indicator: ProgressIndicator) {
146148
try {
147-
mySftpChannel = myConnectionBuilder!!.openSftpChannel(2)
148-
mySftpClient = MySshUtils.getSftpClient(mySftpChannel!!)
149+
SshHandler.handle {
150+
mySftpChannel = myConnectionBuilder!!.openSftpChannel(2)
151+
mySftpClient = MySshUtils.getSftpClient(mySftpChannel!!)
149152

150-
onSuccessful?.run()
151-
} catch (e: SshException) {
152-
if (!cancelled && e.message == "Cancelled by user") {
153-
LOG.warn("Cancelled by user", e)
154-
return
153+
onSuccessful?.run()
155154
}
156-
157-
val exceptionMessage = e.message
158-
?.removePrefix("java.net.SocketTimeoutException: ")
159-
?.replaceFirstChar { it.uppercaseChar() }
160-
?.plus("<br>")
161-
?: ""
162-
163-
val message =
164-
"${exceptionMessage}Plugin can try to automatically reset the Yubikey or you can do it yourself with ${
155+
} catch (ex: OpenFailException) {
156+
val message = "${ex.message}<br>" +
157+
"Plugin can try to automatically reset the Yubikey or you can do it yourself with " +
165158
code("ssh-agent")
166-
}"
167159

168160
AdmErrorNotification(message, true)
169161
.withTitle("Failed to connect to server")
@@ -185,7 +177,50 @@ class SshConnectionService(private var myProject: Project) : Disposable {
185177
})
186178
.show()
187179

188-
LOG.warn("Failed to connect", e)
180+
LOG.warn("Failed to connect", ex)
181+
} catch (ex: TimeoutException) {
182+
if (indicator.isCanceled) {
183+
LOG.info("Cancelled by user", ex)
184+
return
185+
}
186+
187+
AdmNotification("Touch the yubikey when it blinks while using AdmStorm")
188+
.withTitle("Yubikey waiting timeout")
189+
.withActions(AdmNotification.Action("Reconnect...") { _, notification ->
190+
notification.expire()
191+
connectWithConnector(connector, onSuccessful)
192+
}).show()
193+
194+
LOG.info("Yubikey waiting timeout", ex)
195+
} catch (ex: TransportException) {
196+
if (ex.message == null) {
197+
LOG.error("Transport exception:", ex.javaClass.name)
198+
return
199+
}
200+
201+
if (ex.message?.contains("HostKeyAlgorithms") == true) {
202+
AdmErrorNotification("Recheck your config or the documentation", true)
203+
.withTitle("Did you add HostKeyAlgorithms to your config?")
204+
.withActions(AdmNotification.Action("Reconnect...") { _, notification ->
205+
notification.expire()
206+
connectWithConnector(connector, onSuccessful)
207+
}).show()
208+
LOG.info("Not correct ssh config type", ex)
209+
return
210+
}
211+
212+
AdmErrorNotification("Check the documentation about connection to network", true)
213+
.withTitle("Did you miss your connection?")
214+
.withActions(AdmNotification.Action("Reconnect...") { _, notification ->
215+
notification.expire()
216+
connectWithConnector(connector, onSuccessful)
217+
}).show()
218+
LOG.info("Corporate access error", ex)
219+
} catch (ex: Exception) {
220+
val exceptionName = ex.javaClass.name
221+
LOG.error("Unhandled exception", ex)
222+
AdmErrorNotification("Unhandled exception $exceptionName").show()
223+
return
189224
}
190225
}
191226

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.vk.admstorm.ssh
2+
3+
import com.intellij.ssh.SshException
4+
5+
object SshHandler {
6+
inline fun <reified T> handle(call: () -> T): T {
7+
return try {
8+
call()
9+
} catch (ex: SshException) {
10+
throw unwrap(ex)
11+
}
12+
}
13+
14+
fun unwrap(ex: Exception): Throwable {
15+
var cause = ex.cause
16+
while (cause?.cause != null) {
17+
cause = cause.cause
18+
}
19+
20+
return cause ?: ex
21+
}
22+
}

src/main/kotlin/com/vk/admstorm/utils/MySshUtils.kt

+20-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.intellij.openapi.diagnostic.logger
66
import com.intellij.openapi.project.Project
77
import com.intellij.remote.ColoredRemoteProcessHandler
88
import com.intellij.ssh.ExecBuilder
9-
import com.intellij.ssh.SshException
109
import com.intellij.ssh.channels.SftpChannel
1110
import com.intellij.ssh.process.SshExecProcess
1211
import com.intellij.util.ReflectionUtil
@@ -16,9 +15,11 @@ import com.vk.admstorm.notifications.AdmErrorNotification
1615
import com.vk.admstorm.notifications.AdmNotification
1716
import com.vk.admstorm.notifications.AdmWarningNotification
1817
import com.vk.admstorm.ssh.SshConnectionService
18+
import com.vk.admstorm.ssh.SshHandler
1919
import com.vk.admstorm.ssh.YubikeyHandler
2020
import com.vk.admstorm.utils.MyUtils.executeOnPooledThread
2121
import git4idea.util.GitUIUtil.code
22+
import net.schmizz.sshj.connection.channel.OpenFailException
2223
import net.schmizz.sshj.sftp.SFTPClient
2324
import java.lang.reflect.Field
2425
import java.nio.charset.Charset
@@ -85,21 +86,25 @@ object MySshUtils {
8586
}
8687

8788
val process = try {
88-
execSync(builder)
89-
} catch (e: SshException) {
90-
handleSshException(project, e)
89+
SshHandler.handle {
90+
execSync(builder)
91+
}
92+
} catch (ex: OpenFailException) {
93+
handleSshException(project, ex)
9194
null
92-
} catch (e: TimeoutException) {
95+
} catch (ex: TimeoutException) {
9396
AdmWarningNotification("Don't forget to touch the yubikey if it blinks when using the AdmStorm plugin's features")
9497
.withTitle("Yubikey waiting timeout")
9598
.show()
96-
LOG.warn("Yubikey waiting timeout", e)
99+
LOG.info("Yubikey waiting timeout", ex)
97100
null
98-
} catch (e: IllegalStateException) {
99-
handleSshException(project, e)
101+
} catch (ex: IllegalStateException) {
102+
handleSshException(project, ex)
100103
null
101-
} catch (e: Exception) {
102-
handleSshException(project, e)
104+
} catch (ex: Exception) {
105+
val exceptionName = ex.javaClass.name
106+
LOG.error("Unhandled exception", ex)
107+
AdmErrorNotification("Unhandled exception $exceptionName").show()
103108
null
104109
} ?: return null
105110

@@ -109,17 +114,10 @@ object MySshUtils {
109114
return ColoredRemoteProcessHandler(process, firstLine, Charset.defaultCharset())
110115
}
111116

112-
private fun handleSshException(project: Project, e: Exception) {
113-
val exceptionMessage = e.message
114-
?.removePrefix("java.net.SocketTimeoutException: ")
115-
?.replaceFirstChar { it.uppercaseChar() }
116-
?.plus("<br>")
117-
?: ""
118-
119-
val message = """
120-
${exceptionMessage}Plugin can try to automatically reset the Yubikey and reconnect or you can do it
121-
yourself with ${code("ssh-agent")} and push 'Reconnect' button.
122-
""".trimIndent()
117+
private fun handleSshException(project: Project, ex: Exception) {
118+
val message = "${ex.message}<br>" +
119+
"Plugin can try to automatically reset the Yubikey or you can do it yourself with " +
120+
code("ssh-agent")
123121

124122
AdmWarningNotification(message)
125123
.withTitle("SSH connection lost")
@@ -144,8 +142,7 @@ object MySshUtils {
144142
}
145143
)
146144
.show()
147-
148-
LOG.warn("Unexpected exception for execSync(builder)", e)
145+
LOG.warn("Unexpected exception for execSync(builder)", ex)
149146
}
150147

151148
private fun execSync(builder: ExecBuilder): SshExecProcess {

0 commit comments

Comments
 (0)