Skip to content

Commit

Permalink
Merge branch 'main' into fix-ecr-assets-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Apr 8, 2024
2 parents 87574b2 + f1fd56a commit d9c4d2b
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 40 deletions.
14 changes: 8 additions & 6 deletions packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// The SDK is only used to reference `DescribeChangeSetOutput`, so the SDK is added as a devDependency.
// The SDK should not make network calls here
import type { CloudFormation } from 'aws-sdk';
import type { DescribeChangeSetOutput as DescribeChangeSet } from '@aws-sdk/client-cloudformation';
import * as impl from './diff';
import * as types from './diff/types';
import { deepEqual, diffKeyedEntities, unionOf } from './diff/util';

export * from './diff/types';

export type DescribeChangeSetOutput = DescribeChangeSet;

type DiffHandler = (diff: types.ITemplateDiff, oldValue: any, newValue: any) => void;
type HandlerRegistry = { [section: string]: DiffHandler };

Expand Down Expand Up @@ -45,7 +47,7 @@ const DIFF_HANDLERS: HandlerRegistry = {
export function fullDiff(
currentTemplate: { [key: string]: any },
newTemplate: { [key: string]: any },
changeSet?: CloudFormation.DescribeChangeSetOutput,
changeSet?: DescribeChangeSetOutput,
isImport?: boolean,
): types.TemplateDiff {

Expand Down Expand Up @@ -212,7 +214,7 @@ function deepCopy(x: any): any {
return x;
}

function addImportInformation(diff: types.TemplateDiff, changeSet: CloudFormation.DescribeChangeSetOutput) {
function addImportInformation(diff: types.TemplateDiff, changeSet: DescribeChangeSetOutput) {
const imports = findResourceImports(changeSet);
diff.resources.forEachDifference((logicalId: string, change: types.ResourceDifference) => {
if (imports.includes(logicalId)) {
Expand All @@ -227,7 +229,7 @@ function makeAllResourceChangesImports(diff: types.TemplateDiff) {
});
}

function filterFalsePositives(diff: types.TemplateDiff, changeSet: CloudFormation.DescribeChangeSetOutput) {
function filterFalsePositives(diff: types.TemplateDiff, changeSet: DescribeChangeSetOutput) {
const replacements = findResourceReplacements(changeSet);
diff.resources.forEachDifference((logicalId: string, change: types.ResourceDifference) => {
if (change.resourceType.includes('AWS::Serverless')) {
Expand Down Expand Up @@ -268,7 +270,7 @@ function filterFalsePositives(diff: types.TemplateDiff, changeSet: CloudFormatio
});
}

function findResourceImports(changeSet: CloudFormation.DescribeChangeSetOutput): string[] {
function findResourceImports(changeSet: DescribeChangeSetOutput): string[] {
const importedResourceLogicalIds = [];
for (const resourceChange of changeSet.Changes ?? []) {
if (resourceChange.ResourceChange?.Action === 'Import') {
Expand All @@ -279,7 +281,7 @@ function findResourceImports(changeSet: CloudFormation.DescribeChangeSetOutput):
return importedResourceLogicalIds;
}

function findResourceReplacements(changeSet: CloudFormation.DescribeChangeSetOutput): types.ResourceReplacements {
function findResourceReplacements(changeSet: DescribeChangeSetOutput): types.ResourceReplacements {
const replacements: types.ResourceReplacements = {};
for (const resourceChange of changeSet.Changes ?? []) {
const propertiesReplaced: { [propName: string]: types.ChangeSetReplacement } = {};
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/cloudformation-diff/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"dependencies": {
"@aws-cdk/aws-service-spec": "^0.0.61",
"@aws-cdk/service-spec-types": "^0.0.61",
"aws-sdk": "2.1586.0",
"chalk": "^4",
"diff": "^5.2.0",
"fast-deep-equal": "^3.1.3",
Expand All @@ -35,6 +34,7 @@
"devDependencies": {
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@aws-sdk/client-cloudformation": "^3.529.1",
"@types/jest": "^29.5.12",
"@types/string-width": "^4.0.1",
"fast-check": "^3.17.0",
Expand Down Expand Up @@ -64,4 +64,4 @@
"dependencies/cdk-point-dependencies"
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,7 @@ describe('changeset', () => {
ResourceType: 'AWS::S3::Bucket',
Replacement: 'True',
Details: [{
Evaluation: 'Direct',
Evaluation: 'Static',
Target: {
Attribute: 'Properties',
Name: 'BucketName',
Expand Down Expand Up @@ -1153,7 +1153,7 @@ describe('changeset', () => {
ResourceType: 'AWS::Lambda::Function', // The SAM transform is applied before the changeset is created, so the changeset has a Lambda resource here!
Replacement: 'False',
Details: [{
Evaluation: 'Direct',
Evaluation: 'Static',
Target: {
Attribute: 'Properties',
Name: 'Code',
Expand Down
11 changes: 7 additions & 4 deletions packages/aws-cdk/lib/api/util/cloudformation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Temporarily pull this in to avoid creating conflicts with the sdks in this package
import { DescribeChangeSetOutput } from '@aws-cdk/cloudformation-diff';
import { SSMPARAM_NO_INVALIDATE } from '@aws-cdk/cx-api';
import * as cxapi from '@aws-cdk/cx-api';
import { CloudFormation } from 'aws-sdk';
Expand Down Expand Up @@ -311,7 +313,7 @@ export type CreateChangeSetOptions = {
/**
* Create a changeset for a diff operation
*/
export async function createDiffChangeSet(options: PrepareChangeSetOptions): Promise<CloudFormation.DescribeChangeSetOutput | undefined> {
export async function createDiffChangeSet(options: PrepareChangeSetOptions): Promise<DescribeChangeSetOutput | undefined> {
// `options.stack` has been modified to include any nested stack templates directly inline with its own template, under a special `NestedTemplate` property.
// Thus the parent template's Resources section contains the nested template's CDK metadata check, which uses Fn::Equals.
// This causes CreateChangeSet to fail with `Template Error: Fn::Equals cannot be partially collapsed`.
Expand All @@ -327,7 +329,7 @@ export async function createDiffChangeSet(options: PrepareChangeSetOptions): Pro
return uploadBodyParameterAndCreateChangeSet(options);
}

async function uploadBodyParameterAndCreateChangeSet(options: PrepareChangeSetOptions): Promise<CloudFormation.DescribeChangeSetOutput | undefined> {
async function uploadBodyParameterAndCreateChangeSet(options: PrepareChangeSetOptions): Promise<DescribeChangeSetOutput | undefined> {
try {
const preparedSdk = (await options.deployments.prepareSdkWithDeployRole(options.stack));
const bodyParameter = await makeBodyParameterAndUpload(
Expand Down Expand Up @@ -363,7 +365,7 @@ async function uploadBodyParameterAndCreateChangeSet(options: PrepareChangeSetOp
}
}

async function createChangeSet(options: CreateChangeSetOptions): Promise<CloudFormation.DescribeChangeSetOutput> {
async function createChangeSet(options: CreateChangeSetOptions): Promise<DescribeChangeSetOutput> {
await cleanupOldChangeset(options.changeSetName, options.stack.stackName, options.cfn);

debug(`Attempting to create ChangeSet with name ${options.changeSetName} for stack ${options.stack.stackName}`);
Expand All @@ -390,7 +392,8 @@ async function createChangeSet(options: CreateChangeSetOptions): Promise<CloudFo
const createdChangeSet = await waitForChangeSet(options.cfn, options.stack.stackName, options.changeSetName, { fetchAll: options.willExecute });
await cleanupOldChangeset(options.changeSetName, options.stack.stackName, options.cfn);

return createdChangeSet;
// TODO: Update this once we remove sdkv2 from the rest of this package
return createdChangeSet as DescribeChangeSetOutput;
}

export async function cleanupOldChangeset(changeSetName: string, stackName: string, cfn: CloudFormation) {
Expand Down
31 changes: 19 additions & 12 deletions packages/aws-cdk/lib/diff.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { format } from 'util';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import * as cfnDiff from '@aws-cdk/cloudformation-diff';
import {
type DescribeChangeSetOutput,
type FormatStream,
type TemplateDiff,
formatDifferences,
formatSecurityChanges,
fullDiff,
mangleLikeCloudFormation,
} from '@aws-cdk/cloudformation-diff';
import * as cxapi from '@aws-cdk/cx-api';
import { CloudFormation } from 'aws-sdk';
import * as chalk from 'chalk';
import { NestedStackTemplates } from './api/nested-stack-helpers';
import { print, warning } from './logging';
Expand All @@ -24,18 +31,18 @@ export function printStackDiff(
strict: boolean,
context: number,
quiet: boolean,
changeSet?: CloudFormation.DescribeChangeSetOutput,
changeSet?: DescribeChangeSetOutput,
isImport?: boolean,
stream: cfnDiff.FormatStream = process.stderr,
stream: FormatStream = process.stderr,
nestedStackTemplates?: { [nestedStackLogicalId: string]: NestedStackTemplates }): number {

let diff = cfnDiff.fullDiff(oldTemplate, newTemplate.template, changeSet, isImport);
let diff = fullDiff(oldTemplate, newTemplate.template, changeSet, isImport);

// detect and filter out mangled characters from the diff
let filteredChangesCount = 0;
if (diff.differenceCount && !strict) {
const mangledNewTemplate = JSON.parse(cfnDiff.mangleLikeCloudFormation(JSON.stringify(newTemplate.template)));
const mangledDiff = cfnDiff.fullDiff(oldTemplate, mangledNewTemplate, changeSet);
const mangledNewTemplate = JSON.parse(mangleLikeCloudFormation(JSON.stringify(newTemplate.template)));
const mangledDiff = fullDiff(oldTemplate, mangledNewTemplate, changeSet);
filteredChangesCount = Math.max(0, diff.differenceCount - mangledDiff.differenceCount);
if (filteredChangesCount > 0) {
diff = mangledDiff;
Expand All @@ -55,7 +62,7 @@ export function printStackDiff(
let stackDiffCount = 0;
if (!diff.isEmpty) {
stackDiffCount++;
cfnDiff.formatDifferences(stream, diff, {
formatDifferences(stream, diff, {
...logicalIdMapFromTemplate(oldTemplate),
...buildLogicalToPathMap(newTemplate),
}, context);
Expand Down Expand Up @@ -109,16 +116,16 @@ export function printSecurityDiff(
oldTemplate: any,
newTemplate: cxapi.CloudFormationStackArtifact,
requireApproval: RequireApproval,
changeSet?: CloudFormation.DescribeChangeSetOutput,
changeSet?: DescribeChangeSetOutput,
): boolean {
const diff = cfnDiff.fullDiff(oldTemplate, newTemplate.template, changeSet);
const diff = fullDiff(oldTemplate, newTemplate.template, changeSet);

if (difRequiresApproval(diff, requireApproval)) {
// eslint-disable-next-line max-len
warning(`This deployment will make potentially sensitive changes according to your current security approval level (--require-approval ${requireApproval}).`);
warning('Please confirm you intend to make the following modifications:\n');

cfnDiff.formatSecurityChanges(process.stdout, diff, buildLogicalToPathMap(newTemplate));
formatSecurityChanges(process.stdout, diff, buildLogicalToPathMap(newTemplate));
return true;
}
return false;
Expand All @@ -130,7 +137,7 @@ export function printSecurityDiff(
* TODO: Filter the security impact determination based off of an enum that allows
* us to pick minimum "severities" to alert on.
*/
function difRequiresApproval(diff: cfnDiff.TemplateDiff, requireApproval: RequireApproval) {
function difRequiresApproval(diff: TemplateDiff, requireApproval: RequireApproval) {
switch (requireApproval) {
case RequireApproval.Never: return false;
case RequireApproval.AnyChange: return diff.permissionsAnyChanges;
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/test/diff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ describe('stack exists checks', () => {
Changes: [
{
ResourceChange: {
Action: 'Dummy',
Action: 'Add',
LogicalResourceId: 'Object',
},
},
Expand Down
Loading

0 comments on commit d9c4d2b

Please sign in to comment.