Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(integ-runner): integ-runner --watch #26087

Merged
merged 2 commits into from
Jun 23, 2023
Merged
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
55 changes: 53 additions & 2 deletions packages/@aws-cdk/cdk-cli-wrapper/lib/cdk-wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DefaultCdkOptions, DeployOptions, DestroyOptions, SynthOptions, ListOptions, StackActivityProgress } from './commands';
import { exec } from './utils';
import { ChildProcess } from 'child_process';
import { DefaultCdkOptions, DeployOptions, DestroyOptions, SynthOptions, ListOptions, StackActivityProgress, HotswapMode } from './commands';
import { exec, watch } from './utils';

/**
* AWS CDK CLI operations
Expand Down Expand Up @@ -30,6 +31,11 @@ export interface ICdk {
* cdk synth fast
*/
synthFast(options: SynthFastOptions): void;

/**
* cdk watch
*/
watch(options: DeployOptions): ChildProcess;
}

/**
Expand Down Expand Up @@ -176,6 +182,7 @@ export class CdkCliWrapper implements ICdk {
...options.changeSetName ? ['--change-set-name', options.changeSetName] : [],
...options.toolkitStackName ? ['--toolkit-stack-name', options.toolkitStackName] : [],
...options.progress ? ['--progress', options.progress] : ['--progress', StackActivityProgress.EVENTS],
...options.deploymentMethod ? ['--method', options.deploymentMethod] : [],
...this.createDefaultArguments(options),
];

Expand All @@ -186,6 +193,50 @@ export class CdkCliWrapper implements ICdk {
});
}

public watch(options: DeployOptions): ChildProcess {
let hotswap: string;
switch (options.hotswap) {
case HotswapMode.FALL_BACK:
hotswap = '--hotswap-fallback';
break;
case HotswapMode.HOTSWAP_ONLY:
hotswap = '--hotswap';
break;
default:
hotswap = '--hotswap-fallback';
break;
}
const deployCommandArgs: string[] = [
'--watch',
...renderBooleanArg('ci', options.ci),
...renderBooleanArg('execute', options.execute),
...renderBooleanArg('exclusively', options.exclusively),
...renderBooleanArg('force', options.force),
...renderBooleanArg('previous-parameters', options.usePreviousParameters),
...renderBooleanArg('rollback', options.rollback),
...renderBooleanArg('staging', options.staging),
...renderBooleanArg('logs', options.traceLogs),
hotswap,
...options.reuseAssets ? renderArrayArg('--reuse-assets', options.reuseAssets) : [],
...options.notificationArns ? renderArrayArg('--notification-arns', options.notificationArns) : [],
...options.parameters ? renderMapArrayArg('--parameters', options.parameters) : [],
...options.outputsFile ? ['--outputs-file', options.outputsFile] : [],
...options.requireApproval ? ['--require-approval', options.requireApproval] : [],
...options.changeSetName ? ['--change-set-name', options.changeSetName] : [],
...options.toolkitStackName ? ['--toolkit-stack-name', options.toolkitStackName] : [],
...options.progress ? ['--progress', options.progress] : ['--progress', StackActivityProgress.EVENTS],
...options.deploymentMethod ? ['--method', options.deploymentMethod] : [],
...this.createDefaultArguments(options),
];

return watch([this.cdk, 'deploy', ...deployCommandArgs], {
cwd: this.directory,
verbose: this.showOutput,
env: this.env,
});

}

/**
* cdk destroy
*/
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/cdk-cli-wrapper/lib/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,53 @@ export interface DeployOptions extends DefaultCdkOptions {
* @default StackActivityProgress.EVENTS
*/
readonly progress?: StackActivityProgress;

/**
* Whether this 'deploy' command should actually delegate to the 'watch' command.
*
* @default false
*/
readonly watch?: boolean;

/**
* Whether to perform a 'hotswap' deployment.
* A 'hotswap' deployment will attempt to short-circuit CloudFormation
* and update the affected resources like Lambda functions directly.
*
* @default - `HotswapMode.FALL_BACK` for regular deployments, `HotswapMode.HOTSWAP_ONLY` for 'watch' deployments
*/
readonly hotswap?: HotswapMode;

/**
* Whether to show CloudWatch logs for hotswapped resources
* locally in the users terminal
*
* @default - false
*/
readonly traceLogs?: boolean;

/**
* Deployment method
*/
readonly deploymentMethod?: DeploymentMethod;
}
export type DeploymentMethod = 'direct' | 'change-set';

export enum HotswapMode {
/**
* Will fall back to CloudFormation when a non-hotswappable change is detected
*/
FALL_BACK = 'fall-back',

/**
* Will not fall back to CloudFormation when a non-hotswappable change is detected
*/
HOTSWAP_ONLY = 'hotswap-only',

/**
* Will not attempt to hotswap anything and instead go straight to CloudFormation
*/
FULL_DEPLOYMENT = 'full-deployment',
}

/**
Expand Down
21 changes: 20 additions & 1 deletion packages/@aws-cdk/cdk-cli-wrapper/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Helper functions for CDK Exec
import { spawnSync } from 'child_process';
import { spawn, spawnSync } from 'child_process';

/**
* Our own execute function which doesn't use shells and strings.
Expand Down Expand Up @@ -37,3 +37,22 @@ export function exec(commandLine: string[], options: { cwd?: string, json?: bool
throw new Error('Command output is not JSON');
}
}

/**
* For use with `cdk deploy --watch`
*/
export function watch(commandLine: string[], options: { cwd?: string, verbose?: boolean, env?: any } = { }) {
const proc = spawn(commandLine[0], commandLine.slice(1), {
stdio: ['ignore', 'pipe', options.verbose ? 'inherit' : 'pipe'], // inherit STDERR in verbose mode
env: {
...process.env,
...options.env,
},
cwd: options.cwd,
});
proc.on('error', (err: Error) => {
throw err;
});

return proc;
}
34 changes: 33 additions & 1 deletion packages/@aws-cdk/cdk-cli-wrapper/test/cdk-wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as child_process from 'child_process';
import { CdkCliWrapper } from '../lib/cdk-wrapper';
import { RequireApproval, StackActivityProgress } from '../lib/commands';
let spawnSyncMock: jest.SpyInstance;
let spawnMock: jest.SpyInstance;

beforeEach(() => {
spawnSyncMock = jest.spyOn(child_process, 'spawnSync').mockReturnValue({
Expand All @@ -12,6 +13,11 @@ beforeEach(() => {
output: ['stdout', 'stderr'],
signal: null,
});
spawnMock = jest.spyOn(child_process, 'spawn').mockImplementation(jest.fn(() => {
return {
on: jest.fn(() => {}),
} as unknown as child_process.ChildProcess;
}));
});

afterEach(() => {
Expand Down Expand Up @@ -317,7 +323,33 @@ test('default synth', () => {
);
});

test('synth arguments', () => {
test('watch arguments', () => {
// WHEN
const cdk = new CdkCliWrapper({
directory: '/project',
env: {
KEY: 'value',
},
});
cdk.watch({
app: 'node bin/my-app.js',
stacks: ['test-stack1'],
});

// THEN
expect(spawnMock).toHaveBeenCalledWith(
expect.stringMatching(/cdk/),
['deploy', '--watch', '--hotswap-fallback', '--progress', 'events', '--app', 'node bin/my-app.js', 'test-stack1'],
expect.objectContaining({
env: expect.objectContaining({
KEY: 'value',
}),
cwd: '/project',
}),
);
});

test('destroy arguments', () => {
// WHEN
const cdk = new CdkCliWrapper({
directory: '/project',
Expand Down
54 changes: 54 additions & 0 deletions packages/@aws-cdk/integ-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ to be a self contained CDK app. The runner will execute the following for each f
- `--test-regex`
Detect integration test files matching this JavaScript regex pattern. If used multiple times, all files matching any one of the patterns are detected.

- `--watch`
Run a single integration test in watch mode. In watch mode the integ-runner
will not save any snapshots.

Use together with `--app` to fully customize how tests are run, or use with a single `--language` preset to change which files are detected for this language.
- `--language`
The language presets to use. You can discover and run tests written in multiple languages by passing this flag multiple times (`--language typescript --language python`). Defaults to all supported languages. Currently supported language presets are:
Expand Down Expand Up @@ -221,6 +225,56 @@ integ-runner --update-on-failed --disable-update-workflow integ.new-test.js

This is because for a new test we do not need to test the update workflow (there is nothing to update).

### watch

It can be useful to run an integration test in watch mode when you are iterating
on a specific test.

```console
integ-runner integ.new-test.js --watch
```

In watch mode the integ test will run similar to `cdk deploy --watch` with the
addition of also displaying the assertion results. By default the output will
only show the assertion results.

- To show the console output from watch run with `-v`
- To also stream the CloudWatch logs (i.e. `cdk deploy --watch --logs`) run with `-vv`

When running in watch mode most of the integ-runner functionality will be turned
off.

- Snapshots will not be created
- Update workflow will not be run
- Stacks will not be cleaned up (you must manually clean up the stacks)
- Only a single test can be run

Once you are done iterating using watch and want to create the snapshot you can
run the integ test like normal to create the snapshot and clean up the test.

#### cdk.context.json

cdk watch depends on a `cdk.context.json` file existing with a `watch` key. The
integ-runner will create a default `cdk.context.json` file if one does not
exist.

```json
{
"watch": {}
}
```

You can further edit this file after it is created and add additional `watch`
fields. For example:

```json
{
"watch": {
"include": ["**/*.js"]
}
}
```

### integ.json schema

See [@aws-cdk/cloud-assembly-schema/lib/integ-tests/schema.ts](../cloud-assembly-schema/lib/integ-tests/schema.ts)
Expand Down
Loading