Skip to content

Commit 6636f31

Browse files
committed
Store binary in data directory
The @jvmoverloads annotations make Kotlin generate overloads otherwise the test code thinks the optional arguments are not optional.
1 parent 1ac681c commit 6636f31

File tree

2 files changed

+115
-36
lines changed

2 files changed

+115
-36
lines changed

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

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,44 +22,35 @@ import javax.xml.bind.annotation.adapters.HexBinaryAdapter
2222
/**
2323
* Manage the CLI for a single deployment.
2424
*/
25-
class CoderCLIManager(deployment: URL) {
25+
class CoderCLIManager @JvmOverloads constructor(deployment: URL, destinationDir: Path = getDataDir()) {
2626
private var remoteBinaryUrl: URL
2727
var localBinaryPath: Path
28-
private var binaryNamePrefix: String
29-
private var destinationDir: Path
3028

3129
init {
32-
// TODO: Should use a persistent path to avoid needing to download on
33-
// each restart.
34-
destinationDir = Path.of(System.getProperty("java.io.tmpdir"))
35-
.resolve("coder-gateway").resolve(deployment.host)
36-
val os = getOS()
37-
binaryNamePrefix = getCoderCLIForOS(os, getArch())
38-
val binaryName = if (os == OS.WINDOWS) "$binaryNamePrefix.exe" else binaryNamePrefix
30+
val binaryName = getCoderCLIForOS(getOS(), getArch())
3931
remoteBinaryUrl = URL(
4032
deployment.protocol,
4133
deployment.host,
4234
deployment.port,
4335
"/bin/$binaryName"
4436
)
45-
localBinaryPath = destinationDir.resolve(binaryName)
37+
localBinaryPath = destinationDir.resolve(deployment.host).resolve(binaryName)
4638
}
47-
4839
/**
49-
* Return the name of the binary (sans extension) for the provided OS and
40+
* Return the name of the binary (with extension) for the provided OS and
5041
* architecture.
5142
*/
5243
private fun getCoderCLIForOS(os: OS?, arch: Arch?): String {
5344
logger.info("Resolving binary for $os $arch")
5445
if (os == null) {
5546
logger.error("Could not resolve client OS and architecture, defaulting to WINDOWS AMD64")
56-
return "coder-windows-amd64"
47+
return "coder-windows-amd64.exe"
5748
}
5849
return when (os) {
5950
OS.WINDOWS -> when (arch) {
60-
Arch.AMD64 -> "coder-windows-amd64"
61-
Arch.ARM64 -> "coder-windows-arm64"
62-
else -> "coder-windows-amd64"
51+
Arch.AMD64 -> "coder-windows-amd64.exe"
52+
Arch.ARM64 -> "coder-windows-arm64.exe"
53+
else -> "coder-windows-amd64.exe"
6354
}
6455

6556
OS.LINUX -> when (arch) {
@@ -93,7 +84,7 @@ class CoderCLIManager(deployment: URL) {
9384
when (conn.responseCode) {
9485
200 -> {
9586
logger.info("Downloading binary to ${localBinaryPath.toAbsolutePath()}")
96-
Files.createDirectories(destinationDir)
87+
Files.createDirectories(localBinaryPath.parent)
9788
conn.inputStream.use {
9889
Files.copy(
9990
if (conn.contentEncoding == "gzip") GZIPInputStream(it) else it,
@@ -207,6 +198,7 @@ class CoderCLIManager(deployment: URL) {
207198
* Return the config directory used by the CLI.
208199
*/
209200
@JvmStatic
201+
@JvmOverloads
210202
fun getConfigDir(env: Environment = Environment()): Path {
211203
var dir = env.get("CODER_CONFIG_DIR")
212204
if (!dir.isNullOrBlank()) {
@@ -226,6 +218,25 @@ class CoderCLIManager(deployment: URL) {
226218
}
227219
}
228220
}
221+
222+
/**
223+
* Return the data directory.
224+
*/
225+
@JvmStatic
226+
@JvmOverloads
227+
fun getDataDir(env: Environment = Environment()): Path {
228+
return when (getOS()) {
229+
OS.WINDOWS -> Paths.get(env.get("LOCALAPPDATA"), "coder-gateway")
230+
OS.MAC -> Paths.get(env.get("HOME"), "Library/Application Support/coder-gateway")
231+
else -> {
232+
val dir = env.get("XDG_DATA_HOME")
233+
if (!dir.isNullOrBlank()) {
234+
return Paths.get(dir, "coder-gateway")
235+
}
236+
return Paths.get(env.get("HOME"), ".local/share/coder-gateway")
237+
}
238+
}
239+
}
229240
}
230241
}
231242

src/test/groovy/CoderCLIManagerTest.groovy

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,38 @@ import java.nio.file.Path
88

99
@Unroll
1010
class CoderCLIManagerTest extends spock.lang.Specification {
11-
// TODO: Probably not good to depend on dev.coder.com being up...should use
12-
// a mock? Or spin up a Coder deployment in CI?
11+
/**
12+
* Create a CLI manager pointing to the URL in the environment or to the
13+
default URLs.
14+
*
15+
* @TODO: Implement a mock.
16+
*/
17+
def createCLIManager(Boolean alternate = false) {
18+
var url = System.getenv("CODER_GATEWAY_TEST_DEPLOYMENT")
19+
if (url == null) {
20+
url = "https://dev.coder.com"
21+
}
22+
if (alternate) {
23+
url = System.getenv("CODER_GATEWAY_TEST_DEPLOYMENT_ALT")
24+
if (url == null) {
25+
url = "https://oss.demo.coder.com"
26+
}
27+
}
28+
var tmpdir = Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test")
29+
return new CoderCLIManager(new URL(url), tmpdir)
30+
}
31+
32+
def "defaults to a sub-directory in the data directory"() {
33+
given:
34+
def ccm = new CoderCLIManager(new URL("https://test.coder.invalid"))
35+
36+
expect:
37+
ccm.localBinaryPath.getParent() == CoderCLIManager.getDataDir().resolve("test.coder.invalid")
38+
}
39+
1340
def "downloads a working cli"() {
1441
given:
15-
def ccm = new CoderCLIManager(new URL("https://dev.coder.com"))
42+
def ccm = createCLIManager()
1643
ccm.localBinaryPath.getParent().toFile().deleteDir()
1744

1845
when:
@@ -25,7 +52,7 @@ class CoderCLIManagerTest extends spock.lang.Specification {
2552

2653
def "overwrites cli if incorrect version"() {
2754
given:
28-
def ccm = new CoderCLIManager(new URL("https://dev.coder.com"))
55+
def ccm = createCLIManager()
2956
Files.createDirectories(ccm.localBinaryPath.getParent())
3057
ccm.localBinaryPath.toFile().write("cli")
3158

@@ -39,7 +66,7 @@ class CoderCLIManagerTest extends spock.lang.Specification {
3966

4067
def "skips cli download if it already exists"() {
4168
given:
42-
def ccm = new CoderCLIManager(new URL("https://dev.coder.com"))
69+
def ccm = createCLIManager()
4370

4471
when:
4572
ccm.downloadCLI()
@@ -52,8 +79,8 @@ class CoderCLIManagerTest extends spock.lang.Specification {
5279

5380
def "does not clobber other deployments"() {
5481
given:
55-
def ccm1 = new CoderCLIManager(new URL("https://oss.demo.coder.com"))
56-
def ccm2 = new CoderCLIManager(new URL("https://dev.coder.com"))
82+
def ccm1 = createCLIManager(true)
83+
def ccm2 = createCLIManager()
5784

5885
when:
5986
ccm1.downloadCLI()
@@ -63,25 +90,27 @@ class CoderCLIManagerTest extends spock.lang.Specification {
6390
ccm1.localBinaryPath != ccm2.localBinaryPath
6491
}
6592

93+
def testEnv = [
94+
"APPDATA" : "/tmp/coder-gateway-test/appdata",
95+
"LOCALAPPDATA" : "/tmp/coder-gateway-test/localappdata",
96+
"HOME" : "/tmp/coder-gateway-test/home",
97+
"XDG_CONFIG_HOME" : "/tmp/coder-gateway-test/xdg-config",
98+
"XDG_DATA_HOME" : "/tmp/coder-gateway-test/xdg-data",
99+
"CODER_CONFIG_DIR": "",
100+
]
101+
66102
/**
67103
* Get a config dir using default environment variable values.
68104
*/
69105
def configDir(Map<String, String> env = [:]) {
70-
return CoderCLIManager.getConfigDir(new Environment([
71-
"APPDATA" : "/tmp/coder-gateway-test/appdata",
72-
"HOME" : "/tmp/coder-gateway-test/home",
73-
"XDG_CONFIG_HOME" : "/tmp/coder-gateway-test/xdg",
74-
"CODER_CONFIG_DIR": "",
75-
] + env))
106+
return CoderCLIManager.getConfigDir(new Environment(testEnv + env))
76107
}
77108

78109
// Mostly just a sanity check to make sure the default System.getenv runs
79110
// without throwing any errors.
80111
def "gets config dir"() {
81112
when:
82-
def dir = CoderCLIManager.getConfigDir(new Environment([
83-
"CODER_CONFIG_DIR": "",
84-
]))
113+
def dir = CoderCLIManager.getConfigDir()
85114

86115
then:
87116
dir.toString().contains("coderv2")
@@ -97,13 +126,13 @@ class CoderCLIManagerTest extends spock.lang.Specification {
97126
}
98127

99128
@Requires({ os.linux })
100-
def "gets config dir from XDG or HOME"() {
129+
def "gets config dir from XDG_CONFIG_HOME or HOME"() {
101130
expect:
102131
Path.of(path) == configDir(env)
103132

104133
where:
105134
env || path
106-
[:] || "/tmp/coder-gateway-test/xdg/coderv2"
135+
[:] || "/tmp/coder-gateway-test/xdg-config/coderv2"
107136
["XDG_CONFIG_HOME": ""] || "/tmp/coder-gateway-test/home/.config/coderv2"
108137
}
109138

@@ -118,4 +147,43 @@ class CoderCLIManagerTest extends spock.lang.Specification {
118147
expect:
119148
Path.of("/tmp/coder-gateway-test/appdata/coderv2") == configDir()
120149
}
150+
151+
/**
152+
* Get a data dir using default environment variable values.
153+
*/
154+
def dataDir(Map<String, String> env = [:]) {
155+
return CoderCLIManager.getDataDir(new Environment(testEnv + env))
156+
}
157+
// Mostly just a sanity check to make sure the default System.getenv runs
158+
// without throwing any errors.
159+
def "gets data dir"() {
160+
when:
161+
def dir = CoderCLIManager.getDataDir()
162+
163+
then:
164+
dir.toString().contains("coder-gateway")
165+
}
166+
167+
@Requires({ os.linux })
168+
def "gets data dir from XDG_DATA_HOME or HOME"() {
169+
expect:
170+
Path.of(path) == dataDir(env)
171+
172+
where:
173+
env || path
174+
[:] || "/tmp/coder-gateway-test/xdg-data/coder-gateway"
175+
["XDG_DATA_HOME": ""] || "/tmp/coder-gateway-test/home/.local/share/coder-gateway"
176+
}
177+
178+
@Requires({ os.macOs })
179+
def "gets data dir from HOME"() {
180+
expect:
181+
Path.of("/tmp/coder-gateway-test/home/Library/Application Support/coder-gateway") == dataDir()
182+
}
183+
184+
@Requires({ os.windows })
185+
def "gets data dir from LOCALAPPDATA"() {
186+
expect:
187+
Path.of("/tmp/coder-gateway-test/localappdata/coder-gateway") == dataDir()
188+
}
121189
}

0 commit comments

Comments
 (0)