feat: configuration loader.

This commit is contained in:
Ivan Li 2021-10-07 13:46:34 +08:00
parent e4c6718e43
commit a02bc282bb
11 changed files with 224 additions and 37 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"nodenext"
]
}

12
examples/config-loader.ts Normal file
View File

@ -0,0 +1,12 @@
import { ConfigLoader } from "../src/config-loader.js";
const loader = new ConfigLoader();
loader
.loadConfig()
.then((config) => {
console.log("config", config);
})
.catch((error) => {
console.error(error);
});

View File

@ -1,4 +1,4 @@
import { AppConfig, ServiceRegister } from "../src"; import { AppConfig, ServiceRegister } from "../src/index.js.js";
const config: AppConfig = { const config: AppConfig = {
etcd: { etcd: {

144
package-lock.json generated
View File

@ -1,16 +1,17 @@
{ {
"name": "configuration", "name": "configuration",
"version": "1.0.0", "version": "0.0.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "configuration", "name": "configuration",
"version": "1.0.0", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.3.2", "debug": "^4.3.2",
"etcd3": "^1.1.0", "etcd3": "^1.1.0",
"find-up": "^6.1.0",
"js-yaml": "^4.1.0" "js-yaml": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -20,7 +21,7 @@
"@types/node": "^14.17.17", "@types/node": "^14.17.17",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"typescript": "^4.4.3" "typescript": "^4.5.0-beta"
} }
}, },
"node_modules/@cspotcode/source-map-consumer": { "node_modules/@cspotcode/source-map-consumer": {
@ -324,6 +325,22 @@
"cockatiel": "^1.1.1" "cockatiel": "^1.1.1"
} }
}, },
"node_modules/find-up": {
"version": "6.1.0",
"resolved": "https://npm.ivanli.cc/find-up/-/find-up-6.1.0.tgz",
"integrity": "sha512-aBlseiBgQ1RSiF/brMW+toDud3NHJ2Hn3pgNJLmBf2+gBwwNbfhE/Lbg2wwwoHfD3qXReOvDH4hlywQCXp4/Lw==",
"license": "MIT",
"dependencies": {
"locate-path": "^7.0.0",
"path-exists": "^5.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://npm.ivanli.cc/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://npm.ivanli.cc/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -382,6 +399,21 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/locate-path": {
"version": "7.0.0",
"resolved": "https://npm.ivanli.cc/locate-path/-/locate-path-7.0.0.tgz",
"integrity": "sha512-+cg2yXqDUKfo4hsFxwa3G1cBJeA+gs1vD8FyV9/odWoUlQe/4syxHQ5DPtKjtfm6gnKbZzjCqzX03kXosvZB1w==",
"license": "MIT",
"dependencies": {
"p-locate": "^6.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.camelcase": { "node_modules/lodash.camelcase": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://npm.ivanli.cc/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "resolved": "https://npm.ivanli.cc/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@ -430,6 +462,45 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"node_modules/p-limit": {
"version": "4.0.0",
"resolved": "https://npm.ivanli.cc/p-limit/-/p-limit-4.0.0.tgz",
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
"license": "MIT",
"dependencies": {
"yocto-queue": "^1.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "6.0.0",
"resolved": "https://npm.ivanli.cc/p-locate/-/p-locate-6.0.0.tgz",
"integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
"license": "MIT",
"dependencies": {
"p-limit": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-exists": {
"version": "5.0.0",
"resolved": "https://npm.ivanli.cc/path-exists/-/path-exists-5.0.0.tgz",
"integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/path-is-absolute": { "node_modules/path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://npm.ivanli.cc/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://npm.ivanli.cc/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -528,9 +599,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.4.3", "version": "4.5.0-beta",
"resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.4.3.tgz", "resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.5.0-beta.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", "integrity": "sha512-7PvWhki2lwukaR9osVhFnNzxaE4LM+gC94dlwcvS+Tqz8+U65va7FbKo02bT+/MFlnMPM8bsPUXvHiMD/Mg3Jg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@ -557,6 +628,18 @@
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
},
"node_modules/yocto-queue": {
"version": "1.0.0",
"resolved": "https://npm.ivanli.cc/yocto-queue/-/yocto-queue-1.0.0.tgz",
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
"license": "MIT",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
} }
}, },
"dependencies": { "dependencies": {
@ -787,6 +870,15 @@
"cockatiel": "^1.1.1" "cockatiel": "^1.1.1"
} }
}, },
"find-up": {
"version": "6.1.0",
"resolved": "https://npm.ivanli.cc/find-up/-/find-up-6.1.0.tgz",
"integrity": "sha512-aBlseiBgQ1RSiF/brMW+toDud3NHJ2Hn3pgNJLmBf2+gBwwNbfhE/Lbg2wwwoHfD3qXReOvDH4hlywQCXp4/Lw==",
"requires": {
"locate-path": "^7.0.0",
"path-exists": "^5.0.0"
}
},
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://npm.ivanli.cc/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://npm.ivanli.cc/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -831,6 +923,14 @@
"argparse": "^2.0.1" "argparse": "^2.0.1"
} }
}, },
"locate-path": {
"version": "7.0.0",
"resolved": "https://npm.ivanli.cc/locate-path/-/locate-path-7.0.0.tgz",
"integrity": "sha512-+cg2yXqDUKfo4hsFxwa3G1cBJeA+gs1vD8FyV9/odWoUlQe/4syxHQ5DPtKjtfm6gnKbZzjCqzX03kXosvZB1w==",
"requires": {
"p-locate": "^6.0.0"
}
},
"lodash.camelcase": { "lodash.camelcase": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://npm.ivanli.cc/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "resolved": "https://npm.ivanli.cc/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@ -870,6 +970,27 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"p-limit": {
"version": "4.0.0",
"resolved": "https://npm.ivanli.cc/p-limit/-/p-limit-4.0.0.tgz",
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
"requires": {
"yocto-queue": "^1.0.0"
}
},
"p-locate": {
"version": "6.0.0",
"resolved": "https://npm.ivanli.cc/p-locate/-/p-locate-6.0.0.tgz",
"integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
"requires": {
"p-limit": "^4.0.0"
}
},
"path-exists": {
"version": "5.0.0",
"resolved": "https://npm.ivanli.cc/path-exists/-/path-exists-5.0.0.tgz",
"integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="
},
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://npm.ivanli.cc/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://npm.ivanli.cc/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -926,9 +1047,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "4.4.3", "version": "4.5.0-beta",
"resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.4.3.tgz", "resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.5.0-beta.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", "integrity": "sha512-7PvWhki2lwukaR9osVhFnNzxaE4LM+gC94dlwcvS+Tqz8+U65va7FbKo02bT+/MFlnMPM8bsPUXvHiMD/Mg3Jg==",
"dev": true "dev": true
}, },
"wrappy": { "wrappy": {
@ -942,6 +1063,11 @@
"resolved": "https://npm.ivanli.cc/yn/-/yn-3.1.1.tgz", "resolved": "https://npm.ivanli.cc/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true "dev": true
},
"yocto-queue": {
"version": "1.0.0",
"resolved": "https://npm.ivanli.cc/yocto-queue/-/yocto-queue-1.0.0.tgz",
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g=="
} }
} }
} }

View File

@ -4,10 +4,12 @@
"description": "Fennec configuration loader and share info.", "description": "Fennec configuration loader and share info.",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/types/index.d.ts", "types": "./lib/types/index.d.ts",
"type": "module",
"scripts": { "scripts": {
"prebuild": "rimraf lib", "prebuild": "rimraf lib",
"build": "tsc -p \"tsconfig.build.json\"", "build": "tsc -p \"tsconfig.build.json\"",
"start:example-register": "DEBUG=* ts-node ./examples/register.ts", "start:example-register": "DEBUG=* ts-node ./examples/register.ts",
"start:example-config-loader": "DEBUG=* node --loader ts-node/esm ./examples/config-loader.ts",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
@ -23,7 +25,9 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.3.2", "debug": "^4.3.2",
"esm": "^3.2.25",
"etcd3": "^1.1.0", "etcd3": "^1.1.0",
"find-up": "^6.1.0",
"js-yaml": "^4.1.0" "js-yaml": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -33,6 +37,6 @@
"@types/node": "^14.17.17", "@types/node": "^14.17.17",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"typescript": "^4.4.3" "typescript": "^4.5.0-beta"
} }
} }

View File

@ -1,29 +1,68 @@
import { AppConfig } from "./app-config.model"; import { AppConfig } from "./app-config.model.js";
import { debug } from "console";
import { Etcd3 } from "etcd3"; import { Etcd3 } from "etcd3";
import { load } from "js-yaml"; import { load } from "js-yaml";
import { hostname } from "os"; import { hostname } from "os";
import { getEtcdInstance } from "./etcd-connection"; import { getEtcdInstance } from "./etcd-connection.js";
import { readFile } from "fs/promises";
import debug from "debug";
class ConfigLoader { export class ConfigLoader {
private etcd: Etcd3; private etcd: Etcd3;
private readonly currHost = hostname(); private readonly currHost = hostname();
private readonly configKeyPrefix = "share/config"; private readonly configKeyPrefix = "share/config";
private readonly logger = debug("fennec:config:loader");
constructor(config: AppConfig) { constructor(config?: AppConfig) {
this.etcd = getEtcdInstance(config); this.etcd = getEtcdInstance(config);
} }
async loadConfig(configKey: string): Promise<any> { async loadConfig<T extends Object>(configKey?: string): Promise<any> {
const str = await this.etcd const config = await (await import("find-up"))
.get(`${this.configKeyPrefix}/${this.currHost}/${configKey}`) .findUp(["config,yml", "config.yaml"])
.string(); .then((path) => {
if (path) {
return readFile(path, "utf8").then((str) => load(str));
} else {
return undefined;
}
});
if (!str) { if (configKey === undefined) {
debug("config-loader", `No config found for ${configKey}`); if (process.env.CONFIG_KEY) {
return null; configKey = process.env.CONFIG_KEY;
} else if (process.env.config_key) {
configKey = process.env.config_key;
} else {
configKey = await (
await import("find-up")
)
.findUp("package.json")
.then((path) => {
if (path) {
return readFile(path, "utf8");
} else {
throw new Error(
"Fallback to using package name as configuration name"
);
}
})
.then((json) => JSON.parse(json)?.name as string);
}
} }
return load(str); const etcdConfigKey = `${this.configKeyPrefix}/${this.currHost}/${configKey}`;
const str = await this.etcd.get(etcdConfigKey).string();
if (!str || !config) {
this.logger(
`Not define configuration.
- Put configuration in %s
- Provide configuration key in CONFIG_KEY environment variable
- Create "config.yml" file in current working directory (CWD)`,
etcdConfigKey
);
throw new Error(`Not define configuration.`);
}
return Object.assign(config, load(str)) as T;
} }
} }

View File

@ -1,14 +1,14 @@
import { Etcd3 } from "etcd3"; import { Etcd3 } from "etcd3";
import { AppConfig } from "./app-config.model"; import { AppConfig } from "./app-config.model.js";
function connectEtcd(config: AppConfig) { function connectEtcd(config?: AppConfig) {
return new Etcd3({ return new Etcd3({
...config.etcd, ...(config?.etcd ?? { hosts: ["http://rpi:2379"] }),
}); });
} }
let instance: Etcd3; let instance: Etcd3;
export function getEtcdInstance(config: AppConfig) { export function getEtcdInstance(config?: AppConfig) {
if (!instance) { if (!instance) {
instance = connectEtcd(config); instance = connectEtcd(config);
} }

View File

@ -1,4 +1,4 @@
export * from "./app-config.model"; export * from "./app-config.model.js";
export * from "./config-loader"; export * from "./config-loader.js";
export * from "./etcd-connection"; export * from "./etcd-connection.js";
export * from "./service-register"; export * from "./service-register.js";

View File

@ -1,7 +1,7 @@
import { AppConfig } from "./app-config.model"; import { AppConfig } from "./app-config.model.js";
import { Etcd3 } from "etcd3"; import { Etcd3 } from "etcd3";
import { hostname } from "os"; import { hostname } from "os";
import { getEtcdInstance } from "./etcd-connection"; import { getEtcdInstance } from "./etcd-connection.js";
import debug from "debug"; import debug from "debug";
export class ServiceRegister { export class ServiceRegister {
@ -12,7 +12,7 @@ export class ServiceRegister {
private readonly logger = debug("fennec:config:register"); private readonly logger = debug("fennec:config:register");
constructor(config: AppConfig) { constructor(config?: AppConfig) {
this.etcd = getEtcdInstance(config); this.etcd = getEtcdInstance(config);
} }

View File

@ -4,6 +4,7 @@
"outDir": "./lib", "outDir": "./lib",
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
"declarationDir": "./lib/types" "declarationDir": "./lib/types",
} "module": "node12",
},
} }