Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jest-test-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import chalk from 'chalk';
chalk.level = 0;
delete process.env.FORCE_COLOR;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"testMatch": [
"**/?(*.)(test).js"
],
"globalSetup": "./global-jest-setup.js"
"globalSetup": "./global-jest-setup.js",
"setupFilesAfterEnv": ["./jest-test-setup.js"]
},
"contributors": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { frodo, state } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import { configManagerImportIgaWorkflows } from '../../../configManagerOps/FrConfigIgaWorkflowsOps';
import { getTokens } from '../../../ops/AuthenticateOps';
import { printMessage, verboseMessage } from '../../../utils/Console';
import { FrodoCommand } from '../../FrodoCommand';

const { CLOUD_DEPLOYMENT_TYPE_KEY } = frodo.utils.constants;

const deploymentTypes = [CLOUD_DEPLOYMENT_TYPE_KEY];

export default function setup() {
const program = new FrodoCommand(
'frodo config-manager push iga-workflows',
[],
deploymentTypes
);
program
.description('Import iga-workflows.')
.addOption(
new Option(
'-n, --name <name>',
'Workflow name. Only import the workflow with this name.'
)
)
.addOption(
new Option('-d, --draft', 'Push as draft version instead of publishing.')
)
.action(async (host, realm, user, password, options, command) => {
command.handleDefaultArgsAndOpts(
host,
realm,
user,
password,
options,
command
);

const getTokensIsSuccessful = await getTokens(
false,
true,
deploymentTypes
);
if (!getTokensIsSuccessful) process.exit(1);
if (!state.getIsIGA()) {
printMessage(
'Command not supported for non-IGA cloud tenants',
'error'
);
process.exitCode = 1;
return;
}
verboseMessage('Importing IGA workflows.');
const outcome = await configManagerImportIgaWorkflows(
options.name,
options.draft
);
if (!outcome) process.exitCode = 1;
});
return program;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CustomNodes from './config-manager-push-custom-nodes';
import EmailProvider from './config-manager-push-email-provider';
import EmailTemplates from './config-manager-push-email-templates';
import Endpoints from './config-manager-push-endpoints';
import IgaWorkflows from './config-manager-push-iga-workflows';
import InternalRoles from './config-manager-push-internal-roles';
import Kba from './config-manager-push-kba';
import Locales from './config-manager-push-locales';
Expand Down Expand Up @@ -38,6 +39,7 @@ export default function setup() {
program.addCommand(Endpoints().name('endpoints'));
program.addCommand(Kba().name('kba'));
program.addCommand(InternalRoles().name('internal-roles'));
program.addCommand(IgaWorkflows().name('iga-workflows'));
program.addCommand(EmailTemplates().name('email-templates'));
program.addCommand(Schedules().name('schedules'));
program.addCommand(OrgPrivileges().name('org-privileges'));
Expand Down
119 changes: 110 additions & 9 deletions src/configManagerOps/FrConfigIgaWorkflowsOps.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { frodo } from '@rockcarver/frodo-lib';
import fs from 'fs';
import path from 'path';

import { extractFrConfigDataToFile } from '../utils/Config';
import { printError } from '../utils/Console';
import { printError, printMessage, verboseMessage } from '../utils/Console';
import { safeFileName } from '../utils/FrConfig';

const { saveJsonToFile, getFilePath } = frodo.utils;
const { readWorkflows } = frodo.cloud.iga.workflow;
const { readWorkflows, updateWorkflow } = frodo.cloud.iga.workflow;

/**
* Export IGA workflows in fr-config-manager format.
Expand All @@ -24,7 +26,7 @@ export async function configManagerExportIgaWorkflows(
if (name && workflows.length === 0) {
throw new Error(`Workflow ${name} not found`);
}
workflows.forEach(processIgaWorkflow);
workflows.forEach(processIgaWorkflowForExport);
return true;
} catch (error) {
printError(error, 'Error exporting iga-workflows to files');
Expand All @@ -35,10 +37,11 @@ export async function configManagerExportIgaWorkflows(
* Export a single IGA workflow to files in fr-config-manager format.
* @param {object} workflow the workflow to export
*/
async function processIgaWorkflow(workflow) {
async function processIgaWorkflowForExport(workflow) {
try {
const workflowName = safeFileName(workflow.name);
const stepsPath = `${workflowName}/steps`;
const workflowPath = `iga/workflows/${workflowName}`;
const stepsPath = `${workflowPath}/steps`;
workflow.steps.forEach((step) => {
const uniqueId = safeFileName(`${step.displayName} - ${step.name}`);
const stepPath = `${stepsPath}/${uniqueId}`;
Expand All @@ -55,13 +58,111 @@ async function processIgaWorkflow(workflow) {
file: scriptFilename,
};
}
const stepFileName = `${stepPath}/${uniqueId}.json`;
saveJsonToFile(step, getFilePath(stepFileName, true), false, true);
saveJsonToFile(
step,
getFilePath(`${stepPath}/${uniqueId}.json`, true),
false,
true
);
});
delete workflow.steps;
const fileName = `${workflowName}/${workflowName}.json`;
saveJsonToFile(workflow, getFilePath(fileName, true), false, true);
saveJsonToFile(
workflow,
getFilePath(`${workflowPath}/${workflowName}.json`, true),
false,
true
);
} catch (err) {
printError(err);
}
}

/**
* Import IGA workflows in fr-config-manager format.
* @param {string} name optional workflow name to filter by
* @param {boolean} draft if true, will import workflow as draft
* @returns {Promise<boolean>} a promise that resolves to true if successful, false otherwise
*/
export async function configManagerImportIgaWorkflows(
name?: string,
draft: boolean = false
): Promise<boolean> {
try {
const workflowsPath = getFilePath('iga/workflows');
let workflowDirs = [];

if (name) {
const workflowDir = `${workflowsPath}/${name}`;
if (!fs.existsSync(workflowDir)) {
printMessage(`Requested workflow ${name} not found`, 'error');
return false;
}
workflowDirs = [workflowDir];
} else {
workflowDirs = fs
.readdirSync(workflowsPath)
.map((dirName) => `${workflowsPath}/${dirName}`);
}

const status = draft ? 'draft' : 'published';
for (const workflowDir of workflowDirs) {
const workflow = processWorkflowForImport(workflowDir);

if (!workflow.mutable) {
verboseMessage(`Skipping immutable workflow ${workflow.name}`);
continue;
}

workflow.status = status;
await updateWorkflow(workflow.id, workflow);
}
Comment thread
phalestrivir marked this conversation as resolved.
return true;
} catch (error) {
printError(error, 'Error importing iga-workflows');
}
return false;
}

/**
* Process a workflow to import in fr-config-manager format
* @param {string} workflowDir path to the workflow directory
* @returns {object} the assembled workflow object
*/
function processWorkflowForImport(workflowDir: string) {
try {
const workflowName = path.parse(workflowDir).base;
const workflowFile = path.join(workflowDir, `${workflowName}.json`);
const workflow = JSON.parse(fs.readFileSync(workflowFile, 'utf8'));

const stepsDir = path.join(workflowDir, 'steps');
if (!fs.existsSync(stepsDir)) return workflow;
const stepDirs = fs
.readdirSync(stepsDir, { withFileTypes: true })
.map((dirent) => path.join(stepsDir, dirent.name));
const steps = [];
for (const stepDir of stepDirs) {
const stepName = path.parse(stepDir).base;
const stepFile = path.join(stepDir, `${stepName}.json`);
const step = JSON.parse(fs.readFileSync(stepFile, 'utf8'));
const stepBody = step?.[step?.type];
if (
stepBody &&
typeof stepBody === 'object' &&
stepBody.script &&
typeof stepBody.script === 'object' &&
typeof stepBody.script.file === 'string'
) {
const filePath = path.join(stepDir, stepBody.script.file);
if (fs.existsSync(filePath)) {
stepBody.script = fs.readFileSync(filePath, 'utf8');
}
}
steps.push(step);
}
workflow.steps = steps;

return workflow;
} catch (error) {
printError(error, 'Error importing iga-workflows');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CLI help interface for 'config-manager push iga-workflows' should be expected english 1`] = `
"Usage: frodo config-manager push iga-workflows [options] [host] [realm] [username] [password]

[Experimental] Import iga-workflows.

Arguments:
host AM base URL, e.g.: https://cdk.iam.example.com/am. To use a
connection profile, just specify a unique substring or
alias.
realm Realm. Specify realm as '/' for the root realm or 'realm'
or '/parent/child' otherwise. (default: "alpha" for
Identity Cloud tenants, "/" otherwise.)
username Username to login with. Must be an admin user with
appropriate rights to manage authentication journeys/trees.
password Password.

Deployment: Cloud-only

Options:
-d, --draft Push as draft version instead of publishing.
-n, --name <name> Workflow name. Only import the workflow with this name.
-h, --help Help
-hh, --help-more Help with all options.
-hhh, --help-all Help with all options, environment variables, and usage
examples.
"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Commands:

(Cloud-only):
csp [Experimental] Import content security policy.
iga-workflows [Experimental] Import iga-workflows.
restart [Experimental] Restart the environment.
secret-mappings [Experimental] Import secret mappings.
"
Expand Down
10 changes: 10 additions & 0 deletions test/client_cli/en/config-manager-push-iga-workflows.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import cp from 'child_process';
import { promisify } from 'util';

const exec = promisify(cp.exec);
const CMD = 'frodo config-manager push iga-workflows --help';
const { stdout } = await exec(CMD);

test("CLI help interface for 'config-manager push iga-workflows' should be expected english", async () => {
expect(stdout).toMatchSnapshot();
});
Loading