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 cbf5086

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 cbf5086

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-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)
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: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package com.coder.gateway.sdk
22

3+
34
import com.sun.net.httpserver.HttpExchange
45
import com.sun.net.httpserver.HttpHandler
56
import com.sun.net.httpserver.HttpServer
67
import org.zeroturnaround.exec.InvalidExitValueException
78
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
9+
import spock.lang.*
1210

1311
import java.nio.file.Files
1412
import java.nio.file.Path
@@ -137,7 +135,7 @@ class CoderCLIManagerTest extends Specification {
137135

138136
then:
139137
downloaded
140-
ccm.version().contains("Coder")
138+
CoderSemVer.isValidVersion(ccm.version())
141139

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

166164
cleanup:
167165
srv.stop(0)
@@ -172,7 +170,7 @@ class CoderCLIManagerTest extends Specification {
172170
def ccm = new CoderCLIManager(new URL("https://foo"), tmpdir.resolve("does-not-exist"))
173171

174172
when:
175-
ccm.version()
173+
ccm.login("token")
176174

177175
then:
178176
thrown(ProcessInitException)
@@ -193,7 +191,7 @@ class CoderCLIManagerTest extends Specification {
193191
downloaded
194192
ccm.localBinaryPath.toFile().readBytes() != "cli".getBytes()
195193
ccm.localBinaryPath.toFile().lastModified() > 0
196-
ccm.localBinaryPath.toFile().text == "#!/bin/sh\necho '$url'"
194+
ccm.localBinaryPath.toFile().text.contains(url)
197195

198196
cleanup:
199197
srv.stop(0)
@@ -207,6 +205,7 @@ class CoderCLIManagerTest extends Specification {
207205
when:
208206
def downloaded1 = ccm.downloadCLI()
209207
ccm.localBinaryPath.toFile().setLastModified(0)
208+
// Download will be skipped due to a 304.
210209
def downloaded2 = ccm.downloadCLI()
211210

212211
then:
@@ -231,8 +230,8 @@ class CoderCLIManagerTest extends Specification {
231230

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

237236
cleanup:
238237
srv1.stop(0)
@@ -249,7 +248,7 @@ class CoderCLIManagerTest extends Specification {
249248

250249
then:
251250
downloaded
252-
ccm.localBinaryPath.toFile().text == "#!/bin/sh\necho '${expected.replace("{{url}}", url)}'"
251+
ccm.localBinaryPath.toFile().text.contains(expected.replace("{{url}}", url))
253252

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

0 commit comments

Comments
 (0)
Please sign in to comment.