UNPKG

af-consul

Version:

A highly specialized function library

347 lines 16.2 kB
"use strict"; /* eslint-disable no-console,prefer-spread */ // noinspection UnnecessaryLocalVariableJS,JSUnusedGlobalSymbols var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getConsulApiCached = exports.prepareConsulAPI = void 0; const consul_1 = __importDefault(require("consul")); const async_mutex_1 = require("async-mutex"); const color_1 = require("./lib/color"); const curl_text_1 = require("./lib/curl-text"); const http_request_text_1 = __importDefault(require("./lib/http-request-text")); const logger_stub_1 = __importDefault(require("./lib/logger-stub")); const fqdn_1 = require("./lib/fqdn"); const constants_1 = require("./constants"); const utils_1 = require("./lib/utils"); const hash_1 = require("./lib/hash"); const mutex = new async_mutex_1.Mutex(); const dbg = { on: constants_1.CONSUL_DEBUG_ON, curl: /af-consul:curl/i.test(constants_1.DEBUG), }; const debug = (msg) => { if (dbg.on) { console.log(`${color_1.magenta}${constants_1.PREFIX}${color_1.reset}: ${msg}`); } }; const agentTypeS = Symbol.for('agentType'); const consulConfigTypes = ['reg', 'dev', 'prd']; const getConsulAgentOptions = async (clOptions) => { const { agent, service, } = clOptions.config.consul; // const regAgent = { ..._.pick(reg, ['host', 'port', 'secure', 'token']) }; const secure_ = (0, utils_1.parseBoolean)(agent.reg.secure); const result = {}; const reg = { host: agent.reg.host || (await (0, fqdn_1.getFQDNCached)()) || process.env.HOST_HOSTNAME || (service === null || service === void 0 ? void 0 : service.host) || '127.0.0.1', port: String(agent.reg.port || (secure_ ? 433 : 8500)), secure: secure_, defaults: agent.reg.token ? { token: agent.reg.token } : undefined, }; result.reg = reg; ['dev', 'prd'].forEach((id) => { if (agent[id]) { const { host, port, secure, token, dc, } = agent[id]; result[id] = { host: String(host || reg.host), port: String(port || reg.port), secure: (0, utils_1.parseBoolean)(secure == null ? reg.secure : secure), defaults: token ? { token } : reg.defaults, dc, }; } else { result[id] = { ...reg }; } }); consulConfigTypes.forEach((id) => { if (!Number(result[id].port)) { throw new Error(`The port for consul agent[${id}] is invalid: [${result[id].port}]`); } // @ts-ignore result[id][agentTypeS] = id; }); return result; }; let requestCounter = 0; const prepareConsulAPI = async (clOptions) => { let logger = (clOptions.logger || logger_stub_1.default); if (!(logger === null || logger === void 0 ? void 0 : logger.info)) { logger = logger_stub_1.default; } const fullConsulAgentOptions = await getConsulAgentOptions(clOptions); if (dbg.on) { debug(`CONSUL AGENT OPTIONS:\n${JSON.stringify(fullConsulAgentOptions, undefined, 2)}`); } const consulInstances = {}; consulConfigTypes.forEach((id) => { const consulAgentOptions = fullConsulAgentOptions[id]; const consulInstance = new consul_1.default(consulAgentOptions); // { host, port, secure, defaults: { token } } // @ts-ignore consulInstance[agentTypeS] = id; consulInstances[id] = consulInstance; consulInstance._ext('onRequest', (request, next) => { request._id_ = ++requestCounter; if (dbg.on) { const msg = dbg.curl ? (0, curl_text_1.getCurlText)(request) : (0, http_request_text_1.default)(request); debug(`[${request._id_}] ${color_1.yellow}${msg}${color_1.reset}`); } next(); }); consulInstance._ext('onResponse', (request, next) => { var _a, _b, _c, _d, _e, _f; const rqId = `[${request._id_}] `; try { const { res } = request || {}; const { statusCode = 0, body = null, } = res || {}; debug(`${rqId}HTTP Status: ${statusCode}`); if (statusCode > 299 && !((_c = (_b = (_a = request.opts) === null || _a === void 0 ? void 0 : _a.skipCodes) === null || _b === void 0 ? void 0 : _b.includes) === null || _c === void 0 ? void 0 : _c.call(_b, statusCode))) { const serviceName = (_f = (_e = (_d = request._args) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : ''; if (body) { logger.error(`${rqId}[${serviceName ? `consul.${serviceName}` : 'CONSUL'}] ERROR: ${JSON.stringify(body)}`); } else { debug(`${rqId}res.body not found! res: ${res}`); } } } catch (err) { logger.error(`ERROR (onResponse ${rqId}): \n err.message: ${err.message}\n err.stack:\n${err.stack}\n`); } next(); }); }); const getAgentTypeByServiceID = (serviceId) => { const agentType = serviceId.substring(0, 3); return (/(dev|prd)/.test(agentType) ? agentType : 'reg'); }; const getAgentOptionsByServiceID = (serviceId) => fullConsulAgentOptions[getAgentTypeByServiceID(serviceId)]; const getConsulInstanceByServiceID = (serviceId) => consulInstances[getAgentTypeByServiceID(serviceId)]; // @ts-ignore request.res.body request.res.statusCode const common = async (fnName, { consulInstance, agentOptions, options, withError, result, }) => { let fn; if (consulInstance) { fn = consulInstance; } else if (agentOptions) { fn = (0, consul_1.default)(agentOptions); } else { fn = consulInstances.reg; } const namesArr = fnName.split('.'); const method = namesArr.pop(); namesArr.forEach((v) => { // @ts-ignore fn = fn[v]; }); try { // @ts-ignore const res = await fn[method].call(fn, options); return result || res; } catch (err) { logger.error(`[consul.${fnName}] ERROR:\n err.message: ${err.message}\n err.stack:\n${err.stack}\n`); return withError ? err : false; } }; const api = { // Returns the services the agent is managing. - список сервисов на этом агенте async agentServiceList(apiArgs = {}) { // ### GET http://<*.host>:<*.port>/v1/agent/services if (!apiArgs.consulInstance && !apiArgs.agentOptions) { apiArgs.consulInstance = consulInstances.reg; } const result = await common('agent.service.list', apiArgs); return result; }, // Lists services in a given datacenter async catalogServiceList(dc, apiArgs = {}) { // ### GET https://<context.host>:<context.port>/v1/catalog/services?dc=<dc> if (!apiArgs.consulInstance && !apiArgs.agentOptions) { const agentType = (Object.entries(fullConsulAgentOptions) .find(([, v]) => v.dc === dc) || ['dev'])[0]; // @ts-ignore apiArgs.consulInstance = consulInstances[agentType]; } apiArgs.options = dc; const result = await common('catalog.service.list', apiArgs); return result; }, // Returns the nodes and health info of a service async consulHealthService(apiArgs) { // ### GET https://<context.host>:<context.port>/v1/health/service/<apiArgs.options.serviceId>?passing=true&dc=<apiArgs.options.dc || context.dc> const { service: serviceId, dc, } = apiArgs.options; if (!dc) { const agentOptions = getAgentOptionsByServiceID(serviceId); apiArgs.options.dc = agentOptions.dc || undefined; } if (!apiArgs.consulInstance && !apiArgs.agentOptions) { apiArgs.consulInstance = getConsulInstanceByServiceID(serviceId); } const result = await common('health.service', apiArgs); return result; }, async getServiceInfo(serviceName) { var _a; // ### GET https://<context.host>:<context.port>/v1/health/service/<apiArgs.options.serviceId>?passing=true&dc=<apiArgs.options.dc || context.dc> const result = await this.consulHealthService({ options: { service: serviceName, passing: true, }, }); const res = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.Service; if (!res) { logger.debug(`No info about service ID ${color_1.cyan}${serviceName}`); // VVR } return res; }, async getServiceSocket(serviceName, defaults) { var _a; if (process.env.USE_DEFAULT_SERVICE_SOCKET) { return defaults; } // В функции consulHealthService используется агент dev/prd в зависимости от префикаса const result = await this.consulHealthService({ options: { service: serviceName, passing: true, }, }); if (!result || !result.length) { logger.warn(`CONSUL: No working service found: ${color_1.cyan}${serviceName}${color_1.reset}. Return defaults ${defaults.host}:${defaults.port}`); return defaults; } const { Address = (_a = result[0].Node) === null || _a === void 0 ? void 0 : _a.Node, Port, } = (result[0].Service || {}); const host = await (0, fqdn_1.getFQDNCached)(Address); return { host: host || Address || '', port: Port, }; }, // Registers a new service. async agentServiceRegister(options, withError = false) { // ### PUT http://<reg.host>:<reg.port>/v1/agent/service/register const result = await common('agent.service.register', { options, withError, result: true }); return result; }, // Deregister a service. async agentServiceDeregister(serviceId, apiArgs = {}) { // ### PUT http://<reg.host>:<reg.port>/v1/agent/service/deregister/<serviceId> apiArgs.options = serviceId; apiArgs.result = true; if (!apiArgs.agentOptions && !apiArgs.consulInstance) { apiArgs.consulInstance = consulInstances.reg; } const result = await common('agent.service.deregister', apiArgs); return result; }, async deregisterIfNeed(serviceId, agentOptions) { var _a; const apiArgs = { agentOptions }; const healthServiceInfo = await this.checkIfServiceRegistered(serviceId, apiArgs); if (healthServiceInfo) { const nodeHost = (((_a = healthServiceInfo.Node) === null || _a === void 0 ? void 0 : _a.Node) || '').toLowerCase() .split('.')[0] || ''; const [agentType = 'reg'] = Object.entries(fullConsulAgentOptions) .find(([, aOpt]) => aOpt.host.toLowerCase() .startsWith(nodeHost)) || []; // @ts-ignore apiArgs.consulInstance = consulInstances[agentType]; const isDeregister = await this.agentServiceDeregister(serviceId, apiArgs); // @ts-ignore const m = (wasnt = '') => `Previous registration of service '${color_1.cyan}${serviceId}${color_1.reset}'${wasnt} removed from consul agent ${color_1.blue}${fullConsulAgentOptions[agentType].host}${color_1.reset}`; if (isDeregister) { logger.info(m()); } else { logger.error(m(' was NOT')); return false; } } else { logger.info(`Service '${color_1.cyan}${serviceId}${color_1.reset}' is not registered in Consul`); } return true; }, // Returns the members as seen by the consul agent. - список агентов (нод) agentMembers: async (apiArgs = {}) => { // ### GET http://<reg.host>:<reg.port>/v1/agent/members if (!apiArgs.consulInstance && !apiArgs.agentOptions) { apiArgs.consulInstance = consulInstances.reg; } const result = await common('agent.members', apiArgs); return result; }, async checkIfServiceRegistered(serviceIdOrName, apiArgs = {}) { if (!apiArgs.consulInstance && !apiArgs.agentOptions) { apiArgs.consulInstance = getConsulInstanceByServiceID(serviceIdOrName); } const result = await this.consulHealthService({ options: { service: serviceIdOrName } }); return result === null || result === void 0 ? void 0 : result[0]; }, async registerService(registerConfig, registerOptions) { const serviceId = registerConfig.id || registerConfig.name; const srv = `Service '${color_1.cyan}${serviceId}${color_1.reset}'`; const serviceInfo = await this.getServiceInfo(serviceId); const diff = (0, utils_1.serviceConfigDiff)(registerConfig, serviceInfo); const isAlreadyRegistered = !!serviceInfo; const already = () => { if (!registerOptions.noAlreadyRegisteredMessage) { logger.info(`${srv} already registered in Consul`); } return 'already'; }; switch (registerOptions.registerType) { case 'if-config-differ': { if (!diff.length) { return already(); } logger.info(`${srv}. Configuration difference detected. New: config.${diff[0]}=${diff[1]} / Current: config.${diff[2]}=${diff[3]}`); break; } case 'if-not-registered': { if (isAlreadyRegistered) { return already(); } break; } } if (isAlreadyRegistered && registerOptions.deleteOtherInstance) { if (await this.agentServiceDeregister(serviceId)) { logger.info(`Previous registration of ${srv} removed from Consul`); } } const isJustRegistered = await this.agentServiceRegister(registerConfig); if (isJustRegistered) { logger.info(`${srv} is registered in Consul`); } else { logger.error(`${srv} is NOT registered in Consul`); } return isJustRegistered ? 'just' : false; }, agentOptions: fullConsulAgentOptions, getConsulAgentOptions, }; return api; }; exports.prepareConsulAPI = prepareConsulAPI; const consulApiCache = {}; const getConsulApiCached = async (clOptions) => mutex .runExclusive(async () => { const hash = (0, hash_1.getConfigHash)(clOptions); if (!consulApiCache[hash]) { (0, utils_1.minimizeCache)(consulApiCache, constants_1.MAX_API_CACHED); const value = await (0, exports.prepareConsulAPI)(clOptions); consulApiCache[hash] = { created: Date.now(), value, }; } return consulApiCache[hash].value; }); exports.getConsulApiCached = getConsulApiCached; //# sourceMappingURL=prepare-consul-api.js.map