Skip to content

Commit

Permalink
fix(core): cdk diff on large templates fails when passing in `toolk…
Browse files Browse the repository at this point in the history
…itStackName` and `qualifier` (#31636)

Closes #29179 

### Reason for this change

If your account is bootstrapped with a custom `toolkitStackName` or `qualifier`, then running `cdk diff` does not work for large diff templates of over 50 KiB in size.

### Description of changes

The `toolkitStackName` was not passed down all the way through the `cdk diff` code path - this PR fixes the issue by adding the optional `toolkitStackName` property to the `DiffOptions` interface in `cdk-toolkit.ts` and passing that value all the way through the diff code path to the `uploadBodyParameterAndCreateChangeSet` function in `cloudformation.ts`, where the template asset is built and published.

### Description of how you validated changes

I created a CDK app locally and ran the commands (exactly following the reproduction steps from the original issue) using the CLI build from this PR and was able to successfully run `cdk diff` without needing to pass in the `--toolkit-stack-name` flag. 

### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
sumupitchayan authored Oct 7, 2024
1 parent 5e7e61f commit f603c97
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 5 deletions.
7 changes: 6 additions & 1 deletion packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ interface CommonCdkBootstrapCommandOptions {
* @default - none
*/
readonly tags?: string;

/**
* @default - the default CDK qualifier
*/
readonly qualifier?: string;
}

export interface CdkLegacyBootstrapCommandOptions extends CommonCdkBootstrapCommandOptions {
Expand Down Expand Up @@ -408,7 +413,7 @@ export class TestFixture extends ShellHelper {
if (options.bootstrapBucketName) {
args.push('--bootstrap-bucket-name', options.bootstrapBucketName);
}
args.push('--qualifier', this.qualifier);
args.push('--qualifier', options.qualifier ?? this.qualifier);
if (options.cfnExecutionPolicy) {
args.push('--cloudformation-execution-policies', options.cfnExecutionPolicy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,17 @@ class IamRolesStack extends cdk.Stack {
// Environment variabile is used to create a bunch of roles to test
// that large diff templates are uploaded to S3 to create the changeset.
for(let i = 1; i <= Number(process.env.NUMBER_OF_ROLES) ; i++) {
new iam.Role(this, `Role${i}`, {
const role = new iam.Role(this, `Role${i}`, {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});
const cfnRole = role.node.defaultChild;

// For any extra IAM roles created, add a ton of metadata so that the template size is > 50 KiB.
if (i > 1) {
for(let i = 1; i <= 30 ; i++) {
cfnRole.addMetadata('a'.repeat(1000), 'v');
}
}
}
}
}
Expand Down Expand Up @@ -792,6 +800,7 @@ switch (stackSet) {

new LambdaStack(app, `${stackPrefix}-lambda`);

// This stack is used to test diff with large templates by creating a role with a ton of metadata
new IamRolesStack(app, `${stackPrefix}-iam-roles`);

if (process.env.ENABLE_VPC_TESTING == 'IMPORT') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,18 +1034,18 @@ integTest(
integTest(
'cdk diff with large changeset does not fail',
withDefaultFixture(async (fixture) => {
// GIVEN - small initial stack with only ane IAM role
// GIVEN - small initial stack with only one IAM role
await fixture.cdkDeploy('iam-roles', {
modEnv: {
NUMBER_OF_ROLES: '1',
},
});

// WHEN - adding 200 roles to the same stack to create a large diff
// WHEN - adding an additional role with a ton of metadata to create a large diff
const diff = await fixture.cdk(['diff', fixture.fullStackName('iam-roles')], {
verbose: true,
modEnv: {
NUMBER_OF_ROLES: '200',
NUMBER_OF_ROLES: '2',
},
});

Expand All @@ -1055,6 +1055,40 @@ integTest(
}),
);

integTest('cdk diff with large changeset and custom toolkit stack name and qualifier does not fail', withoutBootstrap(async (fixture) => {
// Bootstrapping with custom toolkit stack name and qualifier
const qualifier = 'abc1111';
const toolkitStackName = 'custom-stack2';
await fixture.cdkBootstrapModern({
verbose: true,
toolkitStackName: toolkitStackName,
qualifier: qualifier,
});

// Deploying small initial stack with only one IAM role
await fixture.cdkDeploy('iam-roles', {
modEnv: {
NUMBER_OF_ROLES: '1',
},
options: [
'--toolkit-stack-name', toolkitStackName,
'--context', `@aws-cdk/core:bootstrapQualifier=${qualifier}`,
],
});

// WHEN - adding a role with a ton of metadata to create a large diff
const diff = await fixture.cdk(['diff', '--toolkit-stack-name', toolkitStackName, '--context', `@aws-cdk/core:bootstrapQualifier=${qualifier}`, fixture.fullStackName('iam-roles')], {
verbose: true,
modEnv: {
NUMBER_OF_ROLES: '2',
},
});

// Assert that the CLI assumes the file publishing role:
expect(diff).toMatch(/Assuming role .*file-publishing-role/);
expect(diff).toContain('success: Published');
}));

integTest(
'cdk diff --security-only successfully outputs sso-permission-set-without-managed-policy information',
withDefaultFixture(async (fixture) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/aws-cdk/lib/api/util/cloudformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export type PrepareChangeSetOptions = {
sdkProvider: SdkProvider;
stream: NodeJS.WritableStream;
parameters: { [name: string]: string | undefined };
toolkitStackName?: string;
resourcesToImport?: ResourcesToImport;
}

Expand Down Expand Up @@ -375,9 +376,11 @@ async function uploadBodyParameterAndCreateChangeSet(options: PrepareChangeSetOp
for (const entry of file_entries) {
await options.deployments.buildSingleAsset(artifact, assetManifest, entry, {
stack: options.stack,
toolkitStackName: options.toolkitStackName,
});
await options.deployments.publishSingleAsset(assetManifest, entry, {
stack: options.stack,
toolkitStackName: options.toolkitStackName,
});
}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/aws-cdk/lib/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export class CdkToolkit {
parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]),
resourcesToImport,
stream,
toolkitStackName: options.toolkitStackName,
});
} else {
debug(`the stack '${stack.stackName}' has not been deployed to CloudFormation or describeStacks call failed, skipping changeset creation.`);
Expand Down Expand Up @@ -1062,6 +1063,13 @@ export interface DiffOptions {
*/
stackNames: string[];

/**
* Name of the toolkit stack, if not the default name
*
* @default 'CDKToolkit'
*/
readonly toolkitStackName?: string;

/**
* Only select the given stack
*
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
compareAgainstProcessedTemplate: args.processed,
quiet: args.quiet,
changeSet: args['change-set'],
toolkitStackName: toolkitStackName,
});

case 'bootstrap':
Expand Down

0 comments on commit f603c97

Please sign in to comment.