Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
msambol committed Dec 22, 2023
1 parent e017f82 commit 4f0d20e
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 2 deletions.
134 changes: 134 additions & 0 deletions packages/@aws-cdk-testing/cli-integ/lib/with-cli-no-stacks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import * as os from 'os';
import * as path from 'path';
import { TestContext } from './integ-test';
import { RESOURCES_DIR } from './resources';
import { AwsContext, withAws } from './with-aws';
import { cloneDirectory, installNpmPackages, TestFixture, DEFAULT_TEST_TIMEOUT_S, CdkCliOptions } from './with-cdk-app';
import { withTimeout } from './with-timeout';

/**
* Higher order function to execute a block with a CliLib Integration CDK app fixture
*/
export function withCliLibIntegrationCdkApp<A extends TestContext & AwsContext>(block: (context: CliLibIntegrationTestFixture) => Promise<void>) {
return async (context: A) => {
const randy = context.randomString;
const stackNamePrefix = `cdktest-${randy}`;
const integTestDir = path.join(os.tmpdir(), `cdk-integ-${randy}`);

context.log(` Stack prefix: ${stackNamePrefix}\n`);
context.log(` Test directory: ${integTestDir}\n`);
context.log(` Region: ${context.aws.region}\n`);

await cloneDirectory(path.join(RESOURCES_DIR, 'cdk-apps', 'no-stack-app'), integTestDir, context.output);
const fixture = new CliLibIntegrationTestFixture(
integTestDir,
stackNamePrefix,
context.output,
context.aws,
context.randomString);

let success = true;
try {
const installationVersion = fixture.packages.requestedFrameworkVersion();

if (fixture.packages.majorVersion() === '1') {
throw new Error('This test suite is only compatible with AWS CDK v2');
}

const alphaInstallationVersion = fixture.packages.requestedAlphaVersion();
await installNpmPackages(fixture, {
'aws-cdk-lib': installationVersion,
'@aws-cdk/cli-lib-alpha': alphaInstallationVersion,
'@aws-cdk/aws-lambda-go-alpha': alphaInstallationVersion,
'@aws-cdk/aws-lambda-python-alpha': alphaInstallationVersion,
'constructs': '^10',
});

await block(fixture);
} catch (e: any) {
// We survive certain cases involving gopkg.in
if (errorCausedByGoPkg(e.message)) {
return;
}
success = false;
throw e;
} finally {
if (process.env.INTEG_NO_CLEAN) {
context.log(`Left test directory in '${integTestDir}' ($INTEG_NO_CLEAN)\n`);
} else {
await fixture.dispose(success);
}
}
};
}

/**
* Return whether or not the error is being caused by gopkg.in being down
*
* Our Go build depends on https://gopkg.in/, which has errors pretty often
* (every couple of days). It is run by a single volunteer.
*/
function errorCausedByGoPkg(error: string) {
// The error is different depending on what request fails. Messages recognized:
////////////////////////////////////////////////////////////////////
// go: github.com/aws/aws-lambda-go@v1.28.0 requires
// gopkg.in/yaml.v3@v3.0.0-20200615113413-eeeca48fe776: invalid version: git ls-remote -q origin in /go/pkg/mod/cache/vcs/0901dc1ef67fcce1c9b3ae51078740de4a0e2dc673e720584ac302973af82f36: exit status 128:
// remote: Cannot obtain refs from GitHub: cannot talk to GitHub: Get https://github.com/go-yaml/yaml.git/info/refs?service=git-upload-pack: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
// fatal: unable to access 'https://gopkg.in/yaml.v3/': The requested URL returned error: 502
////////////////////////////////////////////////////////////////////
// go: downloading github.com/aws/aws-lambda-go v1.28.0
// go: github.com/aws/aws-lambda-go@v1.28.0 requires
// gopkg.in/yaml.v3@v3.0.0-20200615113413-eeeca48fe776: unrecognized import path "gopkg.in/yaml.v3": reading https://gopkg.in/yaml.v3?go-get=1: 502 Bad Gateway
// server response: Cannot obtain refs from GitHub: cannot talk to GitHub: Get https://github.com/go-yaml/yaml.git/info/refs?service=git-upload-pack: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
////////////////////////////////////////////////////////////////////
// go: github.com/aws/aws-lambda-go@v1.28.0 requires
// gopkg.in/yaml.v3@v3.0.0-20200615113413-eeeca48fe776: invalid version: git fetch -f origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /go/pkg/mod/cache/vcs/0901dc1ef67fcce1c9b3ae51078740de4a0e2dc673e720584ac302973af82f36: exit status 128:
// error: RPC failed; HTTP 502 curl 22 The requested URL returned error: 502
// fatal: the remote end hung up unexpectedly
////////////////////////////////////////////////////////////////////

return (error.includes('gopkg\.in.*invalid version.*exit status 128')
|| error.match(/unrecognized import path[^\n]gopkg\.in/));
}

/**
* SAM Integration test fixture for CDK - SAM integration test cases
*/
export function withCliLibFixture(block: (context: CliLibIntegrationTestFixture) => Promise<void>) {
return withAws(withTimeout(DEFAULT_TEST_TIMEOUT_S, withCliLibIntegrationCdkApp(block)));
}

export class CliLibIntegrationTestFixture extends TestFixture {
/**
*
*/
public async cdk(args: string[], options: CdkCliOptions = {}) {
const action = args[0];
const stackName = args[1];

const cliOpts: Record<string, any> = {
stacks: stackName ? [stackName] : undefined,
};

if (action === 'deploy') {
cliOpts.requireApproval = options.neverRequireApproval ? 'never' : 'broadening';
}

return this.shell(['node', '--input-type=module', `<<__EOS__
import { AwsCdkCli } from '@aws-cdk/cli-lib-alpha';
const cli = AwsCdkCli.fromCdkAppDirectory();
await cli.${action}(${JSON.stringify(cliOpts)});
__EOS__`], {
...options,
modEnv: {
AWS_REGION: this.aws.region,
AWS_DEFAULT_REGION: this.aws.region,
STACK_NAME_PREFIX: this.stackNamePrefix,
PACKAGE_LAYOUT_VERSION: this.packages.majorVersion(),
...options.modEnv,
},
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ class BuiltinLambdaStack extends cdk.Stack {
}
}

class StackWithNoResources extends cdk.Stack {}

const app = new cdk.App({
context: {
'@aws-cdk/core:assetHashSalt': process.env.CODEBUILD_BUILD_ID, // Force all assets to be unique, but consistent in one build
Expand Down Expand Up @@ -492,6 +494,10 @@ switch (stackSet) {
stage.synth({ validateOnSynthesis: true });
break;

case 'stage-with-no-resources':
new StackWithNoResources(app, `${stackPrefix}-stage-with-no-resources`);
break;

default:
throw new Error(`Unrecognized INTEG_STACK_SET: '${stackSet}'`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const cdk = require('aws-cdk-lib/core');

const stackPrefix = process.env.STACK_NAME_PREFIX;
if (!stackPrefix) {
throw new Error(`the STACK_NAME_PREFIX environment variable is required`);
}

const app = new cdk.App();
new NoStackApp(app, `${stackPrefix}-no-stack-1`);

app.synth();
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"app": "node app.js",
"versionReporting": false,
"context": {
"aws-cdk:enableDiffNoFail": "true"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ integTest('cli-lib deploy', withCliLibFixture(async (fixture) => {
}
}));

integTest('cli-lib deploy no stack', withCliLibFixture(async (fixture) => {
const stackName = fixture.fullStackName('no-stack-1');

try {
// deploy the stack
await fixture.cdk(['deploy', stackName], {
options: ['--ignore-no-stacks'],
});

// verify the number of resources in the stack
const expectedStack = await fixture.aws.cloudFormation('describeStackResources', {
StackName: stackName,
});
expect(expectedStack.StackResources?.length).toEqual(0);
} finally {
// delete the stack
await fixture.cdk(['destroy', stackName], {
captureStderr: false,
});
}
}));

integTest('security related changes without a CLI are expected to fail when approval is required', withCliLibFixture(async (fixture) => {
const stdErr = await fixture.cdk(['deploy', fixture.fullStackName('simple-1')], {
onlyStderr: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,31 @@ integTest('deploy stack without resource', withDefaultFixture(async (fixture) =>
.rejects.toThrow('conditional-resource does not exist');
}));

integTest('deploy --ignore-no-stacks', withDefaultFixture(async (fixture) => {
const stackArn = await fixture.cdkDeploy('stage-with-no-resources', {
options: ['--ignore-no-stacks'],
modEnv: {
INTEG_STACK_SET: 'stage-with-no-resources',
},
});

// verify that we only deployed both stacks (there are 2 ARNs in the output)
/* eslint-disable no-console */
console.log(stackArn);
}));

integTest('deploy stack with no resources and no --ignore-no-stacks', withDefaultFixture(async (fixture) => {
const stackArn = await fixture.cdkDeploy('stage-with-no-resources', {
modEnv: {
INTEG_STACK_SET: 'stage-with-no-resources',
},
});

// verify that we only deployed both stacks (there are 2 ARNs in the output)
/* eslint-disable no-console */
console.log(stackArn);
}));

integTest('IAM diff', withDefaultFixture(async (fixture) => {
const output = await fixture.cdk(['diff', fixture.fullStackName('iam-test')]);

Expand Down
14 changes: 14 additions & 0 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,20 @@ $ cdk deploy --method=prepare-change-set --change-set-name MyChangeSetName
For more control over when stack changes are deployed, the CDK can generate a
CloudFormation change set but not execute it.

#### Ignore No Stacks

You may have an app with multiple environments, e.g., dev and prod. When starting
development, your prod app may not have any resources or the resources are commented
out. In this scenario, you will receive an error message stating that the app has no
stacks.

To bypass this error messages, you can pass the `--ignore-no-stacks` flag to the
`deploy` command:

```console
$ cdk deploy --ignore-no-stacks
```

#### Hotswap deployments for faster development

You can pass the `--hotswap` flag to the `deploy` command:
Expand Down
8 changes: 6 additions & 2 deletions packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,12 @@ export class CloudAssembly {
const allTopLevel = selector.allTopLevel ?? false;
const patterns = sanitizePatterns(selector.patterns);

if (stacks.length === 0 && !options.ignoreNoStacks) {
throw new Error('This app contains no stacks');
if (stacks.length === 0) {
if (options.ignoreNoStacks) {
return new StackCollection(this, []);
} else {
throw new Error('This app contains no stacks');
}
}

if (allTopLevel) {
Expand Down
32 changes: 32 additions & 0 deletions packages/aws-cdk/test/api/cloud-assembly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,38 @@ test('select behavior with nested assemblies: repeat', async() => {
expect(x.stackCount).toBe(2);
});

test('select behavior with no stacks and ignore stacks option', async() => {
// GIVEN
const cxasm = await testCloudAssemblyNoStacks();

// WHEN
const x = await cxasm.selectStacks({ patterns: [] }, {
defaultBehavior: DefaultSelection.AllStacks,
ignoreNoStacks: true,
});

// THEN
expect(x.stackCount).toBe(0);
});

test('select behavior with no stacks and no ignore stacks option', async() => {
// GIVEN
const cxasm = await testCloudAssemblyNoStacks();

// WHEN & THEN
await expect(cxasm.selectStacks({ patterns: [] }, { defaultBehavior: DefaultSelection.AllStacks, ignoreNoStacks: false }))
.rejects.toThrow('This app contains no stacks');
});

test('select behavior with no stacks and default ignore stacks options (false)', async() => {
// GIVEN
const cxasm = await testCloudAssemblyNoStacks();

// WHEN & THEN
await expect(cxasm.selectStacks({ patterns: [] }, { defaultBehavior: DefaultSelection.AllStacks }))
.rejects.toThrow('This app contains no stacks');
});

async function testCloudAssembly({ env }: { env?: string, versionReporting?: boolean } = {}) {
const cloudExec = new MockCloudExecutable({
stacks: [{
Expand Down

0 comments on commit 4f0d20e

Please sign in to comment.