From 3ab5cfd0665714600e91af0a3c4bd4528f7f051f Mon Sep 17 00:00:00 2001 From: Raghu A Date: Thu, 14 Sep 2023 01:44:34 +0530 Subject: [PATCH 1/3] MWPW-135527 Move common validation and error handler in Floodgate AIO actions Moved the common validation to FgAction and actions updated to refer to FgAction --- actions/FgAction.js | 207 +++++++++++++++++++++++++++++++ actions/copy/copy.js | 127 ++++++++----------- actions/copy/worker.js | 94 +++++++------- actions/delete/delete.js | 124 ++++++++---------- actions/delete/worker.js | 84 ++++++------- actions/fgUser.js | 7 +- actions/promote/createBatch.js | 80 ++++++------ actions/promote/promote.js | 125 ++++++++----------- actions/promote/triggerNTrack.js | 107 ++++++++-------- actions/promote/worker.js | 67 +++++----- 10 files changed, 586 insertions(+), 436 deletions(-) create mode 100644 actions/FgAction.js diff --git a/actions/FgAction.js b/actions/FgAction.js new file mode 100644 index 0000000..f29610b --- /dev/null +++ b/actions/FgAction.js @@ -0,0 +1,207 @@ +/* *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2023 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ************************************************************************* */ +const openwhisk = require('openwhisk'); +const { getAioLogger, actInProgress } = require('./utils'); +const appConfig = require('./appConfig'); +const FgUser = require('./fgUser'); +const FgStatus = require('./fgStatus'); + +const FG_PROOCESS_ACTION = 'fgProcessAction'; +const logger = getAioLogger(); +const BAD_REQUEST_SC = 400; +const AUTH_FAILED_SC = 401; +const GEN_ERROR_SC = 401; +const ALL_OK_SC = 200; + +/** + * The common parameter validation, user check, + */ +class FgAction { + validations = {}; + + constructor(action, params) { + this.action = action || FG_PROOCESS_ACTION; + appConfig.setAppConfig(params); + this.spToken = params.spToken; + // Defaults + this.fgUser = null; + } + + init({ fgStatusParams, skipUserDetails = false, ow }) { + const statsParams = { action: this.action, ...fgStatusParams }; + if (!skipUserDetails) { + this.fgUser = new FgUser({ at: this.spToken }); + statsParams.userDetails = this.fgUser.getUserDetails(); + } + this.fgStatus = new FgStatus(statsParams); + this.ow = ow || openwhisk(); + } + + getActionParams() { + return { + action: this.action, + appConfig, + fgStatus: this.fgStatus, + fgUser: this.fgUser + }; + } + + /** + * Validates parameters for storing statues + * @param {*} statParams - These are parameters those are must for action to start tracking. + * @returns Response validation status like { ok: , details: } + */ + async validateStatusParams(statParams = []) { + const resp = { ok: false, message: 'Status Params Validation' }; + logger.debug(resp.message); + const conf = appConfig.getPayload(); + const valFailed = statParams.find((p) => !conf[p]) !== undefined; + if (valFailed) { + resp.message = 'Could not determine the project path. Try reloading the page and trigger the action again.'; + return resp; + } + resp.ok = true; + return resp; + } + + /** + * Parameter validators for action + * @param {*} Options with + * reqParams - These are parameter which are required for the action to function. + * @returns Response validation status like { ok: , details: } + */ + async validateParams(reqParams = []) { + const resp = { ok: false, message: 'Params Validation.' }; + logger.debug(resp.message); + let stepMsg; + const conf = appConfig.getPayload(); + const valFailed = reqParams.find((p) => !conf[p]) !== undefined; + if (valFailed) { + stepMsg = `Required data is not available to proceed with FG ${this.action}.`; + resp.message = stepMsg; + await this.fgStatus?.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.FAILED, + statusMessage: stepMsg + }); + return resp; + } + resp.ok = true; + return resp; + } + + /** + * User validations for action + */ + async validateUser() { + const resp = { ok: false, message: 'User Validation' }; + logger.debug(resp.message); + let stepMsg; + const storeValue = await this.fgStatus.getStatusFromStateLib(); + if (this.fgUser && !await this.fgUser.isUser()) { + stepMsg = 'Unauthorized Access! Please contact Floodgate Administrators.'; + storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; + storeValue.action.message = stepMsg; + resp.details = storeValue; + return resp; + } + resp.ok = true; + return resp; + } + + /** + * Check if the action is in progress. + * @param {*} options with checkActivation flag if the flag is set then activation + * is check is skipped + * @returns object with ok true + */ + async actionInProgressCheck({ checkActivation = false }) { + const resp = { ok: false, message: 'Action In Progress' }; + logger.debug(resp.message); + let stepMsg; + const storeValue = await this.fgStatus.getStatusFromStateLib(); + const svStatus = storeValue?.action?.status; + const actId = storeValue?.action?.activationId; + const fgInProg = FgStatus.isInProgress(svStatus); + + if (!appConfig.getSkipInProgressCheck() && fgInProg) { + if (!checkActivation || await actInProgress(this.ow, actId, FgStatus.isInProgress(svStatus))) { + stepMsg = `A ${this.action} project with activationid: ${storeValue?.action?.activationId} is already in progress. + Not triggering this action. And the previous action can be retrieved by refreshing the console page`; + storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; + storeValue.action.message = stepMsg; + resp.message = stepMsg; + resp.details = storeValue; + return resp; + } + } + resp.ok = true; + return resp; + } + + /** + * Validation for action for params/user/action + * @param {*} opts options for validation + * Returns null if no error else the payload is returned + */ + async validateAction({ + statParams, + actParams, + checkUser = false, + checkStatus = false, + checkActivation = false + }) { + const OKVAL = { ok: true }; + let vStat = statParams ? await this.validateStatusParams(statParams) : OKVAL; + if (!vStat.ok) { + return { + code: BAD_REQUEST_SC, + payload: vStat.message, + }; + } + + vStat = actParams ? await this.validateParams(actParams) : OKVAL; + if (!vStat.ok) { + return { + code: BAD_REQUEST_SC, + payload: vStat.message, + }; + } + vStat = checkUser ? await this.validateUser() : OKVAL; + if (!vStat.ok) { + return { + code: AUTH_FAILED_SC, + payload: vStat.details, + }; + } + + vStat = checkStatus ? await this.actionInProgressCheck({ checkActivation }) : OKVAL; + if (!vStat.ok) { + return { + code: GEN_ERROR_SC, + payload: vStat.details, + }; + } + + return { code: ALL_OK_SC }; + } + + logStart() { + logger.info(`${this.action} action for ${this.fgStatus?.getStoreKey()} triggered by ${JSON.stringify(this.fgUser?.getUserDetails() || 'FG')}`); + } +} + +module.exports = FgAction; diff --git a/actions/copy/copy.js b/actions/copy/copy.js index 45c62aa..f8ee7dc 100644 --- a/actions/copy/copy.js +++ b/actions/copy/copy.js @@ -18,93 +18,70 @@ // eslint-disable-next-line import/no-extraneous-dependencies const openwhisk = require('openwhisk'); const { - getAioLogger, actInProgress, COPY_ACTION + getAioLogger, COPY_ACTION } = require('../utils'); -const appConfig = require('../appConfig'); -const sharepointAuth = require('../sharepointAuth'); const FgStatus = require('../fgStatus'); -const FgUser = require('../fgUser'); +const FgAction = require('../FgAction'); // This returns the activation ID of the action that it called async function main(args) { const logger = getAioLogger(); - let payload; - const { - spToken, adminPageUri, projectExcelPath, rootFolder - } = args; - appConfig.setAppConfig(args); - const userDetails = sharepointAuth.getUserDetails(spToken); - const fgStatus = new FgStatus({ action: COPY_ACTION, userDetails }); - logger.info(`Copy action for ${fgStatus.getStoreKey()} triggered by ${JSON.stringify(userDetails)}`); + let respPayload; + const valParams = { + statParams: ['rootFolder', 'projectExcelPath'], + actParams: ['adminPageUri'], + checkUser: true, + checkStatus: true, + checkActivation: true + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(COPY_ACTION, args); + fgAction.init({ ow }); + const { fgStatus } = fgAction.getActionParams(); + try { - if (!rootFolder || !projectExcelPath) { - payload = 'Could not determine the project path. Try reloading the page and trigger the action again.'; - logger.error(payload); - } else if (!adminPageUri) { - payload = 'Required data is not available to proceed with FG Copy action.'; - logger.error(payload); - payload = await fgStatus.updateStatusToStateLib({ + // Validations + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; + } + + fgAction.logStart(); + + respPayload = await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.STARTED, + statusMessage: 'Triggering copy action' + }); + return ow.actions.invoke({ + name: 'milo-fg/copy-worker', + blocking: false, // this is the flag that instructs to execute the worker asynchronous + result: false, + params: args + }).then(async (result) => { + logger.info(result); + // attaching activation id to the status + respPayload = await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + activationId: result.activationId + }); + return { + code: 200, + payload: respPayload + }; + }).catch(async (err) => { + respPayload = await fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: payload + statusMessage: `Failed to invoke actions ${err.message}` }); - } else { - const ow = openwhisk(); - const storeValue = await fgStatus.getStatusFromStateLib(); - const actId = storeValue?.action?.activationId; - const svStatus = storeValue?.action?.status; - const fgUser = new FgUser({ at: args.spToken }); - if (!await fgUser.isUser()) { - payload = 'Unauthorized Access! Please contact Floodgate Administrators.'; - storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; - storeValue.action.message = payload; - payload = storeValue; - } else if (!appConfig.getSkipInProgressCheck() && - await actInProgress(ow, actId, FgStatus.isInProgress(svStatus))) { - payload = `A copy action project with activationid: ${storeValue?.action?.activationId} is already in progress. - Not triggering this action. And the previous action can be retrieved by refreshing the console page`; - storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; - storeValue.action.message = payload; - payload = storeValue; - } else { - payload = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.STARTED, - statusMessage: 'Triggering copy action' - }); - return ow.actions.invoke({ - name: 'milo-fg/copy-worker', - blocking: false, // this is the flag that instructs to execute the worker asynchronous - result: false, - params: args - }).then(async (result) => { - logger.info(result); - // attaching activation id to the status - payload = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - activationId: result.activationId - }); - return { - code: 200, - payload - }; - }).catch(async (err) => { - payload = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: `Failed to invoke actions ${err.message}` - }); - logger.error('Failed to invoke actions', err); - return { - code: 500, - payload - }; - }); - } + logger.error('Failed to invoke actions', err); return { code: 500, - payload + payload: respPayload }; - } + }); } catch (err) { - payload = fgStatus.updateStatusToStateLib({ + respPayload = fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.FAILED, statusMessage: `Failed to invoke actions ${err.message}` }); @@ -113,7 +90,7 @@ async function main(args) { return { code: 500, - payload, + payload: respPayload, }; } diff --git a/actions/copy/worker.js b/actions/copy/worker.js index ddab799..8154f7f 100644 --- a/actions/copy/worker.js +++ b/actions/copy/worker.js @@ -15,6 +15,7 @@ * from Adobe. ************************************************************************* */ +const openwhisk = require('openwhisk'); const { getProjectDetails, updateProjectWithDocs } = require('../project'); const { updateExcelTable, getFile, saveFile, copyFile, bulkCreateFolders @@ -23,9 +24,9 @@ const { getAioLogger, handleExtension, delay, PREVIEW, logMemUsage, COPY_ACTION } = require('../utils'); const helixUtils = require('../helixUtils'); -const appConfig = require('../appConfig'); const urlInfo = require('../urlInfo'); const FgStatus = require('../fgStatus'); +const FgAction = require('../FgAction'); const sharepointAuth = require('../sharepointAuth'); const BATCH_REQUEST_COPY = 20; @@ -33,64 +34,63 @@ const DELAY_TIME_COPY = 3000; const ENABLE_HLX_PREVIEW = false; async function main(params) { - const logger = getAioLogger(); logMemUsage(); - let payload; - const { - adminPageUri, projectExcelPath, rootFolder - } = params; - appConfig.setAppConfig(params); - const fgStatus = new FgStatus({ action: COPY_ACTION }); + const logger = getAioLogger(); + let respPayload; + const valParams = { + statParams: ['rootFolder', 'projectExcelPath'], + actParams: ['adminPageUri'], + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(COPY_ACTION, params); + fgAction.init({ ow, skipUserDetails: true }); + const { fgStatus, appConfig } = fgAction.getActionParams(); + const { adminPageUri, projectExcelPath } = appConfig.getPayload(); try { - if (!rootFolder || !projectExcelPath) { - payload = 'Could not determine the project path. Try reloading the page and trigger the action again.'; - logger.error(payload); - } else if (!adminPageUri) { - payload = 'Required data is not available to proceed with FG Copy action.'; - fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: payload - }); - logger.error(payload); - } else { - urlInfo.setUrlInfo(adminPageUri); - payload = 'Getting all files to be floodgated from the project excel file'; - logger.info(payload); - fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: payload - }); - - const projectDetail = await getProjectDetails(projectExcelPath); - - payload = 'Injecting sharepoint data'; - logger.info(payload); - fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: payload - }); - await updateProjectWithDocs(projectDetail); - - payload = 'Start floodgating content'; - logger.info(payload); - fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: payload - }); - - payload = await floodgateContent(projectExcelPath, projectDetail, fgStatus); + // Validations + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; } + + urlInfo.setUrlInfo(adminPageUri); + respPayload = 'Getting all files to be floodgated from the project excel file'; + logger.info(respPayload); + fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); + + const projectDetail = await getProjectDetails(projectExcelPath); + + respPayload = 'Injecting sharepoint data'; + logger.info(respPayload); + fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); + await updateProjectWithDocs(projectDetail); + + respPayload = 'Start floodgating content'; + logger.info(respPayload); + fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); + + respPayload = await floodgateContent(projectExcelPath, projectDetail, fgStatus); } catch (err) { fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR, statusMessage: err.message }); logger.error(err); - payload = err; + respPayload = err; } logMemUsage(); return { - body: payload, + body: respPayload, }; } diff --git a/actions/delete/delete.js b/actions/delete/delete.js index fb03517..bc29f45 100644 --- a/actions/delete/delete.js +++ b/actions/delete/delete.js @@ -18,92 +18,68 @@ // eslint-disable-next-line import/no-extraneous-dependencies const openwhisk = require('openwhisk'); const { - getAioLogger, actInProgress, DELETE_ACTION + getAioLogger, DELETE_ACTION } = require('../utils'); -const appConfig = require('../appConfig'); -const sharepointAuth = require('../sharepointAuth'); const FgStatus = require('../fgStatus'); -const FgUser = require('../fgUser'); +const FgAction = require('../FgAction'); // This returns the activation ID of the action that it called async function main(args) { const logger = getAioLogger(); - let payload; - const { - spToken, adminPageUri, projectExcelPath, rootFolder - } = args; - appConfig.setAppConfig(args); - const projectPath = `${rootFolder}${projectExcelPath}`; - const userDetails = sharepointAuth.getUserDetails(spToken); - const fgStatus = new FgStatus({ action: DELETE_ACTION, userDetails }); - logger.info(`Delete action for ${projectPath} triggered by ${JSON.stringify(userDetails)}`); + let respPayload; + const valParams = { + statParams: ['fgRootFolder', 'projectExcelPath'], + actParams: ['adminPageUri'], + checkUser: true, + checkStatus: true, + checkActivation: true + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(DELETE_ACTION, args); + fgAction.init({ ow }); + const { fgStatus } = fgAction.getActionParams(); try { - if (!rootFolder || !projectExcelPath) { - payload = 'Could not determine the project path. Try reloading the page and trigger the action again.'; - logger.error(payload); - } else if (!adminPageUri) { - payload = 'Required data is not available to proceed with Delete action.'; - logger.error(payload); - payload = await fgStatus.updateStatusToStateLib({ + // Validations + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; + } + fgAction.logStart(); + + respPayload = await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.STARTED, + statusMessage: 'Triggering delete action' + }); + return ow.actions.invoke({ + name: 'milo-fg/delete-worker', + blocking: false, // this is the flag that instructs to execute the worker asynchronous + result: false, + params: args + }).then(async (result) => { + logger.info(result); + // attaching activation id to the status + respPayload = await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + activationId: result.activationId + }); + return { + code: 200, + payload: respPayload + }; + }).catch(async (err) => { + respPayload = await fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: payload + statusMessage: `Failed to invoke actions ${err.message}` }); - } else { - const ow = openwhisk(); - const storeValue = await fgStatus.getStatusFromStateLib(); - const actId = storeValue?.action?.activationId; - const svStatus = storeValue?.action?.status; - const fgUser = new FgUser({ at: args.spToken }); - if (!await fgUser.isUser()) { - payload = 'Could not determine the user.'; - logger.error(payload); - } else if (!appConfig.getSkipInProgressCheck() && - await actInProgress(ow, actId, FgStatus.isInProgress(svStatus))) { - payload = `A delete action with activationid: ${storeValue?.action?.activationId} is already in progress. - Not triggering this action. And the previous action can be retrieved by refreshing the console page`; - storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; - storeValue.action.message = payload; - payload = storeValue; - } else { - payload = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.STARTED, - statusMessage: 'Triggering delete action' - }); - return ow.actions.invoke({ - name: 'milo-fg/delete-worker', - blocking: false, // this is the flag that instructs to execute the worker asynchronous - result: false, - params: args - }).then(async (result) => { - logger.info(result); - // attaching activation id to the status - payload = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - activationId: result.activationId - }); - return { - code: 200, - payload - }; - }).catch(async (err) => { - payload = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: `Failed to invoke actions ${err.message}` - }); - logger.error('Failed to invoke actions', err); - return { - code: 500, - payload - }; - }); - } + logger.error('Failed to invoke actions', err); return { code: 500, - payload + payload: respPayload }; - } + }); } catch (err) { - payload = fgStatus.updateStatusToStateLib({ + respPayload = fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.FAILED, statusMessage: `Failed to invoke actions ${err.message}` }); @@ -112,7 +88,7 @@ async function main(args) { return { code: 500, - payload, + payload: respPayload, }; } diff --git a/actions/delete/worker.js b/actions/delete/worker.js index a68741c..19a535a 100644 --- a/actions/delete/worker.js +++ b/actions/delete/worker.js @@ -14,69 +14,67 @@ * is strictly forbidden unless prior written permission is obtained * from Adobe. ************************************************************************* */ - +const openwhisk = require('openwhisk'); const { deleteFloodgateDir, updateExcelTable } = require('../sharepoint'); const { getAioLogger, logMemUsage, DELETE_ACTION } = require('../utils'); -const appConfig = require('../appConfig'); const urlInfo = require('../urlInfo'); const FgStatus = require('../fgStatus'); +const FgAction = require('../FgAction'); async function main(params) { - const logger = getAioLogger(); logMemUsage(); - let payload; - const { - adminPageUri, projectExcelPath, rootFolder, - } = params; - appConfig.setAppConfig(params); - const { fgRootFolder } = appConfig.getPayload(); - const fgStatus = new FgStatus({ action: DELETE_ACTION }); - try { - if (!rootFolder || !projectExcelPath) { - payload = 'Could not determine the project path. Try reloading the page and trigger the action again.'; - logger.error(payload); - } else if (!adminPageUri) { - payload = 'Required data is not available to proceed with Delete action.'; - fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: payload - }); - logger.error(payload); - } else { - urlInfo.setUrlInfo(adminPageUri); - payload = 'Started deleting content'; - logger.info(payload); - fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: payload - }); - const deleteStatus = await deleteFloodgateDir(); - payload = deleteStatus === false ? - 'Error occurred when deleting content. Check project excel sheet for additional information.' : - 'Delete action was completed'; - await fgStatus.updateStatusToStateLib({ - status: deleteStatus === false ? FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR : FgStatus.PROJECT_STATUS.COMPLETED, - statusMessage: payload - }); - const { startTime: startDelete, endTime: endDelete } = fgStatus.getStartEndTime(); - const excelValues = [['DELETE', startDelete, endDelete, payload]]; - await updateExcelTable(projectExcelPath, 'DELETE_STATUS', excelValues); - logger.info('Project excel file updated with delete status.'); + const logger = getAioLogger(); + let respPayload; + const valParams = { + statParams: ['fgRootFolder', 'projectExcelPath'], + actParams: ['adminPageUri'], + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(DELETE_ACTION, params); + fgAction.init({ ow, skipUserDetails: true }); + const { fgStatus, appConfig } = fgAction.getActionParams(); + const { adminPageUri, projectExcelPath } = appConfig.getPayload(); + try { + // Validations + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; } + urlInfo.setUrlInfo(adminPageUri); + respPayload = 'Started deleting content'; + logger.info(respPayload); + fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); + + const deleteStatus = await deleteFloodgateDir(); + respPayload = deleteStatus === false ? + 'Error occurred when deleting content. Check project excel sheet for additional information.' : + 'Delete action was completed'; + await fgStatus.updateStatusToStateLib({ + status: deleteStatus === false ? FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR : FgStatus.PROJECT_STATUS.COMPLETED, + statusMessage: respPayload + }); + const { startTime: startDelete, endTime: endDelete } = fgStatus.getStartEndTime(); + const excelValues = [['DELETE', startDelete, endDelete, respPayload]]; + await updateExcelTable(projectExcelPath, 'DELETE_STATUS', excelValues); + logger.info('Project excel file updated with delete status.'); } catch (err) { fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR, statusMessage: err.message }); logger.error(err); - payload = err; + respPayload = err; } logMemUsage(); return { - body: payload, + body: respPayload, }; } diff --git a/actions/fgUser.js b/actions/fgUser.js index e76d9d3..14b5cd1 100644 --- a/actions/fgUser.js +++ b/actions/fgUser.js @@ -27,7 +27,12 @@ class FgUser { constructor({ at }) { this.at = at; - this.userOid = sharepointAuth.getUserDetails(at)?.oid; + this.userDetails = sharepointAuth.getUserDetails(at); + this.userOid = this.userDetails?.oid; + } + + getUserDetails() { + return this.userDetails; } async isInGroups(grpIds) { diff --git a/actions/promote/createBatch.js b/actions/promote/createBatch.js index 6150c67..e63d1f0 100644 --- a/actions/promote/createBatch.js +++ b/actions/promote/createBatch.js @@ -14,7 +14,7 @@ * is strictly forbidden unless prior written permission is obtained * from Adobe. ************************************************************************* */ - +const openwhisk = require('openwhisk'); const { getConfig } = require('../config'); const { getAuthorizedRequestOption, fetchWithRetry @@ -22,8 +22,8 @@ const { const { getAioLogger, logMemUsage, getInstanceKey, PROMOTE_ACTION } = require('../utils'); -const appConfig = require('../appConfig'); const urlInfo = require('../urlInfo'); +const FgAction = require('../FgAction'); const FgStatus = require('../fgStatus'); const BatchManager = require('../batchManager'); @@ -48,64 +48,68 @@ const MAX_CHILDREN = 5000; */ async function main(params) { logMemUsage(); - let stepMsg; - appConfig.setAppConfig(params); + let respPayload; + const valParams = { + statParams: ['fgRootFolder'], + actParams: ['adminPageUri', 'projectExcelPath'], + checkUser: false, + checkStatus: false, + checkActivation: false + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(PROMOTE_ACTION, params); + fgAction.init({ ow, skipUserDetails: true }); + const { fgStatus, appConfig } = fgAction.getActionParams(); const { payload, siteFgRootPath } = appConfig.getConfig(); - const fgStatus = new FgStatus({ action: PROMOTE_ACTION }); const batchManager = new BatchManager({ key: PROMOTE_ACTION, instanceKey: getInstanceKey({ fgRootFolder: siteFgRootPath }) }); await batchManager.init(); // For current cleanup files before starting await batchManager.cleanupFiles(); try { - if (!payload.fgRootFolder) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - logger.error(stepMsg); - } else if (!payload.adminPageUri || !payload.projectExcelPath) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: stepMsg - }); - } else { - urlInfo.setUrlInfo(payload.adminPageUri); - stepMsg = 'Getting all files to be promoted.'; - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: stepMsg - }); - logger.info(stepMsg); - stepMsg = 'Creating batches.'; - logger.info(stepMsg); - stepMsg = await createBatch(batchManager); - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: stepMsg, - batchesInfo: batchManager.getBatchesInfo() - }); - logger.info(stepMsg); - - // Finalize and Trigger N Track the batches - await batchManager.finalizeInstance(appConfig.getPassthruParams()); - logger.info('Instance finalized and started'); + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; } + + urlInfo.setUrlInfo(payload.adminPageUri); + respPayload = 'Getting all files to be promoted.'; + await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); + logger.info(respPayload); + respPayload = 'Creating batches.'; + logger.info(respPayload); + respPayload = await createBatch(batchManager, appConfig); + await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload, + batchesInfo: batchManager.getBatchesInfo() + }); + logger.info(respPayload); + + // Finalize and Trigger N Track the batches + await batchManager.finalizeInstance(appConfig.getPassthruParams()); + logger.info('Instance finalized and started'); } catch (err) { await fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR, statusMessage: err.message, }); logger.error(err); - stepMsg = err; + respPayload = err; } return { - body: stepMsg, + body: respPayload, }; } /** * Find all files in the pink tree to promote. Add to batches */ -async function createBatch(batchManager) { +async function createBatch(batchManager, appConfig) { const { sp } = await getConfig(); const options = await getAuthorizedRequestOption({ method: 'GET' }); const promoteIgnoreList = appConfig.getPromoteIgnorePaths(); diff --git a/actions/promote/promote.js b/actions/promote/promote.js index e0e17e7..c7ee732 100644 --- a/actions/promote/promote.js +++ b/actions/promote/promote.js @@ -20,91 +20,70 @@ const openwhisk = require('openwhisk'); const { getAioLogger, PROMOTE_ACTION } = require('../utils'); -const appConfig = require('../appConfig'); -const sharepointAuth = require('../sharepointAuth'); const FgStatus = require('../fgStatus'); -const FgUser = require('../fgUser'); +const FgAction = require('../FgAction'); // This returns the activation ID of the action that it called async function main(args) { const logger = getAioLogger(); - appConfig.setAppConfig(args); - let stepMsg; - const payload = appConfig.getPayload(); - const userDetails = sharepointAuth.getUserDetails(payload.spToken); - const fgStatus = new FgStatus({ action: PROMOTE_ACTION, userDetails }); - logger.info(`Promote action for ${payload.fgRootFolder} triggered by ${JSON.stringify(userDetails)}`); + let respPayload; + const valParams = { + statParams: ['fgRootFolder'], + actParams: ['adminPageUri', 'projectExcelPath'], + checkUser: true, + checkStatus: true, + checkActivation: false + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(PROMOTE_ACTION, args); + fgAction.init({ ow }); + const { fgStatus, appConfig } = fgAction.getActionParams(); + try { - if (!payload.fgRootFolder) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - logger.error(stepMsg); - } else if (!payload.adminPageUri || !payload.projectExcelPath) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - logger.error(stepMsg); - stepMsg = await fgStatus.updateStatusToStateLib({ + // Validations + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; + } + fgAction.logStart(); + + fgStatus.clearState(); + respPayload = await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.STARTED, + statusMessage: 'Triggering promote action', + batches: {} + }); + logger.info(`FGStatus store ${await fgStatus.getStatusFromStateLib()}`); + + return ow.actions.invoke({ + name: 'milo-fg/promote-create-batch', + blocking: false, // this is the flag that instructs to execute the worker asynchronous + result: false, + params: appConfig.getPassthruParams() + }).then(async (result) => { + // attaching activation id to the status + respPayload = await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + activationId: result.activationId + }); + return { + code: 200, + payload: respPayload + }; + }).catch(async (err) => { + respPayload = await fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: stepMsg + statusMessage: `Failed to invoke actions ${err.message}` }); - } else { - const ow = openwhisk(); - const storeValue = await fgStatus.getStatusFromStateLib(); - const svStatus = storeValue?.action?.status; - const fgInProg = FgStatus.isInProgress(svStatus); - const fgUser = new FgUser({ at: args.spToken }); - if (!await fgUser.isUser()) { - stepMsg = 'Unauthorized Access! Please contact Floodgate Administrators.'; - storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; - storeValue.action.message = stepMsg; - stepMsg = storeValue; - } else if (!appConfig.getSkipInProgressCheck() && fgInProg) { - stepMsg = `A promote action project with activationid: ${storeValue?.action?.activationId} is already in progress. - Not triggering this action. And the previous action can be retrieved by refreshing the console page`; - storeValue.action.status = FgStatus.PROJECT_STATUS.FAILED; - storeValue.action.message = stepMsg; - stepMsg = storeValue; - } else { - fgStatus.clearState(); - stepMsg = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.STARTED, - statusMessage: 'Triggering promote action', - batches: {} - }); - logger.info(`FGStatus store ${await fgStatus.getStatusFromStateLib()}`); - - return ow.actions.invoke({ - name: 'milo-fg/promote-create-batch', - blocking: false, // this is the flag that instructs to execute the worker asynchronous - result: false, - params: appConfig.getPassthruParams() - }).then(async (result) => { - // attaching activation id to the status - stepMsg = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - activationId: result.activationId - }); - return { - code: 200, - payload: stepMsg - }; - }).catch(async (err) => { - stepMsg = await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: `Failed to invoke actions ${err.message}` - }); - return { - code: 500, - payload: stepMsg - }; - }); - } return { code: 500, - payload: stepMsg + payload: respPayload }; - } + }); } catch (err) { logger.error(err); - stepMsg = await fgStatus.updateStatusToStateLib({ + respPayload = await fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.FAILED, statusMessage: `Failed to invoke actions ${err.message}` }); @@ -112,7 +91,7 @@ async function main(args) { return { code: 500, - payload: stepMsg, + payload: respPayload, }; } diff --git a/actions/promote/triggerNTrack.js b/actions/promote/triggerNTrack.js index a632f07..cf99d57 100644 --- a/actions/promote/triggerNTrack.js +++ b/actions/promote/triggerNTrack.js @@ -25,13 +25,23 @@ const appConfig = require('../appConfig'); const urlInfo = require('../urlInfo'); const FgStatus = require('../fgStatus'); const BatchManager = require('../batchManager'); +const FgAction = require('../FgAction'); const logger = getAioLogger(); async function main(params) { - let stepMsg; - appConfig.setAppConfig(params); + let respPayload; + + const valParams = { + statParams: ['fgRootFolder'], + actParams: ['adminPageUri', 'projectExcelPath'], + checkUser: false, + checkStatus: false, + checkActivation: false + }; + const ow = openwhisk(); + appConfig.setAppConfig(params); const batchManager = new BatchManager({ key: PROMOTE_ACTION }); await batchManager.init(); // Read instance_info.json @@ -42,64 +52,55 @@ async function main(params) { const { batchesInfo } = instanceContent.dtls; - const ow = openwhisk(); - // Reset with inputs - appConfig.setAppConfig({ - ...params, ...instanceContent.dtls - }); - const payload = appConfig.getPayload(); + // Initialize action + const fgAction = new FgAction(PROMOTE_ACTION, { ...params, ...instanceContent.dtls }); + fgAction.init({ ow, skipUserDetails: true }); + const { fgStatus } = fgAction.getActionParams(); + const { payload } = appConfig.getConfig(); - const fgStatus = new FgStatus({ action: PROMOTE_ACTION }); try { - if (!payload.fgRootFolder) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - logger.error(stepMsg); - } else if (!payload.adminPageUri || !payload.projectExcelPath) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: stepMsg - }); - logger.error(stepMsg); - } else { - urlInfo.setUrlInfo(payload.adminPageUri); - stepMsg = 'Getting status of all reference activation.'; - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: stepMsg - }); + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; + } - // Check to see all batches are complete - const batchCheckResp = await checkBatchesInProg(payload.fgRootFolder, batchesInfo, ow); - const { anyInProg, allDone } = batchCheckResp; - await batchManager.writeToInstanceFile(instanceContent); + urlInfo.setUrlInfo(payload.adminPageUri); + respPayload = 'Getting status of all reference activation.'; + await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); - // Collect status and mark as complete - if (allDone) { - await completePromote(payload.projectExcelPath, batchesInfo, batchManager, fgStatus); - await batchManager.writeToInstanceFile(instanceContent); - } else if (!anyInProg) { - // Trigger next activation - const nextItem = batchesInfo.find((b) => !b.activationId); - const batchNumber = nextItem?.batchNumber; - if (batchNumber) { - const newActDtls = await triggerPromoteWorkerAction(ow, - { - ...appConfig.getPassthruParams(), - batchNumber - }, - fgStatus); - nextItem.activationId = newActDtls?.activationId; - } - await batchManager.writeToInstanceFile(instanceContent); - } + // Check to see all batches are complete + const batchCheckResp = await checkBatchesInProg(payload.fgRootFolder, batchesInfo, ow); + const { anyInProg, allDone } = batchCheckResp; + await batchManager.writeToInstanceFile(instanceContent); - stepMsg = 'Promote trigger and track completed.'; - logger.info(stepMsg); + // Collect status and mark as complete + if (allDone) { + await completePromote(payload.projectExcelPath, batchesInfo, batchManager, fgStatus); + await batchManager.writeToInstanceFile(instanceContent); + } else if (!anyInProg) { + // Trigger next activation + const nextItem = batchesInfo.find((b) => !b.activationId); + const batchNumber = nextItem?.batchNumber; + if (batchNumber) { + const newActDtls = await triggerPromoteWorkerAction(ow, + { + ...appConfig.getPassthruParams(), + batchNumber + }, + fgStatus); + nextItem.activationId = newActDtls?.activationId; + } + await batchManager.writeToInstanceFile(instanceContent); } + + respPayload = 'Promote trigger and track completed.'; + logger.info(respPayload); } catch (err) { logger.error(err); - stepMsg = err; + respPayload = err; // In case of error log status with end time try { await fgStatus.updateStatusToStateLib({ @@ -111,7 +112,7 @@ async function main(params) { } } return { - body: stepMsg, + body: respPayload, }; } diff --git a/actions/promote/worker.js b/actions/promote/worker.js index 101abaf..237885c 100644 --- a/actions/promote/worker.js +++ b/actions/promote/worker.js @@ -15,7 +15,7 @@ * is strictly forbidden unless prior written permission is obtained * from Adobe. ************************************************************************* */ - +const openwhisk = require('openwhisk'); const { getConfig } = require('../config'); const { getAuthorizedRequestOption, saveFile, getFileUsingDownloadUrl, fetchWithRetry @@ -24,8 +24,8 @@ const { getAioLogger, handleExtension, delay, logMemUsage, getInstanceKey, PREVIEW, PUBLISH, PROMOTE_ACTION, PROMOTE_BATCH } = require('../utils'); const helixUtils = require('../helixUtils'); -const appConfig = require('../appConfig'); const urlInfo = require('../urlInfo'); +const FgAction = require('../FgAction'); const FgStatus = require('../fgStatus'); const BatchManager = require('../batchManager'); @@ -35,50 +35,53 @@ const ENABLE_HLX_PREVIEW = false; async function main(params) { const logger = getAioLogger(); logMemUsage(); - let stepMsg; const { batchNumber } = params; - appConfig.setAppConfig(params); - // Tracker uses the below hence change here might need change in tracker as well. + const valParams = { + statParams: ['fgRootFolder', 'projectExcelPath'], + actParams: ['adminPageUri'], + checkUser: false, + checkStatus: false, + checkActivation: false + }; + const ow = openwhisk(); + // Initialize action + const fgAction = new FgAction(`${PROMOTE_BATCH}_${batchNumber}`, params); + fgAction.init({ ow, skipUserDetails: true, fgStatusParams: { keySuffix: `Batch_${batchNumber}` } }); + const { fgStatus, appConfig } = fgAction.getActionParams(); const { payload, siteFgRootPath } = appConfig.getConfig(); - const fgStatus = new FgStatus({ action: `${PROMOTE_BATCH}_${batchNumber}`, keySuffix: `Batch_${batchNumber}` }); + + let respPayload; const batchManager = new BatchManager({ key: PROMOTE_ACTION, instanceKey: getInstanceKey({ fgRootFolder: siteFgRootPath }) }); await batchManager.init({ batchNumber }); try { - if (!payload.fgRootFolder) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - logger.error(stepMsg); - } else if (!payload.adminPageUri || !payload.projectExcelPath) { - stepMsg = 'Required data is not available to proceed with FG Promote action.'; - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.FAILED, - statusMessage: stepMsg - }); - logger.error(stepMsg); - } else { - urlInfo.setUrlInfo(payload.adminPageUri); - stepMsg = 'Getting all files to be promoted.'; - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.IN_PROGRESS, - statusMessage: stepMsg - }); - logger.info(stepMsg); - stepMsg = await promoteFloodgatedFiles(payload.doPublish, batchManager); - await fgStatus.updateStatusToStateLib({ - status: FgStatus.PROJECT_STATUS.COMPLETED, - statusMessage: stepMsg - }); + const vStat = await fgAction.validateAction(valParams); + if (vStat && vStat.code !== 200) { + return vStat; } + + urlInfo.setUrlInfo(payload.adminPageUri); + respPayload = 'Getting all files to be promoted.'; + await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.IN_PROGRESS, + statusMessage: respPayload + }); + logger.info(respPayload); + respPayload = await promoteFloodgatedFiles(payload.doPublish, batchManager, appConfig); + await fgStatus.updateStatusToStateLib({ + status: FgStatus.PROJECT_STATUS.COMPLETED, + statusMessage: respPayload + }); } catch (err) { await fgStatus.updateStatusToStateLib({ status: FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR, statusMessage: err.message, }); logger.error(err); - stepMsg = err; + respPayload = err; } return { - body: stepMsg, + body: respPayload, }; } @@ -113,7 +116,7 @@ async function promoteCopy(srcPath, destinationFolder) { return copySuccess; } -async function promoteFloodgatedFiles(doPublish, batchManager) { +async function promoteFloodgatedFiles(doPublish, batchManager, appConfig) { const logger = getAioLogger(); async function promoteFile(batchItem) { From 628a8b23d28d8c04cfb17650c326f3832433ace5 Mon Sep 17 00:00:00 2001 From: Raghu A Date: Thu, 14 Sep 2023 01:51:21 +0530 Subject: [PATCH 2/3] Minor update --- actions/FgAction.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/actions/FgAction.js b/actions/FgAction.js index f29610b..eedc7c8 100644 --- a/actions/FgAction.js +++ b/actions/FgAction.js @@ -31,8 +31,6 @@ const ALL_OK_SC = 200; * The common parameter validation, user check, */ class FgAction { - validations = {}; - constructor(action, params) { this.action = action || FG_PROOCESS_ACTION; appConfig.setAppConfig(params); From ffa29dd751e6b7fd772a12e3220ba6caade41df7 Mon Sep 17 00:00:00 2001 From: Raghu A Date: Thu, 14 Sep 2023 10:39:42 +0530 Subject: [PATCH 3/3] Updated for review comment --- actions/FgAction.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/FgAction.js b/actions/FgAction.js index eedc7c8..1b67ecb 100644 --- a/actions/FgAction.js +++ b/actions/FgAction.js @@ -24,7 +24,7 @@ const FG_PROOCESS_ACTION = 'fgProcessAction'; const logger = getAioLogger(); const BAD_REQUEST_SC = 400; const AUTH_FAILED_SC = 401; -const GEN_ERROR_SC = 401; +const GEN_ERROR_SC = 500; const ALL_OK_SC = 200; /** @@ -178,6 +178,7 @@ class FgAction { payload: vStat.message, }; } + vStat = checkUser ? await this.validateUser() : OKVAL; if (!vStat.ok) { return {