Skip to content

Commit

Permalink
Merge pull request #442 from trivir/bugfix/fix-usage
Browse files Browse the repository at this point in the history
Fix and improve --usage flag for ESVs and Scripts
  • Loading branch information
vscheuber committed Sep 9, 2024
2 parents 07abe9e + 7277982 commit 37df33b
Show file tree
Hide file tree
Showing 113 changed files with 285,201 additions and 25,147 deletions.
36 changes: 32 additions & 4 deletions src/cli/esv/esv-secret-describe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Option } from 'commander';

import { getTokens } from '../../ops/AuthenticateOps';
import { describeSecret } from '../../ops/cloud/SecretsOps';
import { verboseMessage } from '../../utils/Console.js';
import { printMessage, verboseMessage } from '../../utils/Console.js';
import { FrodoCommand } from '../FrodoCommand';

const deploymentTypes = ['cloud'];
Expand All @@ -22,6 +22,19 @@ export default function setup() {
'Secret id.'
).makeOptionMandatory()
)
.addOption(
new Option(
'-f, --file [file]',
'Optional export file to use to determine usage. Overrides -D, --directory. Only used if -u or --usage is provided as well.'
)
)
.addOption(
new Option(
'-u, --usage',
'List all uses of the secret. If a file is provided with -f or --file, it will search for usage in the file. If a directory is provided with -D or --directory, it will search for usage in all .json files in the directory and sub-directories. If no file or directory is provided, it will perform a full export automatically to determine usage.'
).default(false, 'false')
)
.addOption(new Option('--json', 'Output in JSON format.'))
.action(
// implement command logic inside action handler
async (host, user, password, options, command) => {
Expand All @@ -32,11 +45,26 @@ export default function setup() {
options,
command
);
if (await getTokens(false, true, deploymentTypes)) {
if (
options.secretId &&
(await getTokens(false, true, deploymentTypes))
) {
verboseMessage(`Describing secret ${options.secretId}...`);
const outcome = await describeSecret(options.secretId);
const outcome = await describeSecret(
options.secretId,
options.file,
options.usage,
options.json
);
if (!outcome) process.exitCode = 1;
} else {
}
// unrecognized combination of options or no options
else {
printMessage(
'Unrecognized combination of options or no options...',
'error'
);
program.help();
process.exitCode = 1;
}
}
Expand Down
30 changes: 27 additions & 3 deletions src/cli/esv/esv-variable-describe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Option } from 'commander';

import { getTokens } from '../../ops/AuthenticateOps';
import { describeVariable } from '../../ops/cloud/VariablesOps';
import { verboseMessage } from '../../utils/Console.js';
import { printMessage, verboseMessage } from '../../utils/Console.js';
import { FrodoCommand } from '../FrodoCommand';

const deploymentTypes = ['cloud'];
Expand All @@ -22,6 +22,18 @@ export default function setup() {
'Variable id.'
).makeOptionMandatory()
)
.addOption(
new Option(
'-f, --file [file]',
'Optional export file to use to determine usage. Overrides -D, --directory. Only used if -u or --usage is provided as well.'
)
)
.addOption(
new Option(
'-u, --usage',
'List all uses of the variable. If a file is provided with -f or --file, it will search for usage in the file. If a directory is provided with -D or --directory, it will search for usage in all .json files in the directory and sub-directories. If no file or directory is provided, it will perform a full export automatically to determine usage.'
).default(false, 'false')
)
.addOption(new Option('--json', 'Output in JSON format.'))
.action(
// implement command logic inside action handler
Expand All @@ -33,14 +45,26 @@ export default function setup() {
options,
command
);
if (await getTokens(false, true, deploymentTypes)) {
if (
options.variableId &&
(await getTokens(false, true, deploymentTypes))
) {
verboseMessage(`Describing variable ${options.variableId}...`);
const outcome = await describeVariable(
options.variableId,
options.file,
options.usage,
options.json
);
if (!outcome) process.exitCode = 1;
} else {
}
// unrecognized combination of options or no options
else {
printMessage(
'Unrecognized combination of options or no options...',
'error'
);
program.help();
process.exitCode = 1;
}
}
Expand Down
44 changes: 41 additions & 3 deletions src/cli/script/script-describe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Option } from 'commander';

import { getTokens } from '../../ops/AuthenticateOps';
import { describeScript } from '../../ops/ScriptOps';
import { printMessage, verboseMessage } from '../../utils/Console';
import { FrodoCommand } from '../FrodoCommand';

export default function setup() {
Expand All @@ -14,6 +16,25 @@ export default function setup() {
'Uuid of the script. If specified, -a and -A are ignored.'
)
)
.addOption(
new Option(
'-n, --script-name <name>',
'Name of the script. If specified, -a and -A are ignored.'
)
)
.addOption(
new Option(
'-f, --file [file]',
'Optional export file to use to determine usage. Overrides -D, --directory. Only used if -u or --usage is provided as well.'
)
)
.addOption(
new Option(
'-u, --usage',
'List all uses of the script. If a file is provided with -f or --file, it will search for usage in the file. If a directory is provided with -D or --directory, it will search for usage in all .json files in the directory and sub-directories. If no file or directory is provided, it will perform a full export automatically to determine usage.'
).default(false, 'false')
)
.addOption(new Option('--json', 'Output in JSON format.'))
.action(
// implement command logic inside action handler
async (host, realm, user, password, options, command) => {
Expand All @@ -25,9 +46,26 @@ export default function setup() {
options,
command
);
if (await getTokens()) {
// code goes here
} else {
if ((options.scriptName || options.scriptId) && (await getTokens())) {
verboseMessage(
`Describing script ${options.scriptName ? options.scriptName : options.scriptId}...`
);
const outcome = await describeScript(
options.scriptId,
options.scriptName,
options.file,
options.usage,
options.json
);
if (!outcome) process.exitCode = 1;
}
// unrecognized combination of options or no options
else {
printMessage(
'Unrecognized combination of options or no options...',
'error'
);
program.help();
process.exitCode = 1;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/cli/script/script.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FrodoStubCommand } from '../FrodoCommand';
import DeleteCmd from './script-delete.js';
// import DescribeCmd from './script-describe.js';
import DescribeCmd from './script-describe.js';
import ExportCmd from './script-export.js';
import ImportCmd from './script-import.js';
import ListCmd from './script-list.js';
Expand All @@ -10,7 +10,7 @@ export default function setup() {

program.addCommand(ListCmd().name('list'));

// program.addCommand(DescribeCmd().name('describe'));
program.addCommand(DescribeCmd().name('describe'));

program.addCommand(ExportCmd().name('export'));

Expand Down
124 changes: 116 additions & 8 deletions src/ops/ScriptOps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { frodo, FrodoError, state } from '@rockcarver/frodo-lib';
import { type ScriptSkeleton } from '@rockcarver/frodo-lib/types/api/ScriptApi';
import { FullExportInterface } from '@rockcarver/frodo-lib/types/ops/ConfigOps';
import {
type ScriptExportInterface,
ScriptExportOptions,
Expand All @@ -8,8 +9,9 @@ import {
import chokidar from 'chokidar';
import fs from 'fs';

import { getFullExportConfig, isIdUsed } from '../utils/Config';
import { getFullExportConfig, getIdLocations } from '../utils/Config';
import {
createKeyValueTable,
createProgressIndicator,
createTable,
debugMessage,
Expand All @@ -36,6 +38,8 @@ const {
saveToFile,
} = frodo.utils;
const {
readScript,
readScriptByName,
readScripts,
exportScript,
exportScriptByName,
Expand All @@ -46,6 +50,8 @@ const {
deleteScripts,
} = frodo.script;

const langMap = { JAVASCRIPT: 'JavaScript', GROOVY: 'Groovy' };

/**
* Get a one-line description of the script object
* @param {ScriptSkeleton} scriptObj script object to describe
Expand Down Expand Up @@ -73,7 +79,6 @@ export function getTableHeaderMd(): string {
* @returns {string} markdown table row
*/
export function getTableRowMd(scriptObj: ScriptSkeleton): string {
const langMap = { JAVASCRIPT: 'JavaScript', GROOVY: 'Groovy' };
const description = `| ${scriptObj.name} | ${
langMap[scriptObj.language]
} | ${titleCase(scriptObj.context.split('_').join(' '))} | \`${
Expand Down Expand Up @@ -122,7 +127,7 @@ export async function listScripts(
debugMessage(`Cli.ScriptOps.listScripts: end`);
return true;
}
let fullExport = null;
let fullExport: FullExportInterface = null;
const headers = long
? ['Name', 'UUID', 'Language', 'Context', 'Description']
: ['Name'];
Expand All @@ -134,11 +139,12 @@ export async function listScripts(
return false;
}
//Delete scripts from full export so they aren't mistakenly used for determining usage
delete fullExport.script;
for (const realmExport of Object.values(fullExport.realm)) {
delete realmExport.script;
}
headers.push('Used');
}
const table = createTable(headers);
const langMap = { JAVASCRIPT: 'JS', GROOVY: 'Groovy' };
scripts.forEach((script) => {
const values = long
? [
Expand All @@ -150,10 +156,10 @@ export async function listScripts(
]
: [wordwrap(script.name, 25, ' ')];
if (usage) {
const isScriptUsed = isIdUsed(fullExport, script._id, false);
const locations = getIdLocations(fullExport, script._id, false);
values.push(
isScriptUsed.used
? `${'yes'['brightGreen']} (at ${isScriptUsed.location})`
locations.length > 0
? `${'yes'['brightGreen']} (${locations.length === 1 ? `at` : `${locations.length} uses, including:`} ${locations[0]})`
: 'no'['brightRed']
);
}
Expand All @@ -164,6 +170,108 @@ export async function listScripts(
return true;
}

/**
* Describe a script
* @param {string} scriptId script id
* @param {string} scriptName script name
* @param {string} file optional export file
* @param {boolean} usage true to describe usage, false otherwise. Default: false
* @param {boolean} json output description as json. Default: false
* @returns {Promise<boolean>} true if successful, false otherwise
*/
export async function describeScript(
scriptId: string,
scriptName: string,
file?: string,
usage = false,
json = false
): Promise<boolean> {
const spinnerId = createProgressIndicator(
'indeterminate',
0,
`Describing script '${scriptId ? scriptId : scriptName}'...`
);
try {
let script;
if (scriptId) {
script = (await readScript(scriptId)) as ScriptSkeleton & {
locations: string[];
};
} else {
script = (await readScriptByName(scriptName)) as ScriptSkeleton & {
locations: string[];
};
}
if (usage) {
try {
const fullExport = await getFullExportConfig(file);
//Delete scripts from full export so they aren't mistakenly used for determining usage
for (const realmExport of Object.values(fullExport.realm)) {
delete realmExport.script;
}
script.locations = getIdLocations(fullExport, script._id, false);
} catch (error) {
stopProgressIndicator(
spinnerId,
`Error determining usage for script '${scriptId ? scriptId : scriptName}'`,
'fail'
);
printError(error);
return false;
}
}
stopProgressIndicator(
spinnerId,
`Successfully retrieved script '${scriptId ? scriptId : scriptName}'`,
'success'
);
if (json) {
printMessage(script, 'data');
} else {
const table = createKeyValueTable();
table.push(['Id'['brightCyan'], script._id]);
table.push(['Name'['brightCyan'], script.name]);
table.push(['Language'['brightCyan'], langMap[script.language]]);
table.push([
'Context'['brightCyan'],
titleCase(script.context.split('_').join(' ')),
]);
table.push(['Description'['brightCyan'], script.description]);
table.push([
'Default'['brightCyan'],
script.default ? 'true'['brightGreen'] : 'false'['brightRed'],
]);
table.push(['Evaluator Version'['brightCyan'], script.evaluatorVersion]);
const scriptWrapLength = 80;
const wrapRegex = new RegExp(`.{1,${scriptWrapLength + 1}}`, 'g');
const scriptParts = script.script.match(wrapRegex);
table.push(['Script (Base 64)'['brightCyan'], scriptParts[0]]);
for (let i = 1; i < scriptParts.length; i++) {
table.push(['', scriptParts[i]]);
}
if (usage) {
table.push([
`Usage Locations (${script.locations.length} total)`['brightCyan'],
script.locations.length > 0 ? script.locations[0] : '',
]);
for (let i = 1; i < script.locations.length; i++) {
table.push(['', script.locations[i]]);
}
}
printMessage(table.toString(), 'data');
}
return true;
} catch (error) {
stopProgressIndicator(
spinnerId,
`Error describing script '${scriptId ? scriptId : scriptName}'`,
'fail'
);
printError(error);
}
return false;
}

/**
* Export script by id to file
* @param {string} scriptId script uuid
Expand Down
Loading

0 comments on commit 37df33b

Please sign in to comment.