Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Encapsulate version in checks in features object
  • Loading branch information
code-asher committed Feb 23, 2024
commit 1dece088ac5c0f547e8b0520162f47e6836c2ab7
33 changes: 27 additions & 6 deletions src/main/kotlin/com/coder/gateway/cli/CoderCLIManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ fun ensureCLI(
return if (cliMatches == null && dataCLIMatches != null) dataCLI else cli
}

/**
* The supported features of the CLI.
*/
data class Features (
val disableAutostart: Boolean = false,
)

/**
* Manage the CLI for a single deployment.
*/
Expand Down Expand Up @@ -200,10 +207,10 @@ class CoderCLIManager(
/**
* Configure SSH to use this binary.
*
* This can take a version for testing purposes only.
* This can take supported features for testing purposes only.
*/
fun configSsh(workspaceNames: List<String>, version: SemVer? = tryVersion()) {
writeSSHConfig(modifySSHConfig(readSSHConfig(), workspaceNames, version))
fun configSsh(workspaceNames: List<String>, feats: Features = features) {
writeSSHConfig(modifySSHConfig(readSSHConfig(), workspaceNames, feats))
}

/**
Expand All @@ -221,8 +228,11 @@ class CoderCLIManager(
* Given an existing SSH config modify it to add or remove the config for
* this deployment and return the modified config or null if it does not
* need to be modified.
*
* If features are not provided, calculate them based on the binary
* version.
*/
private fun modifySSHConfig(contents: String?, workspaceNames: List<String>, version: SemVer?): String? {
private fun modifySSHConfig(contents: String?, workspaceNames: List<String>, feats: Features): String? {
val host = deploymentURL.safeHost()
val startBlock = "# --- START CODER JETBRAINS $host"
val endBlock = "# --- END CODER JETBRAINS $host"
Expand All @@ -233,8 +243,7 @@ class CoderCLIManager(
if (settings.headerCommand.isNotBlank()) "--header-command" else null,
if (settings.headerCommand.isNotBlank()) escapeSubcommand(settings.headerCommand) else null,
"ssh", "--stdio",
// Autostart on SSH was added in 2.5.0.
if (settings.disableAutostart && version != null && version >= SemVer(2, 5, 0)) "--disable-autostart" else null)
if (settings.disableAutostart && feats.disableAutostart) "--disable-autostart" else null)
val blockContent = workspaceNames.joinToString(
System.lineSeparator(),
startBlock + System.lineSeparator(),
Expand Down Expand Up @@ -392,6 +401,18 @@ class CoderCLIManager(
return stdout
}

val features: Features
get() {
val version = tryVersion()
return if (version == null) {
Features()
} else {
Features(
// Autostart with SSH was added in 2.5.0.
disableAutostart = version >= SemVer(2, 5, 0))
}
}

companion object {
val logger = Logger.getInstance(CoderCLIManager::class.java.simpleName)

Expand Down
44 changes: 33 additions & 11 deletions src/test/kotlin/com/coder/gateway/cli/CoderCLIManagerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ import kotlin.test.assertTrue

internal class CoderCLIManagerTest {
private fun mkbin(version: String): String {
return listOf("#!/bin/sh", """echo '{"version": "${version}"}'""")
return listOf("#!/bin/sh", """echo '{"version": "$version"}'""")
.joinToString("\n")
}

private fun mockServer(errorCode: Int = 0): Pair<HttpServer, URL> {
private fun mockServer(errorCode: Int = 0, version: String? = null): Pair<HttpServer, URL> {
val srv = HttpServer.create(InetSocketAddress(0), 0)
srv.createContext("/") {exchange ->
var code = HttpURLConnection.HTTP_OK
// TODO: Is there some simple way to create an executable file on
// Windows without having to execute something to generate said
// executable or having to commit one to the repo?
var response = mkbin("${srv.address.port}.0.0")
var response = mkbin(version ?: "${srv.address.port}.0.0")
val eTags = exchange.requestHeaders["If-None-Match"]
if (exchange.requestURI.path == "/bin/override") {
code = HttpURLConnection.HTTP_OK
Expand Down Expand Up @@ -241,7 +241,7 @@ internal class CoderCLIManagerTest {
val remove: String,
val headerCommand: String?,
val disableAutostart: Boolean = false,
val version: SemVer? = null,
val features: Features? = null,
)

@Test
Expand All @@ -265,11 +265,8 @@ internal class CoderCLIManagerTest {
} else {
SSHTest(listOf("header"), null, "header-command", "blank", "my-header-command --url=\"\$CODER_URL\" --test=\"foo bar\" --literal='\$CODER_URL'")
},
SSHTest(listOf("foo"), null, "disable-autostart", "blank", null, true, SemVer(2, 5, 0)),
SSHTest(listOf("foo"), null, "disable-autostart", "blank", null, true, SemVer(3, 5, 0)),
SSHTest(listOf("foo"), null, "no-disable-autostart", "blank", null, true, SemVer(2, 4, 9)),
SSHTest(listOf("foo"), null, "no-disable-autostart", "blank", null, true, SemVer(1, 0, 1)),
SSHTest(listOf("foo"), null, "no-disable-autostart", "blank", null, true),
SSHTest(listOf("foo"), null, "disable-autostart", "blank", null, true, Features(true)),
SSHTest(listOf("foo"), null, "no-disable-autostart", "blank", null, true, Features(false)),
)

val newlineRe = "\r?\n".toRegex()
Expand Down Expand Up @@ -299,12 +296,12 @@ internal class CoderCLIManagerTest {
.replace("/tmp/coder-gateway/test.coder.invalid/coder-linux-amd64", escape(ccm.localBinaryPath.toString()))

// Add workspaces.
ccm.configSsh(it.workspaces, it.version)
ccm.configSsh(it.workspaces, it.features ?: Features())

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

// Remove configuration.
ccm.configSsh(emptyList(), it.version)
ccm.configSsh(emptyList(), it.features ?: Features())

// Remove is the configuration we expect after removing.
assertEquals(
Expand Down Expand Up @@ -554,6 +551,31 @@ internal class CoderCLIManagerTest {
srv.stop(0)
}

@Test
fun testFeatures() {
if (getOS() == OS.WINDOWS) {
return // Cannot execute mock binaries on Windows.
}

val tests = listOf(
Pair("2.5.0", Features(true)),
Pair("4.9.0", Features(true)),
Pair("2.4.9", Features(false)),
Pair("1.0.1", Features(false)),
)

tests.forEach {
val (srv, url) = mockServer(version = it.first)
val ccm = CoderCLIManager(url, CoderSettings(CoderSettingsState(
dataDirectory = tmpdir.resolve("features").toString()))
)
assertEquals(true, ccm.download())
assertEquals(it.second, ccm.features, "version: ${it.first}")

srv.stop(0)
}
}

companion object {
private val tmpdir: Path = Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test/cli-manager")

Expand Down