af-consul
Version:
A highly specialized function library
347 lines • 16.2 kB
JavaScript
/* 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
;