UNPKG

@sasjs/cli

Version:

Command line interface for SASjs

336 lines (335 loc) 19.5 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.getContextName = exports.executeJobViya = void 0; var displayResult_1 = require("../../../../utils/displayResult"); var file_1 = require("../../../../utils/file"); var fetchLogFileContent_1 = require("../../../shared/fetchLogFileContent"); var path_1 = __importDefault(require("path")); var node_1 = require("@sasjs/adapter/node"); var utils_1 = require("@sasjs/utils"); var utils_2 = require("../../../../utils/"); var command_1 = require("../../../../types/command"); var utils_3 = require("../utils"); /** * Triggers existing job for execution on Viya server. * @param {object} sasjs - configuration object of SAS adapter. * @param {string} accessToken - an access token for an authorized user. * @param {string} jobPath - location of the job on SAS Drive. * @param {object} target - SAS server configuration. * @param {boolean} waitForJob - flag indicating if CLI should wait for job completion. * @param {boolean | string} output - flag indicating if CLI should print out job output. If string was provided, it will be treated as file path to store output. If filepath wasn't provided, output.json file will be created in current folder. * @param {string | undefined} logFile - flag indicating if CLI should fetch and save log to provided file path. If filepath wasn't provided, {job}.log file will be created in current folder. * @param {string | undefined} statusFile - flag indicating if CLI should fetch and save status to the local file. If filepath wasn't provided, it will only print on console. * @param {boolean} ignoreWarnings - flag indicating if CLI should return status '0', when the job state is warning. * @param {string | undefined} source - an optional path to a JSON file containing macro variables. * @param {boolean} streamLog - a flag indicating if the logs should be streamed to the supplied log path during job execution. This is useful for getting feedback on long running jobs. */ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output, logFile, statusFile, ignoreWarnings, source, streamLog) { var _a, _b; return __awaiter(this, void 0, void 0, function () { var pollOptions, result, startTime, contextName, macroVars, submittedJob, endTime, outputJson, currentDirPath, outputPath, folderPath, parentFolderPath, logObj, logUrl, logCount, logData, error_1; var _this = this; return __generator(this, function (_c) { switch (_c.label) { case 0: pollOptions = { maxPollCount: 0, pollInterval: 0, streamLog: streamLog, logFolderPath: logFile }; startTime = new Date().getTime(); if (!statusFile) return [3 /*break*/, 2]; return [4 /*yield*/, displayStatus({ state: 'Initiating' }, statusFile)]; case 1: _c.sent(); _c.label = 2; case 2: (_a = process.logger) === null || _a === void 0 ? void 0 : _a.success("Job located at ".concat(jobPath, " has been submitted for execution...")); contextName = getContextName(target); if (!source) return [3 /*break*/, 5]; if (!(0, file_1.isJsonFile)(source)) throw 'Source file has to be JSON.'; return [4 /*yield*/, (0, utils_1.fileExists)(source).catch(function (_) { throw 'Error while checking if source file exists.'; })]; case 3: _c.sent(); return [4 /*yield*/, (0, utils_1.readFile)(source).catch(function (_) { throw 'Error while reading source file.'; })]; case 4: source = _c.sent(); macroVars = JSON.parse(source); if (!(0, utils_1.isMacroVars)(macroVars)) { throw "Provided source is not valid. An example of valid source:\n{ macroVars: { name1: 'value1', name2: 'value2' } }"; } _c.label = 5; case 5: return [4 /*yield*/, sasjs .startComputeJob(jobPath, null, { contextName: contextName }, authConfig, waitForJob || !!logFile, pollOptions, true, macroVars === null || macroVars === void 0 ? void 0 : macroVars.macroVars) .catch(function (err) { return __awaiter(_this, void 0, void 0, function () { var logData; var _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (!(err instanceof node_1.NoSessionStateError)) return [3 /*break*/, 4]; if (!(err === null || err === void 0 ? void 0 : err.logUrl)) return [3 /*break*/, 3]; return [4 /*yield*/, (0, fetchLogFileContent_1.fetchLogFileContent)(sasjs, authConfig.access_token, err.logUrl, 100000) // save log ]; case 1: logData = _c.sent(); // save log return [4 /*yield*/, (0, utils_3.saveLog)(logData, logFile, jobPath)]; case 2: // save log _c.sent(); _c.label = 3; case 3: (0, displayResult_1.displayError)(err); // terminate process with status code 2 (0, utils_2.terminateProcess)(2); _c.label = 4; case 4: if (!(err === null || err === void 0 ? void 0 : err.log)) return [3 /*break*/, 6]; return [4 /*yield*/, (0, utils_3.saveLog)(err.log, logFile, jobPath) // handle use case when job wasn't found ]; case 5: _c.sent(); _c.label = 6; case 6: // handle use case when job wasn't found if ((err === null || err === void 0 ? void 0 : err.name) === 'NotFoundError') { throw new Error("Job located at '".concat(jobPath, "' was not found.")); } // get additional information about error if it is present result = typeof err === 'object' && err.job && ((_a = err.job) === null || _a === void 0 ? void 0 : _a.state) ? JSON.stringify({ state: (_b = err.job) === null || _b === void 0 ? void 0 : _b.state }) : "".concat(err); if (err.job) { return [2 /*return*/, err.job]; } return [2 /*return*/]; } }); }); }) // job execution end time ]; case 6: submittedJob = _c.sent(); endTime = new Date().getTime(); if (result) { (0, displayResult_1.displayError)(result, 'An error has occurred when executing a job.'); } if (submittedJob && submittedJob.job) submittedJob = submittedJob.job; if (!statusFile) return [3 /*break*/, 8]; return [4 /*yield*/, displayStatus(submittedJob, statusFile, result, true) // job execution completed successfully ]; case 7: _c.sent(); _c.label = 8; case 8: if (!(submittedJob && submittedJob.links)) return [3 /*break*/, 21]; if (!result) result = true; if (!(output || logFile)) return [3 /*break*/, 20]; _c.label = 9; case 9: _c.trys.push([9, 19, , 20]); outputJson = JSON.stringify(submittedJob, null, 2); if (!(typeof output === 'string')) return [3 /*break*/, 14]; currentDirPath = path_1.default.isAbsolute(output) ? '' : process.projectDir; outputPath = path_1.default.join(currentDirPath, /\.[a-z]{3,4}$/i.test(output) ? output : path_1.default.join(output, 'output.json')); folderPath = outputPath.split(path_1.default.sep); folderPath.pop(); parentFolderPath = folderPath.join(path_1.default.sep); return [4 /*yield*/, (0, utils_1.folderExists)(parentFolderPath)]; case 10: if (!!(_c.sent())) return [3 /*break*/, 12]; return [4 /*yield*/, (0, utils_1.createFolder)(parentFolderPath)]; case 11: _c.sent(); _c.label = 12; case 12: return [4 /*yield*/, (0, utils_1.createFile)(outputPath, outputJson)]; case 13: _c.sent(); (0, displayResult_1.displaySuccess)("Output saved to: ".concat(outputPath)); return [3 /*break*/, 15]; case 14: if (output) { // log job object to the console ; (process.logger || console).log(outputJson); } _c.label = 15; case 15: if (!(logFile && !pollOptions.streamLog)) return [3 /*break*/, 18]; logObj = submittedJob.links.find(function (link) { return link.rel === 'log' && link.method === 'GET'; }); if (!logObj) return [3 /*break*/, 18]; logUrl = target.serverUrl + logObj.href; logCount = submittedJob.logStatistics.lineCount; return [4 /*yield*/, (0, fetchLogFileContent_1.fetchLogFileContent)(sasjs, authConfig.access_token, logUrl, logCount)]; case 16: logData = _c.sent(); return [4 /*yield*/, (0, utils_3.saveLog)(logData, logFile, jobPath)]; case 17: _c.sent(); _c.label = 18; case 18: result = submittedJob; return [3 /*break*/, 20]; case 19: error_1 = _c.sent(); // handle use case when submitted job can't be parsed result = false; (0, displayResult_1.displayError)(null, 'An error has occurred when parsing an output of the job.'); return [3 /*break*/, 20]; case 20: // wait for job state and return status(0,1,2) based on it if (waitForJob && submittedJob.state) { switch (submittedJob.state) { case 'completed': (0, utils_2.terminateProcess)(0); case 'warning': // if ignoreWarnings flag is present, process will be terminated with // status code 0, otherwise status code will be 1 (0, utils_2.terminateProcess)(ignoreWarnings ? 0 : 1); case 'error': (0, utils_2.terminateProcess)(2); default: break; } } return [3 /*break*/, 22]; case 21: if (result) { (0, utils_2.terminateProcess)(command_1.ReturnCode.InternalError); } _c.label = 22; case 22: (_b = process.logger) === null || _b === void 0 ? void 0 : _b.info("This job was executed in ".concat((endTime - startTime) / 1000, " seconds")); return [2 /*return*/, result]; } }); }); } exports.executeJobViya = executeJobViya; // REFACTOR: should be a utility /** * Gets job execution context name. * @param target - SAS server configuration. * @returns - job execution context name. */ function getContextName(target) { var _a; // default execution context of @sasjs/cli var defaultContextName = utils_2.contextName; // get gontext name from target if (target === null || target === void 0 ? void 0 : target.contextName) return target.contextName; (_a = process.logger) === null || _a === void 0 ? void 0 : _a.warn("`contextName` was not provided in your target configuration. Falling back to context ".concat(defaultContextName)); return defaultContextName; } exports.getContextName = getContextName; /** * Logs to the console or writes job status to the file. * @param submittedJob - job submitted fro execution. * @param statusFile - file path to the status file. * @param error - error generated during job execution. * @param displayStatusFilePath - boolean indicating if file path to the status file should be logged. */ function displayStatus(submittedJob, statusFile, error, displayStatusFilePath) { if (error === void 0) { error = ''; } if (displayStatusFilePath === void 0) { displayStatusFilePath = false; } return __awaiter(this, void 0, void 0, function () { var adapterStatus, status, folderPath, parentFolderPath; return __generator(this, function (_a) { switch (_a.label) { case 0: adapterStatus = (submittedJob === null || submittedJob === void 0 ? void 0 : submittedJob.state) ? submittedJob.state : 'Not Available'; status = adapterStatus === 'Not Available' ? "Job Status: ".concat(adapterStatus, "\nDetails: ").concat(error, "\n") : "Job Status: ".concat(adapterStatus); // handle job states 'Initiating' and 'completed' if (adapterStatus === 'Initiating' || adapterStatus === 'completed') { (0, displayResult_1.displaySuccess)(status); } // display an job status as an error else { (0, displayResult_1.displayError)({}, status); } if (!statusFile) return [3 /*break*/, 5]; folderPath = statusFile.split(path_1.default.sep); folderPath.pop(); parentFolderPath = folderPath.join(path_1.default.sep); return [4 /*yield*/, (0, utils_1.folderExists)(parentFolderPath)]; case 1: if (!!(_a.sent())) return [3 /*break*/, 3]; return [4 /*yield*/, (0, utils_1.createFolder)(parentFolderPath)]; case 2: _a.sent(); _a.label = 3; case 3: return [4 /*yield*/, (0, utils_1.createFile)(statusFile, status) // display file path to status file ]; case 4: _a.sent(); // display file path to status file if (displayStatusFilePath) (0, displayResult_1.displaySuccess)("Status saved to: ".concat(statusFile)); _a.label = 5; case 5: return [2 /*return*/]; } }); }); }