@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
265 lines • 25 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.waitForExtractorMessage = exports.handleExtract = exports.validateConnectionData = void 0;
var http_status_codes_1 = require("http-status-codes");
var node_path_1 = __importDefault(require("node:path"));
var uuid_1 = require("uuid");
var eps_error_1 = require("../../../types/eps-error");
var eps_error_info_1 = require("../../../types/eps-error-info");
var connector_launch_status_1 = require("../../../../shared/enums/connector-launch-status");
var extractor_type_1 = require("../../../../shared/enums/extractor-type");
var error_codes_1 = require("../../../../shared/errors/error-codes");
var error_source_1 = require("../../../../shared/errors/error-source");
var status_code_1 = require("../../../../shared/errors/status-code");
var extractor_status_1 = require("../../../types/extractor-status");
var eps_logger_1 = require("../../../logging/eps-logger");
var process_manager_1 = require("../../components/process-manager");
var app_global_1 = __importDefault(require("../../core/app-global"));
var eps_process_types_1 = require("../../../types/eps-process-types");
var extractor_message_1 = require("../../../types/extractor-message");
var handler_utils_1 = require("./handler-utils");
var EXTRACTOR_APP_PATH = node_path_1.default.resolve(__dirname, '../../../extractor/extractor-app.js');
function validateConnectionData(body) {
if (!body || typeof body !== 'object' || Object.keys(body).length === 0) {
var message = 'Bad Request: Request body is empty or not JSON; request body must have ConnectionData.';
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.FailedPrecondition, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.M92GXFQP, message);
throw Error(message);
}
// Check there are some handlerInputs
var handlerInputs = body.handlerInputs;
if (!handlerInputs || !Array.isArray(handlerInputs) || handlerInputs.length === 0) {
var errorMessage = 'Bad Request: handlerInputs must be provided in request body.';
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.FailedPrecondition, error_source_1.ErrorSources.Configuration, error_codes_1.ErrorCodes.ENT97JSH, errorMessage);
throw new eps_error_1.EPSError(errorMessage, new eps_error_info_1.EPSErrorInfo(handler_utils_1.ERROR_CODE_BAD_TACO), connector_launch_status_1.ConnectorLaunchStatus.BadTaco);
}
}
exports.validateConnectionData = validateConnectionData;
function handleEpsExtractor(extractorLaunchedEvent, extractHandlerRequest) {
return __awaiter(this, void 0, void 0, function () {
var connectionId, secrets, connectionData, extractorMessage, extractorType, extractorLaunchSuccessOptions, extractApiResponse, errorMessage;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
connectionId = extractHandlerRequest.connectionId, secrets = extractHandlerRequest.secrets, connectionData = extractHandlerRequest.connectionData;
return [4 /*yield*/, launchEpsExtractor(extractHandlerRequest)];
case 1:
extractorMessage = _a.sent();
extractorType = extractor_type_1.ExtractorType.eps;
extractorLaunchSuccessOptions = {
type: 'success',
extractorType: extractorType,
extractorMessage: extractorMessage,
connectionData: connectionData,
secrets: secrets,
};
extractorLaunchedEvent.emit((0, handler_utils_1.getExtractorLaunchedConnectionId)(connectionId), extractorLaunchSuccessOptions);
extractApiResponse = {
status: connector_launch_status_1.ConnectorLaunchStatus.Extract,
extractorType: extractorType,
port: extractorMessage.port,
extractorId: extractorMessage.extractorId,
};
errorMessage = extractorMessage.errorMessage;
if (errorMessage) {
extractApiResponse.status = connector_launch_status_1.ConnectorLaunchStatus.Error;
extractApiResponse.errorMessage = errorMessage;
}
return [2 /*return*/, {
httpStatusCode: http_status_codes_1.StatusCodes.OK,
response: extractApiResponse,
}];
}
});
});
}
function handleExtract(extractorLaunchedEvent, extractHandlerRequest) {
return __awaiter(this, void 0, void 0, function () {
var connectionId, error_1, options;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
connectionId = extractHandlerRequest.connectionId;
// Done with browser, mark completed to declare it was completed properly.
app_global_1.default.connectionManager.setIsSubmitted(connectionId, true);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, handleEpsExtractor(extractorLaunchedEvent, extractHandlerRequest)];
case 2: return [2 /*return*/, _a.sent()];
case 3:
error_1 = _a.sent();
options = {
type: 'failure',
error: error_1,
connectorLaunchStatus: connector_launch_status_1.ConnectorLaunchStatus.BadTaco,
};
extractorLaunchedEvent.emit((0, handler_utils_1.getExtractorLaunchedConnectionId)(connectionId), options);
return [2 /*return*/, {
httpStatusCode: http_status_codes_1.StatusCodes.OK,
response: new eps_error_1.EPSError(error_1, new eps_error_info_1.EPSErrorInfo(handler_utils_1.ERROR_CODE_BAD_TACO), connector_launch_status_1.ConnectorLaunchStatus.BadTaco),
}];
case 4: return [2 /*return*/];
}
});
});
}
exports.handleExtract = handleExtract;
function launchEpsExtractor(extractHandlerRequest) {
return __awaiter(this, void 0, void 0, function () {
var tacoPath, connectionId, connectorClass, connectionData, secrets, distinctConnectorName, handlersDir, permission, extractorOptions, portConfig, extractorId, extractorProcess, extractorMessage;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
tacoPath = extractHandlerRequest.tacoPath, connectionId = extractHandlerRequest.connectionId, connectorClass = extractHandlerRequest.connectorClass, connectionData = extractHandlerRequest.connectionData, secrets = extractHandlerRequest.secrets;
return [4 /*yield*/, (0, handler_utils_1.getOrInitializeDistinctConnectorName)({
connectionId: connectionId,
connectorClass: connectorClass,
tacoPath: tacoPath,
})];
case 1:
distinctConnectorName = _a.sent();
if (app_global_1.default.connectionManager.hasActiveConnection(connectionId)) {
// With ExtractorHandler, this should never be hit. Keep it here for a sanity check to avoid creating an orphan extractor process.
throw new Error("There is already a live extractor for ConnectionId '".concat(connectionId, "'."));
}
handlersDir = app_global_1.default.tacoInitializer.getConnectorHandlersPath(distinctConnectorName);
permission = app_global_1.default.tacoInitializer.getConnectorConfig(distinctConnectorName).permission;
extractorOptions = {
handlersDir: handlersDir,
connectorClass: connectorClass,
connectionId: connectionId,
handlerInputs: connectionData.handlerInputs,
secrets: secrets,
permission: permission,
};
portConfig = {
portRange: app_global_1.default.portManager.getPortRange(),
reservedPorts: app_global_1.default.portManager.getReservedPorts(),
};
extractorId = (0, uuid_1.v4)();
extractorProcess = app_global_1.default.processManager.fork(eps_process_types_1.EPSProcessName.EPSExtractor, process_manager_1.ProcessType.child, EXTRACTOR_APP_PATH, [
extractorId,
// TOOD: nodejs has size limit on param length. We need to handle it better.
JSON.stringify(extractorOptions),
JSON.stringify(portConfig),
]);
return [4 /*yield*/, waitForExtractorMessage(extractorProcess, connectionId)];
case 2:
extractorMessage = _a.sent();
if ((0, extractor_message_1.isSchemaDoneExtractorMessage)(extractorMessage)) {
app_global_1.default.connectionManager.addExtractorProcess(connectionId, extractorProcess, extractorId);
}
return [2 /*return*/, extractorMessage];
}
});
});
}
/**
* Wait for the extractor process to send the message to EPS.
* The function will settle the promise on the first (known) message sent from extractor.
*/
function waitForExtractorMessage(extractorProcess, connectionId) {
var schemaDone = false;
return new Promise(function (resolve, reject) {
var _a, _b;
(_a = extractorProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) {
eps_logger_1.Logger.info("Extractor (pid:".concat(extractorProcess.pid, ") stdout: ").concat(data));
});
(_b = extractorProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', function (data) {
eps_logger_1.Logger.error("Extractor (pid:".concat(extractorProcess.pid, ") stderr: ").concat(data));
});
extractorProcess.on('message', function (message) {
var status = message.status;
if (status === extractor_status_1.ExtractorStatus.SchemaDone) {
schemaDone = true;
resolve(message);
}
else if (status === extractor_status_1.ExtractorStatus.ExtractorError) {
var errMsg = "Extractor (pid:".concat(extractorProcess.pid, ") reported an error: ").concat(message.errorMessage);
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Cancelled, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.HU3H87WB, errMsg);
// if extractor process crashes due to error, evict the connection
app_global_1.default.connectionManager.free(connectionId);
reject(errMsg);
}
else {
// All messages from extractor should be sent by sendExtractorMessage(), so based on the code,
// this line should never be hit. Just logging here in case anything is missed.
eps_logger_1.Logger.debug(message);
}
});
extractorProcess.on('error', function (err) {
// ERR_IPC_CHANNEL_CLOSED is raised when parent is closing and is ignorable in that case.
var errMsg = "Extractor (pid:".concat(extractorProcess.pid, ") emitted an error event: ").concat(err, ".");
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Cancelled, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.PSZECVJ1, errMsg);
reject(errMsg);
});
extractorProcess.on('disconnect', function () {
if (!schemaDone) {
var errMsg = "Extractor (pid:".concat(extractorProcess.pid, ") disconnected before extractor completes. Check logs for more details.");
reject(errMsg);
}
// When schemaDone is true, the promise is already settled.
// Log here for extractor process status.
eps_logger_1.Logger.info("Extractor (pid:".concat(extractorProcess.pid, ") disconnected."));
});
extractorProcess.on('exit', function (code, signal) {
if (!schemaDone) {
var errMsg = "Extractor (pid:".concat(extractorProcess.pid, ") exited before extractor completes. Check logs for more details.");
reject(errMsg);
}
// When schemaDone is true, the promise is already settled.
// Log here for extractor process status.
eps_logger_1.Logger.info("Extractor (pid:".concat(extractorProcess.pid, ") exited with code ").concat(code, " on signal '").concat(signal, "'."));
});
extractorProcess.on('close', function (code, signal) {
if (!schemaDone) {
var errMsg = "Extractor (pid:".concat(extractorProcess.pid, ") closed before extractor completes. Check logs for more details.");
reject(errMsg);
}
// When schemaDone is true, the promise is already settled.
// Log here for extractor process status.
eps_logger_1.Logger.info("Extractor (pid:".concat(extractorProcess.pid, ") closed with code ").concat(code, " on signal '").concat(signal, "'."));
});
});
}
exports.waitForExtractorMessage = waitForExtractorMessage;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXh0cmFjdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9lcHMvbWFpbi9hcGkvZXBzLWhhbmRsZXJzL2V4dHJhY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsdURBQStDO0FBRy9DLHdEQUE0QjtBQUM1Qiw2QkFBbUM7QUFFbkMsc0RBQW1EO0FBQ25ELGdFQUE0RDtBQUM1RCw0RkFBd0Y7QUFDeEYsMEVBQXVFO0FBQ3ZFLHFFQUFrRTtBQUNsRSx1RUFBcUU7QUFDckUscUVBQWtFO0FBQ2xFLG9FQUFpRTtBQUdqRSwwREFBb0Q7QUFDcEQsb0VBQThEO0FBQzlELHFFQUE2QztBQUM3QyxzRUFBaUU7QUFHakUsc0VBSXlDO0FBRXpDLGlEQUl3QjtBQUV4QixJQUFNLGtCQUFrQixHQUFHLG1CQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxxQ0FBcUMsQ0FBQyxDQUFBO0FBZXpGLFNBQWdCLHNCQUFzQixDQUFDLElBQWE7SUFDbEQsSUFBSSxDQUFDLElBQUksSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3ZFLElBQU0sT0FBTyxHQUFHLHdGQUF3RixDQUFBO1FBQ3hHLG1CQUFNLENBQUMsV0FBVyxDQUFDLHdCQUFVLENBQUMsa0JBQWtCLEVBQUUsMkJBQVksQ0FBQyxNQUFNLEVBQUUsd0JBQVUsQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUE7UUFDcEcsTUFBTSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUE7S0FDckI7SUFFRCxxQ0FBcUM7SUFDN0IsSUFBQSxhQUFhLEdBQUssSUFBK0IsY0FBcEMsQ0FBb0M7SUFDekQsSUFBSSxDQUFDLGFBQWEsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsYUFBYSxD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