Allow user-data-dir and extension-dir in config.yaml

Closes #1676
This commit is contained in:
Anmol Sethi 2020-05-19 00:39:57 -04:00
parent aa87270148
commit 8053ec6872
No known key found for this signature in database
GPG Key ID: 8CEF1878FF10ADEB
3 changed files with 58 additions and 52 deletions

View File

@ -152,12 +152,12 @@ export const optionDescriptions = (): string[] => {
) )
} }
export const parse = async ( export const parse = (
argv: string[], argv: string[],
opts?: { opts?: {
configFile: string configFile: string
}, },
): Promise<Args> => { ): Args => {
const error = (msg: string): Error => { const error = (msg: string): Error => {
if (opts?.configFile) { if (opts?.configFile) {
msg = `error reading ${opts.configFile}: ${msg}` msg = `error reading ${opts.configFile}: ${msg}`
@ -288,18 +288,28 @@ export const parse = async (
break break
case LogLevel.Debug: case LogLevel.Debug:
logger.level = Level.Debug logger.level = Level.Debug
args.verbose = false
break break
case LogLevel.Info: case LogLevel.Info:
logger.level = Level.Info logger.level = Level.Info
args.verbose = false
break break
case LogLevel.Warn: case LogLevel.Warn:
logger.level = Level.Warning logger.level = Level.Warning
args.verbose = false
break break
case LogLevel.Error: case LogLevel.Error:
logger.level = Level.Error logger.level = Level.Error
args.verbose = false
break break
} }
return args
}
export async function setDefaults(args: Args): Promise<Args> {
args = { ...args }
if (!args["user-data-dir"]) { if (!args["user-data-dir"]) {
await copyOldMacOSDataDir() await copyOldMacOSDataDir()
args["user-data-dir"] = paths.data args["user-data-dir"] = paths.data
@ -353,7 +363,7 @@ export async function readConfigFile(configPath?: string): Promise<Args> {
} }
return `--${optName}=${opt}` return `--${optName}=${opt}`
}) })
const args = await parse(configFileArgv, { const args = parse(configFileArgv, {
configFile: configPath, configFile: configPath,
}) })
return { return {

View File

@ -9,7 +9,7 @@ import { ProxyHttpProvider } from "./app/proxy"
import { StaticHttpProvider } from "./app/static" import { StaticHttpProvider } from "./app/static"
import { UpdateHttpProvider } from "./app/update" import { UpdateHttpProvider } from "./app/update"
import { VscodeHttpProvider } from "./app/vscode" import { VscodeHttpProvider } from "./app/vscode"
import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile } from "./cli" import { Args, bindAddrFromAllSources, optionDescriptions, parse, readConfigFile, setDefaults } from "./cli"
import { AuthType, HttpServer, HttpServerOptions } from "./http" import { AuthType, HttpServer, HttpServerOptions } from "./http"
import { generateCertificate, hash, open, humanPath } from "./util" import { generateCertificate, hash, open, humanPath } from "./util"
import { ipcMain, wrap } from "./wrapper" import { ipcMain, wrap } from "./wrapper"
@ -43,6 +43,8 @@ const main = async (cliArgs: Args): Promise<void> => {
} }
} }
args = await setDefaults(args)
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`) logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`) logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)
@ -127,9 +129,9 @@ const main = async (cliArgs: Args): Promise<void> => {
} }
async function entry(): Promise<void> { async function entry(): Promise<void> {
const tryParse = async (): Promise<Args> => { const tryParse = (): Args => {
try { try {
return await parse(process.argv.slice(2)) return parse(process.argv.slice(2))
} catch (error) { } catch (error) {
console.error(error.message) console.error(error.message)
process.exit(1) process.exit(1)

View File

@ -2,7 +2,6 @@ import { logger, Level } from "@coder/logger"
import * as assert from "assert" import * as assert from "assert"
import * as path from "path" import * as path from "path"
import { parse } from "../src/node/cli" import { parse } from "../src/node/cli"
import { paths } from "../src/node/util"
describe("cli", () => { describe("cli", () => {
beforeEach(() => { beforeEach(() => {
@ -12,17 +11,15 @@ describe("cli", () => {
// The parser will always fill these out. // The parser will always fill these out.
const defaults = { const defaults = {
_: [], _: [],
"extensions-dir": path.join(paths.data, "extensions"),
"user-data-dir": paths.data,
} }
it("should set defaults", async () => { it("should set defaults", () => {
assert.deepEqual(await await parse([]), defaults) assert.deepEqual(parse([]), defaults)
}) })
it("should parse all available options", async () => { it("should parse all available options", () => {
assert.deepEqual( assert.deepEqual(
await await parse([ parse([
"--bind-addr=192.169.0.1:8080", "--bind-addr=192.169.0.1:8080",
"--auth", "--auth",
"none", "none",
@ -84,8 +81,8 @@ describe("cli", () => {
) )
}) })
it("should work with short options", async () => { it("should work with short options", () => {
assert.deepEqual(await parse(["-vvv", "-v"]), { assert.deepEqual(parse(["-vvv", "-v"]), {
...defaults, ...defaults,
log: "trace", log: "trace",
verbose: true, verbose: true,
@ -95,17 +92,18 @@ describe("cli", () => {
assert.equal(logger.level, Level.Trace) assert.equal(logger.level, Level.Trace)
}) })
it("should use log level env var", async () => { it("should use log level env var", () => {
process.env.LOG_LEVEL = "debug" process.env.LOG_LEVEL = "debug"
assert.deepEqual(await parse([]), { assert.deepEqual(parse([]), {
...defaults, ...defaults,
log: "debug", log: "debug",
verbose: false,
}) })
assert.equal(process.env.LOG_LEVEL, "debug") assert.equal(process.env.LOG_LEVEL, "debug")
assert.equal(logger.level, Level.Debug) assert.equal(logger.level, Level.Debug)
process.env.LOG_LEVEL = "trace" process.env.LOG_LEVEL = "trace"
assert.deepEqual(await parse([]), { assert.deepEqual(parse([]), {
...defaults, ...defaults,
log: "trace", log: "trace",
verbose: true, verbose: true,
@ -116,23 +114,25 @@ describe("cli", () => {
it("should prefer --log to env var and --verbose to --log", async () => { it("should prefer --log to env var and --verbose to --log", async () => {
process.env.LOG_LEVEL = "debug" process.env.LOG_LEVEL = "debug"
assert.deepEqual(await parse(["--log", "info"]), { assert.deepEqual(parse(["--log", "info"]), {
...defaults, ...defaults,
log: "info", log: "info",
verbose: false,
}) })
assert.equal(process.env.LOG_LEVEL, "info") assert.equal(process.env.LOG_LEVEL, "info")
assert.equal(logger.level, Level.Info) assert.equal(logger.level, Level.Info)
process.env.LOG_LEVEL = "trace" process.env.LOG_LEVEL = "trace"
assert.deepEqual(await parse(["--log", "info"]), { assert.deepEqual(parse(["--log", "info"]), {
...defaults, ...defaults,
log: "info", log: "info",
verbose: false,
}) })
assert.equal(process.env.LOG_LEVEL, "info") assert.equal(process.env.LOG_LEVEL, "info")
assert.equal(logger.level, Level.Info) assert.equal(logger.level, Level.Info)
process.env.LOG_LEVEL = "warn" process.env.LOG_LEVEL = "warn"
assert.deepEqual(await parse(["--log", "info", "--verbose"]), { assert.deepEqual(parse(["--log", "info", "--verbose"]), {
...defaults, ...defaults,
log: "trace", log: "trace",
verbose: true, verbose: true,
@ -141,34 +141,31 @@ describe("cli", () => {
assert.equal(logger.level, Level.Trace) assert.equal(logger.level, Level.Trace)
}) })
it("should ignore invalid log level env var", async () => { it("should ignore invalid log level env var", () => {
process.env.LOG_LEVEL = "bogus" process.env.LOG_LEVEL = "bogus"
assert.deepEqual(await parse([]), defaults) assert.deepEqual(parse([]), defaults)
}) })
it("should error if value isn't provided", async () => { it("should error if value isn't provided", () => {
await assert.rejects(async () => await parse(["--auth"]), /--auth requires a value/) assert.throws(() => parse(["--auth"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--auth=", "--log=debug"]), /--auth requires a value/) assert.throws(() => parse(["--auth=", "--log=debug"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--auth", "--log"]), /--auth requires a value/) assert.throws(() => parse(["--auth", "--log"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--auth", "--invalid"]), /--auth requires a value/) assert.throws(() => parse(["--auth", "--invalid"]), /--auth requires a value/)
await assert.rejects(async () => await parse(["--bind-addr"]), /--bind-addr requires a value/) assert.throws(() => parse(["--bind-addr"]), /--bind-addr requires a value/)
}) })
it("should error if value is invalid", async () => { it("should error if value is invalid", () => {
await assert.rejects(async () => await parse(["--port", "foo"]), /--port must be a number/) assert.throws(() => parse(["--port", "foo"]), /--port must be a number/)
await assert.rejects(async () => await parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/) assert.throws(() => parse(["--auth", "invalid"]), /--auth valid values: \[password, none\]/)
await assert.rejects( assert.throws(() => parse(["--log", "invalid"]), /--log valid values: \[trace, debug, info, warn, error\]/)
async () => await parse(["--log", "invalid"]),
/--log valid values: \[trace, debug, info, warn, error\]/,
)
}) })
it("should error if the option doesn't exist", async () => { it("should error if the option doesn't exist", () => {
await assert.rejects(async () => await parse(["--foo"]), /Unknown option --foo/) assert.throws(() => parse(["--foo"]), /Unknown option --foo/)
}) })
it("should not error if the value is optional", async () => { it("should not error if the value is optional", () => {
assert.deepEqual(await parse(["--cert"]), { assert.deepEqual(parse(["--cert"]), {
...defaults, ...defaults,
cert: { cert: {
value: undefined, value: undefined,
@ -176,33 +173,30 @@ describe("cli", () => {
}) })
}) })
it("should not allow option-like values", async () => { it("should not allow option-like values", () => {
await assert.rejects(async () => await parse(["--socket", "--socket-path-value"]), /--socket requires a value/) assert.throws(() => parse(["--socket", "--socket-path-value"]), /--socket requires a value/)
// If you actually had a path like this you would do this instead: // If you actually had a path like this you would do this instead:
assert.deepEqual(await parse(["--socket", "./--socket-path-value"]), { assert.deepEqual(parse(["--socket", "./--socket-path-value"]), {
...defaults, ...defaults,
socket: path.resolve("--socket-path-value"), socket: path.resolve("--socket-path-value"),
}) })
await assert.rejects( assert.throws(() => parse(["--cert", "--socket-path-value"]), /Unknown option --socket-path-value/)
async () => await parse(["--cert", "--socket-path-value"]),
/Unknown option --socket-path-value/,
)
}) })
it("should allow positional arguments before options", async () => { it("should allow positional arguments before options", () => {
assert.deepEqual(await parse(["foo", "test", "--auth", "none"]), { assert.deepEqual(parse(["foo", "test", "--auth", "none"]), {
...defaults, ...defaults,
_: ["foo", "test"], _: ["foo", "test"],
auth: "none", auth: "none",
}) })
}) })
it("should support repeatable flags", async () => { it("should support repeatable flags", () => {
assert.deepEqual(await parse(["--proxy-domain", "*.coder.com"]), { assert.deepEqual(parse(["--proxy-domain", "*.coder.com"]), {
...defaults, ...defaults,
"proxy-domain": ["*.coder.com"], "proxy-domain": ["*.coder.com"],
}) })
assert.deepEqual(await parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), { assert.deepEqual(parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "test.com"]), {
...defaults, ...defaults,
"proxy-domain": ["*.coder.com", "test.com"], "proxy-domain": ["*.coder.com", "test.com"],
}) })