Skip to content

Commit ab45c41

Browse files
authored
Add extra SSH options setting (#382)
1 parent 3096448 commit ab45c41

File tree

7 files changed

+91
-23
lines changed

7 files changed

+91
-23
lines changed

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

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

33
import com.coder.gateway.services.CoderSettingsService
44
import com.coder.gateway.services.CoderSettingsStateService
5+
import com.coder.gateway.settings.CODER_SSH_CONFIG_OPTIONS
56
import com.coder.gateway.util.canCreateDirectory
67
import com.intellij.openapi.components.service
78
import com.intellij.openapi.options.BoundConfigurable
@@ -109,6 +110,13 @@ class CoderSettingsConfigurable : BoundConfigurable("Coder") {
109110
CoderGatewayBundle.message("gateway.connector.settings.disable-autostart.comment")
110111
)
111112
}.layout(RowLayout.PARENT_GRID)
113+
row(CoderGatewayBundle.message("gateway.connector.settings.ssh-config-options.title")) {
114+
textArea().resizableColumn().align(AlignX.FILL)
115+
.bindText(state::sshConfigOptions)
116+
.comment(
117+
CoderGatewayBundle.message("gateway.connector.settings.ssh-config-options.comment", CODER_SSH_CONFIG_OPTIONS)
118+
)
119+
}.layout(RowLayout.PARENT_GRID)
112120
}
113121
}
114122

src/main/kotlin/com/coder/gateway/cli/CoderCLIManager.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ class CoderCLIManager(
255255
if (settings.headerCommand.isNotBlank()) escapeSubcommand(settings.headerCommand) else null,
256256
"ssh", "--stdio",
257257
if (settings.disableAutostart && feats.disableAutostart) "--disable-autostart" else null)
258+
val extraConfig = if (settings.sshConfigOptions.isNotBlank()) {
259+
"\n" + settings.sshConfigOptions.prependIndent(" ")
260+
} else ""
258261
val blockContent = workspaceNames.joinToString(
259262
System.lineSeparator(),
260263
startBlock + System.lineSeparator(),
@@ -268,7 +271,9 @@ class CoderCLIManager(
268271
UserKnownHostsFile /dev/null
269272
LogLevel ERROR
270273
SetEnv CODER_SSH_SESSION_TYPE=JetBrains
271-
""".trimIndent().replace("\n", System.lineSeparator())
274+
""".trimIndent()
275+
.plus(extraConfig)
276+
.replace("\n", System.lineSeparator())
272277
})
273278

274279
if (contents == null) {

src/main/kotlin/com/coder/gateway/settings/CoderSettings.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import java.nio.file.Files
1414
import java.nio.file.Path
1515
import java.nio.file.Paths
1616

17+
const val CODER_SSH_CONFIG_OPTIONS = "CODER_SSH_CONFIG_OPTIONS";
18+
1719
open class CoderSettingsState(
1820
// Used to download the Coder CLI which is necessary to proxy SSH
1921
// connections. The If-None-Match header will be set to the SHA1 of the CLI
@@ -57,6 +59,8 @@ open class CoderSettingsState(
5759
// around issues on macOS where it periodically wakes and Gateway
5860
// reconnects, keeping the workspace constantly up.
5961
open var disableAutostart: Boolean = getOS() == OS.MAC,
62+
// Extra SSH config options.
63+
open var sshConfigOptions: String = "",
6064
)
6165

6266
/**
@@ -113,6 +117,12 @@ open class CoderSettings(
113117
val disableAutostart: Boolean
114118
get() = state.disableAutostart
115119

120+
/**
121+
* Extra SSH config to append to each host block.
122+
*/
123+
val sshConfigOptions: String
124+
get() = state.sshConfigOptions.ifBlank { env.get(CODER_SSH_CONFIG_OPTIONS) }
125+
116126
/**
117127
* Where the specified deployment should put its data.
118128
*/

src/main/resources/messages/CoderGatewayBundle.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,9 @@ gateway.connector.settings.disable-autostart.comment=Checking this box will \
117117
cause the plugin to configure the CLI with --disable-autostart. You must go \
118118
through the IDE selection again for the plugin to reconfigure the CLI with \
119119
this setting.
120+
gateway.connector.settings.ssh-config-options.title=SSH config options
121+
gateway.connector.settings.ssh-config-options.comment=Extra SSH config options \
122+
to use when connecting to a workspace. This text will be appended as-is to \
123+
the SSH configuration block for each workspace. If left blank the \
124+
environment variable {0} will be used, if set.
125+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# --- START CODER JETBRAINS test.coder.invalid
2+
Host coder-jetbrains--extra--test.coder.invalid
3+
ProxyCommand /tmp/coder-gateway/test.coder.invalid/coder-linux-amd64 --global-config /tmp/coder-gateway/test.coder.invalid/config ssh --stdio extra
4+
ConnectTimeout 0
5+
StrictHostKeyChecking no
6+
UserKnownHostsFile /dev/null
7+
LogLevel ERROR
8+
SetEnv CODER_SSH_SESSION_TYPE=JetBrains
9+
ServerAliveInterval 5
10+
ServerAliveCountMax 3
11+
# --- END CODER JETBRAINS test.coder.invalid

src/test/kotlin/com/coder/gateway/cli/CoderCLIManagerTest.kt

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package com.coder.gateway.cli
33
import com.coder.gateway.cli.ex.MissingVersionException
44
import com.coder.gateway.cli.ex.ResponseException
55
import com.coder.gateway.cli.ex.SSHConfigFormatException
6-
import com.coder.gateway.settings.CoderSettingsState
6+
import com.coder.gateway.settings.CODER_SSH_CONFIG_OPTIONS
77
import com.coder.gateway.settings.CoderSettings
8+
import com.coder.gateway.settings.CoderSettingsState
9+
import com.coder.gateway.settings.Environment
810
import com.coder.gateway.util.InvalidVersionException
911
import com.coder.gateway.util.OS
1012
import com.coder.gateway.util.SemVer
@@ -238,34 +240,43 @@ internal class CoderCLIManagerTest {
238240
val input: String?,
239241
val output: String,
240242
val remove: String,
241-
val headerCommand: String?,
243+
val headerCommand: String = "",
242244
val disableAutostart: Boolean = false,
243-
val features: Features? = null,
245+
val features: Features = Features(),
246+
val extraConfig: String = "",
247+
val env: Environment = Environment(),
244248
)
245249

246250
@Test
247251
fun testConfigureSSH() {
252+
val extraConfig = listOf(
253+
"ServerAliveInterval 5",
254+
"ServerAliveCountMax 3").joinToString(System.lineSeparator())
248255
val tests = listOf(
249-
SSHTest(listOf("foo", "bar"), null,"multiple-workspaces", "blank", null),
250-
SSHTest(listOf("foo", "bar"), null,"multiple-workspaces", "blank", null),
251-
SSHTest(listOf("foo-bar"), "blank", "append-blank", "blank", null),
252-
SSHTest(listOf("foo-bar"), "blank-newlines", "append-blank-newlines", "blank", null),
253-
SSHTest(listOf("foo-bar"), "existing-end", "replace-end", "no-blocks", null),
254-
SSHTest(listOf("foo-bar"), "existing-end-no-newline", "replace-end-no-newline", "no-blocks", null),
255-
SSHTest(listOf("foo-bar"), "existing-middle", "replace-middle", "no-blocks", null),
256-
SSHTest(listOf("foo-bar"), "existing-middle-and-unrelated", "replace-middle-ignore-unrelated", "no-related-blocks", null),
257-
SSHTest(listOf("foo-bar"), "existing-only", "replace-only", "blank", null),
258-
SSHTest(listOf("foo-bar"), "existing-start", "replace-start", "no-blocks", null),
259-
SSHTest(listOf("foo-bar"), "no-blocks", "append-no-blocks", "no-blocks", null),
260-
SSHTest(listOf("foo-bar"), "no-related-blocks", "append-no-related-blocks", "no-related-blocks", null),
261-
SSHTest(listOf("foo-bar"), "no-newline", "append-no-newline", "no-blocks", null),
256+
SSHTest(listOf("foo", "bar"), null, "multiple-workspaces", "blank"),
257+
SSHTest(listOf("foo", "bar"), null, "multiple-workspaces", "blank"),
258+
SSHTest(listOf("foo-bar"), "blank", "append-blank", "blank"),
259+
SSHTest(listOf("foo-bar"), "blank-newlines", "append-blank-newlines", "blank"),
260+
SSHTest(listOf("foo-bar"), "existing-end", "replace-end", "no-blocks"),
261+
SSHTest(listOf("foo-bar"), "existing-end-no-newline", "replace-end-no-newline", "no-blocks"),
262+
SSHTest(listOf("foo-bar"), "existing-middle", "replace-middle", "no-blocks"),
263+
SSHTest(listOf("foo-bar"), "existing-middle-and-unrelated", "replace-middle-ignore-unrelated", "no-related-blocks"),
264+
SSHTest(listOf("foo-bar"), "existing-only", "replace-only", "blank"),
265+
SSHTest(listOf("foo-bar"), "existing-start", "replace-start", "no-blocks"),
266+
SSHTest(listOf("foo-bar"), "no-blocks", "append-no-blocks", "no-blocks"),
267+
SSHTest(listOf("foo-bar"), "no-related-blocks", "append-no-related-blocks", "no-related-blocks"),
268+
SSHTest(listOf("foo-bar"), "no-newline", "append-no-newline", "no-blocks"),
262269
if (getOS() == OS.WINDOWS) {
263270
SSHTest(listOf("header"), null, "header-command-windows", "blank", """"C:\Program Files\My Header Command\HeaderCommand.exe" --url="%CODER_URL%" --test="foo bar"""")
264271
} else {
265272
SSHTest(listOf("header"), null, "header-command", "blank", "my-header-command --url=\"\$CODER_URL\" --test=\"foo bar\" --literal='\$CODER_URL'")
266273
},
267-
SSHTest(listOf("foo"), null, "disable-autostart", "blank", null, true, Features(true)),
268-
SSHTest(listOf("foo"), null, "no-disable-autostart", "blank", null, true, Features(false)),
274+
SSHTest(listOf("foo"), null, "disable-autostart", "blank", "", true, Features(true)),
275+
SSHTest(listOf("foo"), null, "no-disable-autostart", "blank", "", true, Features(false)),
276+
SSHTest(listOf("extra"), null, "extra-config", "blank",
277+
extraConfig = extraConfig),
278+
SSHTest(listOf("extra"), null, "extra-config", "blank",
279+
env = Environment(mapOf(CODER_SSH_CONFIG_OPTIONS to extraConfig))),
269280
)
270281

271282
val newlineRe = "\r?\n".toRegex()
@@ -274,8 +285,10 @@ internal class CoderCLIManagerTest {
274285
val settings = CoderSettings(CoderSettingsState(
275286
disableAutostart = it.disableAutostart,
276287
dataDirectory = tmpdir.resolve("configure-ssh").toString(),
277-
headerCommand = it.headerCommand ?: ""),
278-
sshConfigPath = tmpdir.resolve(it.input + "_to_" + it.output + ".conf"))
288+
headerCommand = it.headerCommand,
289+
sshConfigOptions = it.extraConfig),
290+
sshConfigPath = tmpdir.resolve(it.input + "_to_" + it.output + ".conf"),
291+
env = it.env)
279292

280293
val ccm = CoderCLIManager(URL("https://test.coder.invalid"), settings)
281294

@@ -295,12 +308,12 @@ internal class CoderCLIManagerTest {
295308
.replace("/tmp/coder-gateway/test.coder.invalid/coder-linux-amd64", escape(ccm.localBinaryPath.toString()))
296309

297310
// Add workspaces.
298-
ccm.configSsh(it.workspaces.toSet(), it.features ?: Features())
311+
ccm.configSsh(it.workspaces.toSet(), it.features)
299312

300313
assertEquals(expectedConf, settings.sshConfigPath.toFile().readText())
301314

302315
// Remove configuration.
303-
ccm.configSsh(emptySet(), it.features ?: Features())
316+
ccm.configSsh(emptySet(), it.features)
304317

305318
// Remove is the configuration we expect after removing.
306319
assertEquals(

src/test/kotlin/com/coder/gateway/settings/CoderSettingsTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ internal class CoderSettingsTest {
170170
assertEquals(Pair("http://test.gateway.coder.com$expected", "fake-token"), got)
171171
}
172172

173+
@Test
174+
fun testSSHConfigOptions() {
175+
var settings = CoderSettings(CoderSettingsState(sshConfigOptions = "ssh config options from state"))
176+
assertEquals("ssh config options from state", settings.sshConfigOptions)
177+
178+
settings = CoderSettings(CoderSettingsState(),
179+
env = Environment(mapOf(CODER_SSH_CONFIG_OPTIONS to "ssh config options from env")))
180+
assertEquals("ssh config options from env", settings.sshConfigOptions)
181+
182+
// State has precedence.
183+
settings = CoderSettings(CoderSettingsState(sshConfigOptions = "ssh config options from state"),
184+
env = Environment(mapOf(CODER_SSH_CONFIG_OPTIONS to "ssh config options from env")))
185+
assertEquals("ssh config options from state", settings.sshConfigOptions)
186+
}
187+
173188
@Test
174189
fun testSettings() {
175190
// Make sure the remaining settings are being conveyed.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy