Skip to content

Commit

Permalink
Merge pull request #420 from vscheuber/main
Browse files Browse the repository at this point in the history
Resolves #418 - Provide a framework for commands to indicate which deployment types they support
  • Loading branch information
vscheuber authored Jul 15, 2024
2 parents 5d95cf5 + 7977eb6 commit 7cdb4aa
Show file tree
Hide file tree
Showing 117 changed files with 1,780 additions and 881 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- \#418: Developer: Frodo provides a framework for commands to indicate which deployment types they support.
- \#419: Developer: Updated command template with usage samples

### Changed

- Update to frodo-lib 2.0.0-95

## [2.0.0-68] - 2024-07-12

## [2.0.0-67] - 2024-07-11
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
]
},
"devDependencies": {
"@rockcarver/frodo-lib": "2.0.0-94",
"@rockcarver/frodo-lib": "2.0.0-95",
"@types/colors": "^1.2.1",
"@types/fs-extra": "^11.0.1",
"@types/jest": "^29.2.3",
Expand Down
9 changes: 5 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const { initTokenCache } = frodo.cache;
program.addCommand(idp());
program.addCommand(info());
program.addCommand(journey());
program.addCommand(log());
await program.addCommand(log());
program.addCommand(mapping());
program.addCommand(oauth());
program.addCommand(realm());
Expand All @@ -82,10 +82,11 @@ const { initTokenCache } = frodo.cache;

program.showHelpAfterError();
program.enablePositionalOptions();
program.parse();

// most or all frodo commands use async action handlers
await program.parseAsync();
} catch (e) {
process.exitCode = 1;
printMessage(`ERROR: exception running frodo - ${e}`, 'error');
printMessage(e.stack, 'error');
printError(e);
}
})();
48 changes: 31 additions & 17 deletions src/cli/FrodoCommand.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { frodo, state } from '@rockcarver/frodo-lib';
import { frodo, FrodoError, state } from '@rockcarver/frodo-lib';
import { Argument, Command, Help, Option } from 'commander';
import fs from 'fs';

import * as globalConfig from '../storage/StaticStorage';
import {
cleanupProgressIndicators,
createProgressIndicator,
curlirizeMessage,
debugMessage,
printError,
printMessage,
stopProgressIndicator,
updateProgressIndicator,
verboseMessage,
} from '../utils/Console.js';

const { DEFAULT_REALM_KEY, DEPLOYMENT_TYPES } = frodo.utils.constants;

const hostArgument = new Argument(
'[host]',
'Access Management base URL, e.g.: https://cdk.iam.example.com/am. To use a connection profile, just specify a unique substring.'
Expand All @@ -24,7 +26,7 @@ const realmArgument = new Argument(
"Realm. Specify realm as '/' for the root realm or 'realm' or '/parent/child' otherwise."
).default(
// must check for FRODO_REALM env variable here because otherwise cli will overwrite realm with default value
process.env.FRODO_REALM || globalConfig.DEFAULT_REALM_KEY,
process.env.FRODO_REALM || DEFAULT_REALM_KEY,
'"alpha" for Identity Cloud tenants, "/" otherwise.'
);

Expand Down Expand Up @@ -54,7 +56,7 @@ forgeops: A ForgeOps CDK or CDM deployment. \n\
The detected or provided deployment type controls certain behavior like obtaining an Identity \
Management admin token or not and whether to export/import referenced email templates or how \
to walk through the tenant admin login flow of Identity Cloud and handle MFA'
).choices(globalConfig.DEPLOYMENT_TYPES);
).choices(DEPLOYMENT_TYPES);

const directoryOption = new Option(
'-D, --directory <directory>',
Expand Down Expand Up @@ -152,25 +154,18 @@ export class FrodoStubCommand extends Command {
/**
* Creates a new FrodoCommand instance
* @param name Name of the command
* @param omits Array of default argument names and default option names that should not be added to this command
*/
constructor(name: string) {
super(name);

if (!process.listenerCount('unhandledRejection')) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.on('unhandledRejection', (error: any) => {
printMessage(
`${error.config?.method ? error.config.method + ' ' : ''}${
error.config?.url ? error.config.url : ''
}`,
'error'
);
printMessage(error.response?.data, 'error');
printMessage(error.stack, 'error');
printMessage(
`Please report this unhandled error here: https://github.com/rockcarver/frodo-cli/issues`,
'error'
printError(
new FrodoError(
`Please report this unhandled error here: https://github.com/rockcarver/frodo-cli/issues`,
error
)
);
process.exitCode = 1;
});
Expand Down Expand Up @@ -218,14 +213,23 @@ class FrodoStubHelp extends Help {
* Command with default options
*/
export class FrodoCommand extends FrodoStubCommand {
types: string[];

/**
* Creates a new FrodoCommand instance
* @param name Name of the command
* @param omits Array of default argument names and default option names that should not be added to this command
* @param types Array of deployment types this command supports
*/
constructor(name: string, omits: string[] = []) {
constructor(
name: string,
omits: string[] = [],
types: string[] = DEPLOYMENT_TYPES
) {
super(name);

this.types = types;

// register default arguments
for (const arg of defaultArgs) {
if (!omits.includes(arg.name())) this.addArgument(arg);
Expand Down Expand Up @@ -314,5 +318,15 @@ export class FrodoCommand extends FrodoStubCommand {
);
}
}

// fail fast if an incompatible deployment type option (-m or --type) was provided
if (
state.getDeploymentType() &&
!(state.getDeploymentType() in this.types)
) {
throw new FrodoError(
`Command does not support deployment type '${state.getDeploymentType()}'`
);
}
}
}
30 changes: 28 additions & 2 deletions src/cli/_template/something-delete.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { frodo } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import * as s from '../../help/SampleData';
import { getTokens } from '../../ops/AuthenticateOps';
import { FrodoCommand } from '../FrodoCommand';

const { DEPLOYMENT_TYPES } = frodo.utils.constants;

const deploymentTypes = DEPLOYMENT_TYPES;

export default function setup() {
const program = new FrodoCommand('frodo something delete');
const program = new FrodoCommand(
'frodo something delete',
[],
deploymentTypes
);

program
.description('Delete something.')
Expand All @@ -23,6 +33,22 @@ export default function setup() {
'No deep delete. This leaves orphaned configuration artifacts behind.'
)
)
.addHelpText(
'after',
`Usage Examples:\n` +
` Example command one with params and explanation what it does:\n` +
` $ frodo something ${s.amBaseUrl} ${s.username} '${s.password}'\n`[
'brightCyan'
] +
` Example command two with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.amBaseUrl}\n`[
'brightCyan'
] +
` Example command three with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.connId}\n`[
'brightCyan'
]
)
.action(
// implement command logic inside action handler
async (host, realm, user, password, options, command) => {
Expand All @@ -34,7 +60,7 @@ export default function setup() {
options,
command
);
if (await getTokens()) {
if (await getTokens(false, true, deploymentTypes)) {
// code goes here
} else {
process.exitCode = 1;
Expand Down
30 changes: 28 additions & 2 deletions src/cli/_template/something-describe.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,42 @@
import { frodo } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import * as s from '../../help/SampleData';
import { getTokens } from '../../ops/AuthenticateOps';
import { FrodoCommand } from '../FrodoCommand';

const { DEPLOYMENT_TYPES } = frodo.utils.constants;

const deploymentTypes = DEPLOYMENT_TYPES;

export default function setup() {
const program = new FrodoCommand('frodo something describe');
const program = new FrodoCommand(
'frodo something describe',
[],
deploymentTypes
);

program
.description('Describe something.')
.addOption(
new Option('-i, --something-id <something-id>', '[Something] id.')
)
.addHelpText(
'after',
`Usage Examples:\n` +
` Example command one with params and explanation what it does:\n` +
` $ frodo something ${s.amBaseUrl} ${s.username} '${s.password}'\n`[
'brightCyan'
] +
` Example command two with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.amBaseUrl}\n`[
'brightCyan'
] +
` Example command three with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.connId}\n`[
'brightCyan'
]
)
.action(
// implement command logic inside action handler
async (host, realm, user, password, options, command) => {
Expand All @@ -22,7 +48,7 @@ export default function setup() {
options,
command
);
if (await getTokens()) {
if (await getTokens(false, true, deploymentTypes)) {
// code goes here
} else {
process.exitCode = 1;
Expand Down
30 changes: 28 additions & 2 deletions src/cli/_template/something-else-delete.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { frodo } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import * as s from '../../help/SampleData';
import { getTokens } from '../../ops/AuthenticateOps';
import { FrodoCommand } from '../FrodoCommand';

const { DEPLOYMENT_TYPES } = frodo.utils.constants;

const deploymentTypes = DEPLOYMENT_TYPES;

export default function setup() {
const program = new FrodoCommand('frodo something else delete');
const program = new FrodoCommand(
'frodo something else delete',
[],
deploymentTypes
);

program
.description('Delete something else.')
Expand All @@ -21,6 +31,22 @@ export default function setup() {
'No deep delete. This leaves orphaned configuration artifacts behind.'
)
)
.addHelpText(
'after',
`Usage Examples:\n` +
` Example command one with params and explanation what it does:\n` +
` $ frodo something ${s.amBaseUrl} ${s.username} '${s.password}'\n`[
'brightCyan'
] +
` Example command two with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.amBaseUrl}\n`[
'brightCyan'
] +
` Example command three with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.connId}\n`[
'brightCyan'
]
)
.action(
// implement command logic inside action handler
async (host, realm, user, password, options, command) => {
Expand All @@ -32,7 +58,7 @@ export default function setup() {
options,
command
);
if (await getTokens()) {
if (await getTokens(false, true, deploymentTypes)) {
// code goes here
} else {
process.exitCode = 1;
Expand Down
30 changes: 28 additions & 2 deletions src/cli/_template/something-else-describe.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
import { frodo } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import * as s from '../../help/SampleData';
import { getTokens } from '../../ops/AuthenticateOps';
import { FrodoCommand } from '../FrodoCommand';

const { DEPLOYMENT_TYPES } = frodo.utils.constants;

const deploymentTypes = DEPLOYMENT_TYPES;

export default function setup() {
const program = new FrodoCommand('frodo something else describe');
const program = new FrodoCommand(
'frodo something else describe',
[],
deploymentTypes
);

program
.description('Describe something else.')
.addOption(new Option('-i, --else-id <else-id>', '[Else] id.'))
.addHelpText(
'after',
`Usage Examples:\n` +
` Example command one with params and explanation what it does:\n` +
` $ frodo something ${s.amBaseUrl} ${s.username} '${s.password}'\n`[
'brightCyan'
] +
` Example command two with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.amBaseUrl}\n`[
'brightCyan'
] +
` Example command three with params and explanation what it does:\n` +
` $ frodo something --sa-id ${s.saId} --sa-jwk-file ${s.saJwkFile} ${s.connId}\n`[
'brightCyan'
]
)
.action(
// implement command logic inside action handler
async (host, realm, user, password, options, command) => {
Expand All @@ -20,7 +46,7 @@ export default function setup() {
options,
command
);
if (await getTokens()) {
if (await getTokens(false, true, deploymentTypes)) {
// code goes here
} else {
process.exitCode = 1;
Expand Down
Loading

0 comments on commit 7cdb4aa

Please sign in to comment.