Skip to content

Commit 0241244

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 0241244

File tree

2 files changed

+114
-36
lines changed

2 files changed

+114
-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: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,37 @@ 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.
13+
*
14+
* @TODO: Implement a mock.
15+
*/
16+
def createCLIManager(Boolean alternate = false) {
17+
var url = System.getenv("CODER_GATEWAY_TEST_DEPLOYMENT")
18+
if (url == null) {
19+
url = "https://dev.coder.com"
20+
}
21+
if (alternate) {
22+
url = System.getenv("CODER_GATEWAY_TEST_DEPLOYMENT_ALT")
23+
if (url == null) {
24+
url = "https://oss.demo.coder.com"
25+
}
26+
}
27+
var tmpdir = Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test")
28+
return new CoderCLIManager(new URL(url), tmpdir)
29+
}
30+
31+
def "defaults to a sub-directory in the data directory"() {
32+
given:
33+
def ccm = new CoderCLIManager(new URL("https://test.coder.invalid"))
34+
35+
expect:
36+
ccm.localBinaryPath.getParent() == CoderCLIManager.getDataDir().resolve("test.coder.invalid")
37+
}
38+
1339
def "downloads a working cli"() {
1440
given:
15-
def ccm = new CoderCLIManager(new URL("https://dev.coder.com"))
41+
def ccm = createCLIManager()
1642
ccm.localBinaryPath.getParent().toFile().deleteDir()
1743

1844
when:
@@ -25,7 +51,7 @@ class CoderCLIManagerTest extends spock.lang.Specification {
2551

2652
def "overwrites cli if incorrect version"() {
2753
given:
28-
def ccm = new CoderCLIManager(new URL("https://dev.coder.com"))
54+
def ccm = createCLIManager()
2955
Files.createDirectories(ccm.localBinaryPath.getParent())
3056
ccm.localBinaryPath.toFile().write("cli")
3157

@@ -39,7 +65,7 @@ class CoderCLIManagerTest extends spock.lang.Specification {
3965

4066
def "skips cli download if it already exists"() {
4167
given:
42-
def ccm = new CoderCLIManager(new URL("https://dev.coder.com"))
68+
def ccm = createCLIManager()
4369

4470
when:
4571
ccm.downloadCLI()
@@ -52,8 +78,8 @@ class CoderCLIManagerTest extends spock.lang.Specification {
5278

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

5884
when:
5985
ccm1.downloadCLI()
@@ -63,25 +89,27 @@ class CoderCLIManagerTest extends spock.lang.Specification {
6389
ccm1.localBinaryPath != ccm2.localBinaryPath
6490
}
6591

92+
def testEnv = [
93+
"APPDATA" : "/tmp/coder-gateway-test/appdata",
94+
"LOCALAPPDATA" : "/tmp/coder-gateway-test/localappdata",
95+
"HOME" : "/tmp/coder-gateway-test/home",
96+
"XDG_CONFIG_HOME" : "/tmp/coder-gateway-test/xdg-config",
97+
"XDG_DATA_HOME" : "/tmp/coder-gateway-test/xdg-data",
98+
"CODER_CONFIG_DIR": "",
99+
]
100+
66101
/**
67102
* Get a config dir using default environment variable values.
68103
*/
69104
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))
105+
return CoderCLIManager.getConfigDir(new Environment(testEnv + env))
76106
}
77107

78108
// Mostly just a sanity check to make sure the default System.getenv runs
79109
// without throwing any errors.
80110
def "gets config dir"() {
81111
when:
82-
def dir = CoderCLIManager.getConfigDir(new Environment([
83-
"CODER_CONFIG_DIR": "",
84-
]))
112+
def dir = CoderCLIManager.getConfigDir()
85113

86114
then:
87115
dir.toString().contains("coderv2")
@@ -97,13 +125,13 @@ class CoderCLIManagerTest extends spock.lang.Specification {
97125
}
98126

99127
@Requires({ os.linux })
100-
def "gets config dir from XDG or HOME"() {
128+
def "gets config dir from XDG_CONFIG_HOME or HOME"() {
101129
expect:
102130
Path.of(path) == configDir(env)
103131

104132
where:
105133
env || path
106-
[:] || "/tmp/coder-gateway-test/xdg/coderv2"
134+
[:] || "/tmp/coder-gateway-test/xdg-config/coderv2"
107135
["XDG_CONFIG_HOME": ""] || "/tmp/coder-gateway-test/home/.config/coderv2"
108136
}
109137

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

0 commit comments

Comments
 (0)