Support for using boolean values when using yaml docker-compose files rather than strings for booleans (#98)

This commit is contained in:
Simon J
2022-02-28 22:07:17 +11:00
committed by GitHub
parent df9a6d4663
commit 9b3df4ce1a
4 changed files with 225 additions and 180 deletions

View File

@@ -27,8 +27,8 @@ services:
BNB_URL: http://192.168.1.111:4534 BNB_URL: http://192.168.1.111:4534
BNB_SECRET: changeme BNB_SECRET: changeme
BNB_SONOS_SERVICE_ID: 246 BNB_SONOS_SERVICE_ID: 246
BNB_SONOS_AUTO_REGISTER: "true" BNB_SONOS_AUTO_REGISTER: true
BNB_SONOS_DEVICE_DISCOVERY: "true" BNB_SONOS_DEVICE_DISCOVERY: true
# ip address of one of your sonos devices # ip address of one of your sonos devices
BNB_SONOS_SEED_HOST: 192.168.1.121 BNB_SONOS_SEED_HOST: 192.168.1.121
BNB_SUBSONIC_URL: http://navidrome:4533 BNB_SUBSONIC_URL: http://navidrome:4533

View File

@@ -5,20 +5,22 @@ import url from "./url_builder";
export const WORD = /^\w+$/; export const WORD = /^\w+$/;
export const COLOR = /^#?\w+$/; export const COLOR = /^#?\w+$/;
type EnvVarOpts = { type EnvVarOpts<T> = {
default: string | undefined; default: T | undefined;
legacy: string[] | undefined; legacy: string[] | undefined;
validationPattern: RegExp | undefined; validationPattern: RegExp | undefined;
parser: ((value: string) => T) | undefined
}; };
export function envVar( export function envVar<T>(
name: string, name: string,
opts: Partial<EnvVarOpts> = { opts: Partial<EnvVarOpts<T>> = {
default: undefined, default: undefined,
legacy: undefined, legacy: undefined,
validationPattern: undefined, validationPattern: undefined,
parser: undefined
} }
) { ): T {
const result = [name, ...(opts.legacy || [])] const result = [name, ...(opts.legacy || [])]
.map((it) => ({ key: it, value: process.env[it] })) .map((it) => ({ key: it, value: process.env[it] }))
.find((it) => it.value); .find((it) => it.value);
@@ -36,17 +38,28 @@ export function envVar(
logger.warn(`Configuration key '${result.key}' is deprecated, replace with '${name}'`) logger.warn(`Configuration key '${result.key}' is deprecated, replace with '${name}'`)
} }
return result?.value || opts.default; let value: T | undefined = undefined;
if(result?.value && opts.parser) {
value = opts.parser(result?.value)
} else if(result?.value)
value = result?.value as any as T
return value == undefined ? opts.default as T : value;
} }
export const bnbEnvVar = (key: string, opts: Partial<EnvVarOpts> = {}) => export const bnbEnvVar = <T>(key: string, opts: Partial<EnvVarOpts<T>> = {}) =>
envVar(`BNB_${key}`, { envVar(`BNB_${key}`, {
...opts, ...opts,
legacy: [`BONOB_${key}`, ...(opts.legacy || [])], legacy: [`BONOB_${key}`, ...(opts.legacy || [])],
}); });
const asBoolean = (value: string) => value == "true";
const asInt = (value: string) => Number.parseInt(value);
export default function () { export default function () {
const port = +bnbEnvVar("PORT", { default: "4534" })!; const port = bnbEnvVar<number>("PORT", { default: 4534, parser: asInt })!;
const bonobUrl = bnbEnvVar("URL", { const bonobUrl = bnbEnvVar("URL", {
legacy: ["BONOB_WEB_ADDRESS"], legacy: ["BONOB_WEB_ADDRESS"],
default: `http://${hostname()}:${port}`, default: `http://${hostname()}:${port}`,
@@ -62,34 +75,34 @@ export default function () {
return { return {
port, port,
bonobUrl: url(bonobUrl), bonobUrl: url(bonobUrl),
secret: bnbEnvVar("SECRET", { default: "bonob" })!, secret: bnbEnvVar<string>("SECRET", { default: "bonob" })!,
authTimeout: bnbEnvVar("AUTH_TIMEOUT", { default: "1h" })!, authTimeout: bnbEnvVar<string>("AUTH_TIMEOUT", { default: "1h" })!,
icons: { icons: {
foregroundColor: bnbEnvVar("ICON_FOREGROUND_COLOR", { foregroundColor: bnbEnvVar<string>("ICON_FOREGROUND_COLOR", {
validationPattern: COLOR, validationPattern: COLOR,
}), }),
backgroundColor: bnbEnvVar("ICON_BACKGROUND_COLOR", { backgroundColor: bnbEnvVar<string>("ICON_BACKGROUND_COLOR", {
validationPattern: COLOR, validationPattern: COLOR,
}), }),
}, },
sonos: { sonos: {
serviceName: bnbEnvVar("SONOS_SERVICE_NAME", { default: "bonob" })!, serviceName: bnbEnvVar<string>("SONOS_SERVICE_NAME", { default: "bonob" })!,
discovery: { discovery: {
enabled: enabled:
bnbEnvVar("SONOS_DEVICE_DISCOVERY", { default: "true" }) == "true", bnbEnvVar<boolean>("SONOS_DEVICE_DISCOVERY", { default: true, parser: asBoolean }),
seedHost: bnbEnvVar("SONOS_SEED_HOST"), seedHost: bnbEnvVar<string>("SONOS_SEED_HOST"),
}, },
autoRegister: autoRegister:
bnbEnvVar("SONOS_AUTO_REGISTER", { default: "false" }) == "true", bnbEnvVar<boolean>("SONOS_AUTO_REGISTER", { default: false, parser: asBoolean }),
sid: Number(bnbEnvVar("SONOS_SERVICE_ID", { default: "246" })), sid: bnbEnvVar<number>("SONOS_SERVICE_ID", { default: 246, parser: asInt }),
}, },
subsonic: { subsonic: {
url: bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533` })!, url: bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533` })!,
customClientsFor: bnbEnvVar("SUBSONIC_CUSTOM_CLIENTS", { legacy: ["BONOB_NAVIDROME_CUSTOM_CLIENTS"] }), customClientsFor: bnbEnvVar<string>("SUBSONIC_CUSTOM_CLIENTS", { legacy: ["BONOB_NAVIDROME_CUSTOM_CLIENTS"] }),
artistImageCache: bnbEnvVar("SUBSONIC_ARTIST_IMAGE_CACHE"), artistImageCache: bnbEnvVar<string>("SUBSONIC_ARTIST_IMAGE_CACHE"),
}, },
scrobbleTracks: bnbEnvVar("SCROBBLE_TRACKS", { default: "true" }) == "true", scrobbleTracks: bnbEnvVar<boolean>("SCROBBLE_TRACKS", { default: true, parser: asBoolean }),
reportNowPlaying: reportNowPlaying:
bnbEnvVar("REPORT_NOW_PLAYING", { default: "true" }) == "true", bnbEnvVar<boolean>("REPORT_NOW_PLAYING", { default: true, parser: asBoolean }),
}; };
} }

View File

@@ -96,42 +96,35 @@ describe("config", () => {
propertyGetter: (config: any) => any propertyGetter: (config: any) => any
) { ) {
describe(name, () => { describe(name, () => {
function expecting({ it.each([
value, [expectedDefault, ""],
expected, [expectedDefault, undefined],
}: { [true, "true"],
value: string; [false, "false"],
expected: boolean; [false, "foo"],
}) { ])("should be %s when env var is '%s'", (expected, value) => {
describe(`when value is '${value}'`, () => { process.env[envVar] = value;
it(`should be ${expected}`, () => { expect(propertyGetter(config())).toEqual(expected);
process.env[envVar] = value; })
expect(propertyGetter(config())).toEqual(expected);
});
});
}
expecting({ value: "", expected: expectedDefault });
expecting({ value: "true", expected: true });
expecting({ value: "false", expected: false });
expecting({ value: "foo", expected: false });
}); });
} }
describe("bonobUrl", () => { describe("bonobUrl", () => {
["BNB_URL", "BONOB_URL", "BONOB_WEB_ADDRESS"].forEach((key) => { describe.each([
describe(`when ${key} is specified`, () => { "BNB_URL",
"BONOB_URL",
"BONOB_WEB_ADDRESS"
])("when %s is specified", (k) => {
it("should be used", () => { it("should be used", () => {
const url = "http://bonob1.example.com:8877/"; const url = "http://bonob1.example.com:8877/";
process.env["BNB_URL"] = ""; process.env["BNB_URL"] = "";
process.env["BONOB_URL"] = ""; process.env["BONOB_URL"] = "";
process.env["BONOB_WEB_ADDRESS"] = ""; process.env["BONOB_WEB_ADDRESS"] = "";
process.env[key] = url; process.env[k] = url;
expect(config().bonobUrl.href()).toEqual(url); expect(config().bonobUrl.href()).toEqual(url);
}); });
});
}); });
describe("when none of BNB_URL, BONOB_URL, BONOB_WEB_ADDRESS are specified", () => { describe("when none of BNB_URL, BONOB_URL, BONOB_WEB_ADDRESS are specified", () => {
@@ -165,87 +158,89 @@ describe("config", () => {
describe("icons", () => { describe("icons", () => {
describe("foregroundColor", () => { describe("foregroundColor", () => {
["BNB_ICON_FOREGROUND_COLOR", "BONOB_ICON_FOREGROUND_COLOR"].forEach( describe.each([
(k) => { "BNB_ICON_FOREGROUND_COLOR",
describe(`when ${k} is not specified`, () => { "BONOB_ICON_FOREGROUND_COLOR",
it(`should default to undefined`, () => { ])("%s", (k) => {
expect(config().icons.foregroundColor).toEqual(undefined); describe(`when ${k} is not specified`, () => {
}); it(`should default to undefined`, () => {
expect(config().icons.foregroundColor).toEqual(undefined);
}); });
});
describe(`when ${k} is ''`, () => { describe(`when ${k} is ''`, () => {
it(`should default to undefined`, () => { it(`should default to undefined`, () => {
process.env[k] = ""; process.env[k] = "";
expect(config().icons.foregroundColor).toEqual(undefined); expect(config().icons.foregroundColor).toEqual(undefined);
});
}); });
});
describe(`when ${k} is specified as a color`, () => { describe(`when ${k} is specified as a color`, () => {
it(`should use it`, () => { it(`should use it`, () => {
process.env[k] = "pink"; process.env[k] = "pink";
expect(config().icons.foregroundColor).toEqual("pink"); expect(config().icons.foregroundColor).toEqual("pink");
});
}); });
});
describe(`when ${k} is specified as hex`, () => { describe(`when ${k} is specified as hex`, () => {
it(`should use it`, () => { it(`should use it`, () => {
process.env[k] = "#1db954"; process.env[k] = "#1db954";
expect(config().icons.foregroundColor).toEqual("#1db954"); expect(config().icons.foregroundColor).toEqual("#1db954");
});
}); });
});
describe(`when ${k} is an invalid string`, () => { describe(`when ${k} is an invalid string`, () => {
it(`should blow up`, () => { it(`should blow up`, () => {
process.env[k] = "!dfasd"; process.env[k] = "!dfasd";
expect(() => config()).toThrow( expect(() => config()).toThrow(
`Invalid value specified for 'BNB_ICON_FOREGROUND_COLOR', must match ${COLOR}` `Invalid value specified for 'BNB_ICON_FOREGROUND_COLOR', must match ${COLOR}`
); );
});
}); });
} });
); });
}); });
describe("backgroundColor", () => { describe("backgroundColor", () => {
["BNB_ICON_BACKGROUND_COLOR", "BONOB_ICON_BACKGROUND_COLOR"].forEach( describe.each([
(k) => { "BNB_ICON_BACKGROUND_COLOR",
describe(`when ${k} is not specified`, () => { "BONOB_ICON_BACKGROUND_COLOR",
it(`should default to undefined`, () => { ])("%s", (k) => {
expect(config().icons.backgroundColor).toEqual(undefined); describe(`when ${k} is not specified`, () => {
}); it(`should default to undefined`, () => {
expect(config().icons.backgroundColor).toEqual(undefined);
}); });
});
describe(`when ${k} is ''`, () => { describe(`when ${k} is ''`, () => {
it(`should default to undefined`, () => { it(`should default to undefined`, () => {
process.env[k] = ""; process.env[k] = "";
expect(config().icons.backgroundColor).toEqual(undefined); expect(config().icons.backgroundColor).toEqual(undefined);
});
}); });
});
describe(`when ${k} is specified as a color`, () => { describe(`when ${k} is specified as a color`, () => {
it(`should use it`, () => { it(`should use it`, () => {
process.env[k] = "blue"; process.env[k] = "blue";
expect(config().icons.backgroundColor).toEqual("blue"); expect(config().icons.backgroundColor).toEqual("blue");
});
}); });
});
describe(`when ${k} is specified as hex`, () => { describe(`when ${k} is specified as hex`, () => {
it(`should use it`, () => { it(`should use it`, () => {
process.env[k] = "#1db954"; process.env[k] = "#1db954";
expect(config().icons.backgroundColor).toEqual("#1db954"); expect(config().icons.backgroundColor).toEqual("#1db954");
});
}); });
});
describe(`when ${k} is an invalid string`, () => { describe(`when ${k} is an invalid string`, () => {
it(`should blow up`, () => { it(`should blow up`, () => {
process.env[k] = "!red"; process.env[k] = "!red";
expect(() => config()).toThrow( expect(() => config()).toThrow(
`Invalid value specified for 'BNB_ICON_BACKGROUND_COLOR', must match ${COLOR}` `Invalid value specified for 'BNB_ICON_BACKGROUND_COLOR', must match ${COLOR}`
); );
});
}); });
} });
); });
}); });
}); });
@@ -254,9 +249,12 @@ describe("config", () => {
expect(config().secret).toEqual("bonob"); expect(config().secret).toEqual("bonob");
}); });
["BNB_SECRET", "BONOB_SECRET"].forEach((key) => { describe.each([
it(`should be overridable using ${key}`, () => { "BNB_SECRET",
process.env[key] = "new secret"; "BONOB_SECRET"
])("%s", (k) => {
it(`should be overridable using ${k}`, () => {
process.env[k] = "new secret";
expect(config().secret).toEqual("new secret"); expect(config().secret).toEqual("new secret");
}); });
}); });
@@ -271,7 +269,7 @@ describe("config", () => {
process.env["BNB_AUTH_TIMEOUT"] = "33s"; process.env["BNB_AUTH_TIMEOUT"] = "33s";
expect(config().authTimeout).toEqual("33s"); expect(config().authTimeout).toEqual("33s");
}); });
}); });
describe("sonos", () => { describe("sonos", () => {
describe("serviceName", () => { describe("serviceName", () => {
@@ -279,91 +277,114 @@ describe("config", () => {
expect(config().sonos.serviceName).toEqual("bonob"); expect(config().sonos.serviceName).toEqual("bonob");
}); });
["BNB_SONOS_SERVICE_NAME", "BONOB_SONOS_SERVICE_NAME"].forEach((k) => { describe.each([
it("should be overridable", () => { "BNB_SONOS_SERVICE_NAME",
process.env[k] = "foobar1000"; "BONOB_SONOS_SERVICE_NAME"
expect(config().sonos.serviceName).toEqual("foobar1000"); ])(
}); "%s",
}); (k) => {
it("should be overridable", () => {
process.env[k] = "foobar1000";
expect(config().sonos.serviceName).toEqual("foobar1000");
});
}
);
}); });
["BNB_SONOS_DEVICE_DISCOVERY", "BONOB_SONOS_DEVICE_DISCOVERY"].forEach( describe.each([
(k) => { "BNB_SONOS_DEVICE_DISCOVERY",
describeBooleanConfigValue( "BONOB_SONOS_DEVICE_DISCOVERY",
"deviceDiscovery", ])("%s", (k) => {
k, describeBooleanConfigValue(
true, "deviceDiscovery",
(config) => config.sonos.discovery.enabled k,
); true,
} (config) => config.sonos.discovery.enabled
); );
});
describe("seedHost", () => { describe("seedHost", () => {
it("should default to undefined", () => { it("should default to undefined", () => {
expect(config().sonos.discovery.seedHost).toBeUndefined(); expect(config().sonos.discovery.seedHost).toBeUndefined();
}); });
["BNB_SONOS_SEED_HOST", "BONOB_SONOS_SEED_HOST"].forEach((k) => { describe.each([
it("should be overridable", () => { "BNB_SONOS_SEED_HOST",
process.env[k] = "123.456.789.0"; "BONOB_SONOS_SEED_HOST"
expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0"); ])(
}); "%s",
}); (k) => {
}); it("should be overridable", () => {
process.env[k] = "123.456.789.0";
["BNB_SONOS_AUTO_REGISTER", "BONOB_SONOS_AUTO_REGISTER"].forEach((k) => { expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0");
describeBooleanConfigValue( });
"autoRegister", }
k,
false,
(config) => config.sonos.autoRegister
); );
}); });
describe.each([
"BNB_SONOS_AUTO_REGISTER",
"BONOB_SONOS_AUTO_REGISTER"
])(
"%s",
(k) => {
describeBooleanConfigValue(
"autoRegister",
k,
false,
(config) => config.sonos.autoRegister
);
}
);
describe("sid", () => { describe("sid", () => {
it("should default to 246", () => { it("should default to 246", () => {
expect(config().sonos.sid).toEqual(246); expect(config().sonos.sid).toEqual(246);
}); });
["BNB_SONOS_SERVICE_ID", "BONOB_SONOS_SERVICE_ID"].forEach((k) => { describe.each([
it("should be overridable", () => { "BNB_SONOS_SERVICE_ID",
process.env[k] = "786"; "BONOB_SONOS_SERVICE_ID"
expect(config().sonos.sid).toEqual(786); ])(
}); "%s",
}); (k) => {
it("should be overridable", () => {
process.env[k] = "786";
expect(config().sonos.sid).toEqual(786);
});
}
);
}); });
}); });
describe("subsonic", () => { describe("subsonic", () => {
describe("url", () => { describe("url", () => {
["BNB_SUBSONIC_URL", "BONOB_SUBSONIC_URL", "BONOB_NAVIDROME_URL"].forEach( describe.each([
(k) => { "BNB_SUBSONIC_URL",
describe(`when ${k} is not specified`, () => { "BONOB_SUBSONIC_URL",
it(`should default to http://${hostname()}:4533`, () => { "BONOB_NAVIDROME_URL",
expect(config().subsonic.url).toEqual( ])("%s", (k) => {
`http://${hostname()}:4533` describe(`when ${k} is not specified`, () => {
); it(`should default to http://${hostname()}:4533`, () => {
}); expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
}); });
});
describe(`when ${k} is ''`, () => { describe(`when ${k} is ''`, () => {
it(`should default to http://${hostname()}:4533`, () => { it(`should default to http://${hostname()}:4533`, () => {
process.env[k] = ""; process.env[k] = "";
expect(config().subsonic.url).toEqual( expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
`http://${hostname()}:4533`
);
});
}); });
});
describe(`when ${k} is specified`, () => { describe(`when ${k} is specified`, () => {
it(`should use it for ${k}`, () => { it(`should use it for ${k}`, () => {
const url = "http://navidrome.example.com:1234"; const url = "http://navidrome.example.com:1234";
process.env[k] = url; process.env[k] = url;
expect(config().subsonic.url).toEqual(url); expect(config().subsonic.url).toEqual(url);
});
}); });
} });
); });
}); });
describe("customClientsFor", () => { describe("customClientsFor", () => {
@@ -371,11 +392,11 @@ describe("config", () => {
expect(config().subsonic.customClientsFor).toBeUndefined(); expect(config().subsonic.customClientsFor).toBeUndefined();
}); });
[ describe.each([
"BNB_SUBSONIC_CUSTOM_CLIENTS", "BNB_SUBSONIC_CUSTOM_CLIENTS",
"BONOB_SUBSONIC_CUSTOM_CLIENTS", "BONOB_SUBSONIC_CUSTOM_CLIENTS",
"BONOB_NAVIDROME_CUSTOM_CLIENTS", "BONOB_NAVIDROME_CUSTOM_CLIENTS",
].forEach((k) => { ])("%s", (k) => {
it(`should be overridable for ${k}`, () => { it(`should be overridable for ${k}`, () => {
process.env[k] = "whoop/whoop"; process.env[k] = "whoop/whoop";
expect(config().subsonic.customClientsFor).toEqual("whoop/whoop"); expect(config().subsonic.customClientsFor).toEqual("whoop/whoop");
@@ -395,7 +416,10 @@ describe("config", () => {
}); });
}); });
["BNB_SCROBBLE_TRACKS", "BONOB_SCROBBLE_TRACKS"].forEach((k) => { describe.each([
"BNB_SCROBBLE_TRACKS",
"BONOB_SCROBBLE_TRACKS"
])("%s", (k) => {
describeBooleanConfigValue( describeBooleanConfigValue(
"scrobbleTracks", "scrobbleTracks",
k, k,
@@ -404,12 +428,18 @@ describe("config", () => {
); );
}); });
["BNB_REPORT_NOW_PLAYING", "BONOB_REPORT_NOW_PLAYING"].forEach((k) => { describe.each([
describeBooleanConfigValue( "BNB_REPORT_NOW_PLAYING",
"reportNowPlaying", "BONOB_REPORT_NOW_PLAYING"
k, ])(
true, "%s",
(config) => config.reportNowPlaying (k) => {
); describeBooleanConfigValue(
}); "reportNowPlaying",
k,
true,
(config) => config.reportNowPlaying
);
}
);
}); });

View File

@@ -90,6 +90,8 @@ describe("rating to and from ints", () => {
}); });
describe("service config", () => { describe("service config", () => {
jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "2000"));
const bonobWithNoContextPath = url("http://localhost:1234"); const bonobWithNoContextPath = url("http://localhost:1234");
const bonobWithContextPath = url("http://localhost:5678/some-context-path"); const bonobWithContextPath = url("http://localhost:5678/some-context-path");