UNPKG

@tableau/taco-toolkit

Version:
265 lines 25 kB
"use strict"; 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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