UNPKG

@zowe/imperative

Version:
480 lines 19.4 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Operation = void 0; const TaskStage_1 = require("./TaskStage"); const fs = require("fs"); const utilities_1 = require("../../utilities"); const TaskProgress_1 = require("./TaskProgress"); const logger_1 = require("../../logger"); /** * @deprecated */ class Operation { static summarizeResults(operationResults) { let currentResults = operationResults; let overallFailure = false; const staticLogger = logger_1.Logger.getImperativeLogger(); staticLogger.info("***********************************************************************************"); staticLogger.info("**************************** Operation Results Summary ****************************"); if (currentResults == null) { staticLogger.info("**************************************" + "*********************************************"); staticLogger.info("No results to display"); } else { while (currentResults != null) { staticLogger.info("*************************************************" + "**********************************"); if (currentResults.operationFailed) { staticLogger.error("Operation Failed: " + currentResults.operationName); if (currentResults.critical) { overallFailure = true; } } else { staticLogger.info("Operation Succeeded: " + currentResults.operationName); } currentResults.operationObject.logOperationResults(); staticLogger.info("Number of info messages for result: %d", currentResults.infoMessages.length); for (const msg of currentResults.infoMessages) { staticLogger.info(msg); } staticLogger.info("Number of error messages for result: %d", currentResults.errorMessages.length); for (const msg of currentResults.errorMessages) { staticLogger.error(msg); } currentResults = currentResults.nextOperationResult; } } if (overallFailure) { staticLogger.info("**************************************" + "*********************************************"); staticLogger.error("************************The operation has failed. Sorry.***********" + "***************"); staticLogger.info("*******************************************" + "****************************************"); } else { staticLogger.info("***********************************************************************" + "************"); staticLogger.info("**********************The operation has succeeded! Rejoice!*******" + "*****************"); staticLogger.info(Operation.outputSeparator + "****************"); } return overallFailure; } get statusMessage() { return this.mStatusMessage; } get percentComplete() { return this.mPercentComplete; } set percentComplete(newPercent) { if (newPercent > TaskProgress_1.TaskProgress.ONE_HUNDRED_PERCENT) { this.log.warn("An attempt was made to set percent complete in Operation to a value above 100"); this.mPercentComplete = TaskProgress_1.TaskProgress.ONE_HUNDRED_PERCENT; } else { this.mPercentComplete = Math.ceil(newPercent); } } get totalOperations() { return 1; // base case: 1 operation, no subops } /** * Action constructor to build action object * @param {string} opName: the name of the operation * @param {boolean} criticalOperation the operation is critical - meaning if it fails the entire operation fails and * the undo methods should be called. */ constructor(opName, criticalOperation) { this.infoMessages = []; this.errorMessages = []; /** * The full list of operation results */ this.mOperationResults = null; /** * The status of the operation */ this.mStageName = TaskStage_1.TaskStage.NOT_STARTED; this.mPercentComplete = 0; /** * The list of all files that have been created by the service function * @type {Array} */ this.mAllFilesCreated = []; /** * The list of files that will be cleaned during an undo operation. * @type {Array} */ this.mFilesForUndo = []; this.log = logger_1.Logger.getImperativeLogger(); this.mOperationResult = { operationName: opName, resultMessage: "", operationFailed: false, diverge: false, divergeTo: null, continuePath: true, nextOperationResult: null, operationObject: this, operationUndoPossible: false, operationUndoFailed: false, operationUndoAttempted: false, critical: criticalOperation != null ? criticalOperation : false, output: null, infoMessages: [], errorMessages: [] }; } /** * Execute the operation. Set the status of the operation to IN_PROGRESS and invoke the implemented operations * execute method. * * @param {any} inputParameters: The input parameters to this operation. This can be anything as defined by * the operation. * @param {IOperationResultReady} operationComplete: Operation has completed callback */ executeOperation(inputParameters, operationComplete) { /** * Set the status of the operation to in progress, log the operation begin message * and save the callers callback method for after the operation completes. */ this.mStageName = TaskStage_1.TaskStage.IN_PROGRESS; this.logBeginMessage(); this.mOperationCompleteCallback = operationComplete; /** * Execute the operation, the operation must invoke the callback method * to continue the operation sequence. Results will be processed in the * callback function below. */ this.execute(inputParameters, this.operationComplete.bind(this)); } /** * Set the status message for the operation * @param message - the message, including any templates you want replaced like %s or "{{myObject}}" * @param args - variable args as allowed by printf-like syntax or {myObject: this.myObject} * @returns {string} the final translated and formatted string (in case you want to log it etc.) */ setStatusMessage(message, ...args) { if (args != null) { message = utilities_1.TextUtils.formatMessage(message, ...args); } this.mStatusMessage = message; } /** * The perform undo method sets that an attempt was made to perform the undo and invokes the * undo method of the implemented operation. * * Before invoking, any files that have been created and saved on the undo-able list will be cleaned. * * @param {IOperationUndoCompleted} undoCompletedCallback: the undo completed callback */ performUndo(undoCompletedCallback) { this.log.info("Operation: " + this.mOperationResult.operationName + " attempting undo action."); this.setOperationUndoAttempted(); this.deleteAllFilesMarkedForUndo(); this.undo(undoCompletedCallback.bind(this)); } /** * Accessor method for operation result * @returns {IOperationResult}: The operation result */ get operationResult() { const result = this.mOperationResult; result.errorMessages = this.errorMessages; result.infoMessages = this.infoMessages; return result; } /** * Accessor method for operation status * @returns {TaskStage}: The operation status/ stage name e.g. FAILED */ get stageName() { return this.mStageName; } /** * Accessor method to obtain all the files created using the file creator service * @return {string[]} */ get allFilesCreated() { return this.mAllFilesCreated; } /** * Accessor method to obtain all the files created that are marked as delete on undo * @return {string[]} */ get fileToUndo() { return this.mFilesForUndo; } /** * Get the operation name for display and other purposes * @return {string} */ get operationName() { return this.operationResult.operationName; } /** * Set method to indicate that the operation failed. */ setOperationFailed() { this.mOperationResult.operationFailed = true; } /** * Set method to indicate that the operation is "undoable". * * This means that if an operation fails, we will 'rollback' through the completed operations * and undo any that have occurred. */ setOperationUndoable() { this.mOperationResult.operationUndoPossible = true; } /** * Append an additional message to the result message buffer * @param {string} message: The result message you wish to append. */ set operationResultMessage(message) { this.mOperationResult.resultMessage += " " + message; } /** * Determine if the operation failed * * @return {boolean}: If the operation failed */ get operationSucceeded() { return !(this.mStageName === TaskStage_1.TaskStage.FAILED); } /** * Set that the operation undo failed. This is for diagnostic purposes. */ setOperationUndoFailed() { this.mOperationResult.operationUndoFailed = true; } /** * If the operation decides that a different path is required. You can set the next operation to complete * and whether or not you should continue down the original path * * @param {Operation} operation: the operation you use to diverge to * @param {boolean} continuePathAfterDiverge: Indicates that you want to continue down the normal path after * the divergent path is complete. */ setOperationDiverge(operation, continuePathAfterDiverge) { this.mOperationResult.diverge = true; this.mOperationResult.divergeTo = operation; this.mOperationResult.continuePath = continuePathAfterDiverge; } /** * Set the operation undo attempted (whether it succeed or failed is up to the undo method to set) */ setOperationUndoAttempted() { this.mOperationResult.operationUndoAttempted = true; } /** * Add the result to the end of the results list * @param {IOperationResult} result: the result from the last operation */ addResult(result) { if (this.mOperationResults == null) { this.mOperationResults = result; this.log.debug("Queued first operation to result list: " + result.operationName); } else { let prevResult; let currentResult = this.mOperationResults; do { prevResult = currentResult; currentResult = currentResult.nextOperationResult; } while (currentResult != null); prevResult.nextOperationResult = result; this.log.debug("Queued additional operation to result list: " + result.operationName); } } /** * Use this method to create files and optionally push them onto the files created stack that will * be referenced when an undo is required. * * @param {string} filePath * @param {string} message: the error message to print. * @param {boolean} saveFileNameForUndo: Whether you want to keep track of the files created for undo * @param {boolean} isDir: if this is a file or directory */ createFile(filePath, message, saveFileNameForUndo, isDir) { this.log.debug("Operation: " + this.operationName + " creating file/dir: " + filePath); try { if (isDir) { fs.mkdirSync(filePath); } else { fs.closeSync(fs.openSync(filePath, "w")); } this.mAllFilesCreated.push(filePath); if (saveFileNameForUndo) { this.mFilesForUndo.push(filePath); } } catch (error) { const msg = "An error occurred creating file: " + filePath + " during operation: " + this.operationResult.operationName; this.log.error(message); this.log.error(msg); throw new Error(msg); } } /** * Method to print all the files created by the file creator service. */ printFilesCreatedList() { this.log.debug(this.allFilesCreated.length + " files created during " + this.operationResult.operationName); for (let x = 0; x < this.allFilesCreated.length; x++) { this.log.debug("File: " + this.mAllFilesCreated[x]); } } /** * Add a file created by the operation to the list of all files created and optionally mark this file * as undoable, which will cause the undo operation to attempt to remove the file or directory. * * @param {boolean} fileUndoable: The file or directory should be automatically removed by the undo operation. * @param {string} file: The file or directory created. */ addFileCreated(fileUndoable, file) { this.log.debug("Adding files created: %s", file); this.mAllFilesCreated.push(file); if (fileUndoable) { this.mFilesForUndo.push(file); } } /** * Method that is called after the implementing classes undo to delete all the files that have been * saved during the operation (on the mFilesForUndo list). */ deleteAllFilesMarkedForUndo() { this.log.info("Cleaning all files and directories marked as 'undo-able'."); const order = []; /** * Reverse the order that the files/directories were created (in-case they are nested) */ for (let x = this.fileToUndo.length - 1; x > -1; x--) { order.push(this.mFilesForUndo[x]); } for (let x = 0; x < order.length; x++) { this.log.info("Cleaning file: " + this.fileToUndo[x]); try { fs.rmSync(order[x], { recursive: true, force: true }); } catch (error) { this.log.error("An error occurred deleting: " + order[x]); this.log.error("Message: " + error.message); } } } /** * This method logs a standard header for the operation */ logBeginMessage() { const seperator = Operation.outputSeparator; const text = "***Operation: " + this.mOperationResult.operationName + " is starting "; const appendlength = seperator.length - text.length; let append = ""; if (appendlength > 0) { for (let x = 0; x < appendlength; x++) { append += "*"; } } this.log.debug(Operation.outputSeparator); this.log.debug(text + append); this.log.debug(Operation.outputSeparator); this.logOperationBeginMessages(); } /** * This method logs a standard header for the operation */ logEndMessage() { this.log.debug("Operation: " + this.mOperationResult.operationName + " has ended."); if (this.mOperationResult.operationFailed) { this.log.error("Operation " + this.mOperationResult.operationName + " has failed."); } else { this.log.debug("Operation has succeeded."); } } /** * Operation complete callback method. The operation must invoke the callback to indicate that it * has finished. If it does not, the operation sequence will not continue. */ operationComplete(output) { /** * Set the status of the operation based on whether or not it failed. */ if (this.mOperationResult.operationFailed) { this.mStageName = TaskStage_1.TaskStage.FAILED; /** * If the operation is marked as critical, then we cannot continue the set of operations */ if (this.mOperationResult.critical) { this.mOperationResult.continuePath = false; } this.log.error("Operation: " + this.mOperationResult.operationName + " has failed."); this.log.error("Will attempt undo operations where applicable."); } else { this.mStageName = TaskStage_1.TaskStage.COMPLETE; } /** * Add the result of this operation to the operation results chain. */ this.addResult(this.operationResult); this.logEndMessage(); // /** // * Determine if this operation has chosen to diverge to an arbitrary operation // * // * An operation can diverge to any operation it chooses, and the result is chained // */ // if (this.mOperationResult.diverge && this.mOperationResult.continuePath) { // this.addResult(this.mOperationResult.divergeTo.executeOperation()); // } /** * If the operation failed and undo is possible, attempt the undo now. Operations must be undone * in the LIFO order to effectively rollback any changes. */ if (this.mOperationResult.operationFailed && this.mOperationResult.operationUndoPossible) { this.performUndo(this.undoComplete.bind(this)); } /** * Invoke the callers operation complete callback if we are not performing an operation undo. */ if (!this.mOperationResult.operationUndoAttempted) { this.log.debug("Calling operation complete callback for: " + this.mOperationResult.operationName); this.mOperationCompleteCallback(output, this.mOperationResult); } } /** * The operation undo complete callback method, this must be called by the operations undo method * in order for the operation 'undos' to continue. */ undoComplete() { this.log.debug("Calling operation complete callback after undo: " + this.mOperationResult.operationName); this.mOperationCompleteCallback(null, this.mOperationResult); } } exports.Operation = Operation; Operation.NO_PARMS = null; Operation.NO_OUTPUT = null; /** * Traverses the results chain and prints results and gives overall failure status */ Operation.outputSeparator = "*******************************************************************"; //# sourceMappingURL=Operation.js.map