Skip to content

Commit

Permalink
feat(cli): --force flag and glob-style key matches for context --reset (
Browse files Browse the repository at this point in the history
#19890)

New pull request for glob style key matches in context --reset (#19840)

Adds the `--force` flag to ignore missing key errors.

This is in response to #19888 

----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
gillisandrew authored Jul 14, 2022
1 parent f15cb27 commit 39a7c1f
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ async function parseCommandLineArguments() {
)
.command('context', 'Manage cached context values', (yargs: Argv) => yargs
.option('reset', { alias: 'e', desc: 'The context key (or its index) to reset', type: 'string', requiresArg: true })
.option('force', { alias: 'f', desc: 'Ignore missing key error', type: 'boolean', default: false })
.option('clear', { desc: 'Clear all context', type: 'boolean' }))
.command(['docs', 'doc'], 'Opens the reference documentation in a browser', (yargs: Argv) => yargs
.option('browser', {
Expand Down
87 changes: 75 additions & 12 deletions packages/aws-cdk/lib/commands/context.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import * as chalk from 'chalk';
import * as minimatch from 'minimatch';
import * as version from '../../lib/version';
import { CommandOptions } from '../command-api';
import { print } from '../logging';
import { Context, PROJECT_CONFIG } from '../settings';
import { print, error, warning } from '../logging';
import { Context, PROJECT_CONFIG, PROJECT_CONTEXT, USER_DEFAULTS } from '../settings';
import { renderTable } from '../util';

export async function realHandler(options: CommandOptions): Promise<number> {
const { configuration, args } = options;

if (args.clear) {
configuration.context.clear();
await configuration.saveContext();
print('All context values cleared.');
} else if (args.reset) {
invalidateContext(configuration.context, args.reset as string);
invalidateContext(configuration.context, args.reset as string, args.force as boolean);
await configuration.saveContext();
} else {
// List -- support '--json' flag
Expand Down Expand Up @@ -48,30 +48,93 @@ function listContext(context: Context) {
const jsonWithoutNewlines = JSON.stringify(context.all[key], undefined, 2).replace(/\s+/g, ' ');
data.push([i, key, jsonWithoutNewlines]);
}

print(`Context found in ${chalk.blue(PROJECT_CONFIG)}:\n`);

print('Context found in %s:', chalk.blue(PROJECT_CONFIG));
print('');
print(renderTable(data, process.stdout.columns));

// eslint-disable-next-line max-len
print(`Run ${chalk.blue('cdk context --reset KEY_OR_NUMBER')} to remove a context key. It will be refreshed on the next CDK synthesis run.`);
}

function invalidateContext(context: Context, key: string) {
function invalidateContext(context: Context, key: string, force: boolean) {
const i = parseInt(key, 10);
if (`${i}` === key) {
// was a number and we fully parsed it.
key = keyByNumber(context, i);
}

// Unset!
if (context.has(key)) {
context.unset(key);
print(`Context value ${chalk.blue(key)} reset. It will be refreshed on next synthesis`);
} else {
print(`No context value with key ${chalk.blue(key)}`);
// check if the value was actually unset.
if (!context.has(key)) {
print('Context value %s reset. It will be refreshed on next synthesis', chalk.blue(key));
return;
}

// Value must be in readonly bag
error('Only context values specified in %s can be reset through the CLI', chalk.blue(PROJECT_CONTEXT));
if (!force) {
throw new Error(`Cannot reset readonly context value with key: ${key}`);
}
}

// check if value is expression matching keys
const matches = keysByExpression(context, key);

if (matches.length > 0) {

matches.forEach((match) => {
context.unset(match);
});

const { unset, readonly } = getUnsetAndReadonly(context, matches);

// output the reset values
printUnset(unset);

// warn about values not reset
printReadonly(readonly);

// throw when none of the matches were reset
if (!force && unset.length === 0) {
throw new Error('None of the matched context values could be reset');
}
return;
}
if (!force) {
throw new Error(`No context value matching key: ${key}`);
}
}
function printUnset(unset: string[]) {
if (unset.length === 0) return;
print('The following matched context values reset. They will be refreshed on next synthesis');
unset.forEach((match) => {
print(' %s', match);
});
}
function printReadonly(readonly: string[]) {
if (readonly.length === 0) return;
warning('The following matched context values could not be reset through the CLI');
readonly.forEach((match) => {
print(' %s', match);
});
print('');
print('This usually means they are configured in %s or %s', chalk.blue(PROJECT_CONFIG), chalk.blue(USER_DEFAULTS));
}
function keysByExpression(context: Context, expression: string) {
return context.keys.filter(minimatch.filter(expression));
}

function getUnsetAndReadonly(context: Context, matches: string[]) {
return matches.reduce<{ unset: string[], readonly: string[] }>((acc, match) => {
if (context.has(match)) {
acc.readonly.push(match);
} else {
acc.unset.push(match);
}
return acc;
}, { unset: [], readonly: [] });
}

function keyByNumber(context: Context, n: number) {
for (const [i, key] of contextKeys(context)) {
Expand Down
Loading

0 comments on commit 39a7c1f

Please sign in to comment.