Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 10cdff2

Browse files
committedMay 9, 2023
Bypass download if CLI version matches
This is a bit faster than waiting for a 304 but more importantly it works if the user customizes the download source to be a place that does not have the same etag support that Coder does. The test bypasses Windows since we would need to create an executable there for the test. We could split out the exec and the parsing although I am not sure it is worth the noise. The part most likely to break between platforms is probably the exec, not the parsing?
1 parent 59dac3d commit 10cdff2

File tree

3 files changed

+61
-16
lines changed

3 files changed

+61
-16
lines changed
 

‎src/main/kotlin/com/coder/gateway/sdk/CoderCLIManager.kt

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

33
import com.coder.gateway.models.WorkspaceAgentModel
44
import com.coder.gateway.views.steps.CoderWorkspacesStepView
5+
import com.google.gson.Gson
56
import com.intellij.openapi.diagnostic.Logger
67
import org.zeroturnaround.exec.ProcessExecutor
78
import java.io.BufferedInputStream
@@ -277,10 +278,24 @@ class CoderCLIManager @JvmOverloads constructor(
277278
}
278279

279280
/**
280-
* Return the binary version.
281+
* Version output from the CLI's version command.
281282
*/
282-
fun version(): String {
283-
return exec("version")
283+
private data class Version(
284+
val version: String,
285+
)
286+
287+
/**
288+
* Return the binary version or null if it could not be determined.
289+
*/
290+
fun version(): String? {
291+
return try {
292+
val raw = exec("version", "--output", "json")
293+
val json = Gson().fromJson(raw, Version::class.java)
294+
json.version
295+
} catch (e: Exception) {
296+
logger.warn("Unable to determine CLI version: ${e.message}")
297+
null
298+
}
284299
}
285300

286301
private fun exec(vararg args: String): String {

‎src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,17 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
467467
appPropertiesService.setValue(CODER_URL_KEY, deploymentURL.toString())
468468
appPropertiesService.setValue(SESSION_TOKEN, token.first)
469469

470-
this.indicator.text = "Downloading Coder CLI..."
471-
cliManager.downloadCLI()
470+
// Short-circuit if we already have the expected version. This
471+
// lets us bypass the 304 which is slower and may not be
472+
// supported if the binary is downloaded from alternate sources.
473+
// For CLIs without the JSON output flag we will fall back to
474+
// the 304 method.
475+
if (cliManager.version() != clientService.buildVersion) {
476+
this.indicator.text = "Downloading Coder CLI..."
477+
cliManager.downloadCLI()
478+
} else {
479+
logger.info("Found existing binary at ${cliManager.localBinaryPath} with the expected version ${clientService.buildVersion}")
480+
}
472481

473482
this.indicator.text = "Authenticating Coder CLI..."
474483
cliManager.login(token.first)

‎src/test/groovy/CoderCLIManagerTest.groovy

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import com.sun.net.httpserver.HttpHandler
55
import com.sun.net.httpserver.HttpServer
66
import org.zeroturnaround.exec.InvalidExitValueException
77
import org.zeroturnaround.exec.ProcessInitException
8-
import spock.lang.Requires
9-
import spock.lang.Shared
10-
import spock.lang.Specification
11-
import spock.lang.Unroll
8+
import spock.lang.*
129

1310
import java.nio.file.Files
1411
import java.nio.file.Path
@@ -137,7 +134,7 @@ class CoderCLIManagerTest extends Specification {
137134

138135
then:
139136
downloaded
140-
ccm.version().contains("Coder")
137+
CoderSemVer.isValidVersion(ccm.version())
141138

142139
// Make sure login failures propagate correctly.
143140
when:
@@ -161,7 +158,7 @@ class CoderCLIManagerTest extends Specification {
161158
// The mock does not serve a binary that works on Windows so do not
162159
// actually execute. Checking the contents works just as well as proof
163160
// that the binary was correctly downloaded anyway.
164-
ccm.localBinaryPath.toFile().text == "#!/bin/sh\necho '$url'"
161+
ccm.localBinaryPath.toFile().text.contains(url)
165162

166163
cleanup:
167164
srv.stop(0)
@@ -172,7 +169,7 @@ class CoderCLIManagerTest extends Specification {
172169
def ccm = new CoderCLIManager(new URL("https://foo"), tmpdir.resolve("does-not-exist"))
173170

174171
when:
175-
ccm.version()
172+
ccm.login("token")
176173

177174
then:
178175
thrown(ProcessInitException)
@@ -193,7 +190,7 @@ class CoderCLIManagerTest extends Specification {
193190
downloaded
194191
ccm.localBinaryPath.toFile().readBytes() != "cli".getBytes()
195192
ccm.localBinaryPath.toFile().lastModified() > 0
196-
ccm.localBinaryPath.toFile().text == "#!/bin/sh\necho '$url'"
193+
ccm.localBinaryPath.toFile().text.contains(url)
197194

198195
cleanup:
199196
srv.stop(0)
@@ -207,6 +204,7 @@ class CoderCLIManagerTest extends Specification {
207204
when:
208205
def downloaded1 = ccm.downloadCLI()
209206
ccm.localBinaryPath.toFile().setLastModified(0)
207+
// Download will be skipped due to a 304.
210208
def downloaded2 = ccm.downloadCLI()
211209

212210
then:
@@ -231,8 +229,8 @@ class CoderCLIManagerTest extends Specification {
231229

232230
then:
233231
ccm1.localBinaryPath != ccm2.localBinaryPath
234-
ccm1.localBinaryPath.toFile().text == "#!/bin/sh\necho '$url1'"
235-
ccm2.localBinaryPath.toFile().text == "#!/bin/sh\necho '$url2'"
232+
ccm1.localBinaryPath.toFile().text.contains(url1)
233+
ccm2.localBinaryPath.toFile().text.contains(url2)
236234

237235
cleanup:
238236
srv1.stop(0)
@@ -249,7 +247,7 @@ class CoderCLIManagerTest extends Specification {
249247

250248
then:
251249
downloaded
252-
ccm.localBinaryPath.toFile().text == "#!/bin/sh\necho '${expected.replace("{{url}}", url)}'"
250+
ccm.localBinaryPath.toFile().text.contains(expected.replace("{{url}}", url))
253251

254252
cleanup:
255253
srv.stop(0)
@@ -429,4 +427,27 @@ class CoderCLIManagerTest extends Specification {
429427
"malformed-start-after-end",
430428
]
431429
}
430+
431+
@IgnoreIf({ os.windows })
432+
def "parses version"() {
433+
given:
434+
def ccm = new CoderCLIManager(new URL("https://test.coder.invalid"), tmpdir)
435+
Files.createDirectories(ccm.localBinaryPath.parent)
436+
437+
when:
438+
ccm.localBinaryPath.toFile().text = "#!/bin/sh\n$contents"
439+
ccm.localBinaryPath.toFile().setExecutable(true)
440+
441+
then:
442+
ccm.version() == expected
443+
444+
where:
445+
contents | expected
446+
"""echo '{"version": "1.0.0"}'""" | "1.0.0"
447+
"""echo '{"version": "1.0.0", "foo": true, "baz": 1}'""" | "1.0.0"
448+
"""echo '{"foo": true, "baz": 1}'""" | null
449+
"""echo '{"version: "1.0.0", "foo": true, "baz": 1}'""" | null
450+
"exit 0" | null
451+
"exit 1" | null
452+
}
432453
}

0 commit comments

Comments
 (0)
Please sign in to comment.