diff --git a/packages/amplify-cli/src/__tests__/commands/drift-detection/services/drift-formatter.test.ts b/packages/amplify-cli/src/__tests__/commands/drift-detection/services/drift-formatter.test.ts index d549ba27f6b..b33259a4651 100644 --- a/packages/amplify-cli/src/__tests__/commands/drift-detection/services/drift-formatter.test.ts +++ b/packages/amplify-cli/src/__tests__/commands/drift-detection/services/drift-formatter.test.ts @@ -150,6 +150,7 @@ describe('createUnifiedCategoryView', () => { ResourceType: 'AWS::CloudFormation::Stack', Action: ChangeAction.Modify, ChangeSetId: nestedChangeSetId, + PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123:stack/nested-api-stack/abc', nestedChanges: [ { LogicalResourceId: 'Schema', @@ -173,12 +174,12 @@ describe('createUnifiedCategoryView', () => { " API Schema Template Drift: S3 and deployed templates differ - Changeset Id: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/changesets/details?changeSetId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3AchangeSet%2Fnested-api-cs%2Fdef + Changeset Id: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/changesets/changes?stackId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3Astack%2Fnested-api-stack%2Fabc&changeSetId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3AchangeSet%2Fnested-api-cs%2Fdef ~ AWS::AppSync::GraphQLSchema API NewResolver Template Drift: S3 and deployed templates differ - Changeset Id: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/changesets/details?changeSetId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3AchangeSet%2Fnested-api-cs%2Fdef + Changeset Id: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/changesets/changes?stackId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3Astack%2Fnested-api-stack%2Fabc&changeSetId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3AchangeSet%2Fnested-api-cs%2Fdef + AWS::AppSync::Resolver " @@ -374,12 +375,14 @@ describe('createUnifiedCategoryView', () => { ResourceType: 'AWS::CloudFormation::Stack', Action: ChangeAction.Modify, ChangeSetId: outerChangeSetId, + PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123:stack/outer-stack/abc', nestedChanges: [ { LogicalResourceId: 'apiMyGraphQLGraphQLAPI', ResourceType: 'AWS::CloudFormation::Stack', Action: ChangeAction.Modify, ChangeSetId: deepChangeSetId, + PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123:stack/deep-stack/ghi', nestedChanges: [ { LogicalResourceId: 'Schema', @@ -399,7 +402,7 @@ describe('createUnifiedCategoryView', () => { " API Schema Template Drift: S3 and deployed templates differ - Changeset Id: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/changesets/details?changeSetId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3AchangeSet%2Fdeep-cs%2Fghi + Changeset Id: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/changesets/changes?stackId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3Astack%2Fdeep-stack%2Fghi&changeSetId=arn%3Aaws%3Acloudformation%3Aus-east-1%3A123%3AchangeSet%2Fdeep-cs%2Fghi ~ AWS::AppSync::GraphQLSchema " diff --git a/packages/amplify-cli/src/commands/drift-detection/services/drift-formatter.ts b/packages/amplify-cli/src/commands/drift-detection/services/drift-formatter.ts index 14d1774f8b5..58b61d48715 100644 --- a/packages/amplify-cli/src/commands/drift-detection/services/drift-formatter.ts +++ b/packages/amplify-cli/src/commands/drift-detection/services/drift-formatter.ts @@ -21,6 +21,7 @@ interface DriftBlock { driftDetectionId?: string; templateChange?: ResourceChangeWithNested; changeSetId?: string; + stackArn?: string; } /** @@ -76,12 +77,12 @@ function cfnDriftConsoleUrl(stackArn: string): string | undefined { /** * Build CloudFormation console URL for a changeset details page */ -function cfnChangesetConsoleUrl(changeSetArn: string): string | undefined { +function cfnChangesetConsoleUrl(changeSetArn: string, stackArn?: string): string | undefined { const region = regionFromArn(changeSetArn); if (!region) return undefined; - return `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/changesets/details?changeSetId=${encodeURIComponent( - changeSetArn, - )}`; + const encodedStackId = encodeURIComponent(stackArn || ''); + const encodedChangeSetId = encodeURIComponent(changeSetArn); + return `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/changesets/changes?stackId=${encodedStackId}&changeSetId=${encodedChangeSetId}`; } /** @@ -114,10 +115,20 @@ function collectDriftBlocks(phase1: CloudFormationDriftResults, phase2: Template // Phase 2: One block per template change (flatten nested stacks to leaf resources) if (!phase2.skipped && phase2.changes.length > 0) { - const flattenChanges = (changes: ResourceChangeWithNested[], fallbackCategory: string, fallbackChangeSetId?: string): void => { + const flattenChanges = ( + changes: ResourceChangeWithNested[], + fallbackCategory: string, + fallbackChangeSetId?: string, + parentStackArn?: string, + ): void => { for (const change of changes) { if (change.ResourceType === 'AWS::CloudFormation::Stack' && change.nestedChanges && change.nestedChanges.length > 0) { - flattenChanges(change.nestedChanges, extractCategory(change.LogicalResourceId), change.ChangeSetId || fallbackChangeSetId); + flattenChanges( + change.nestedChanges, + extractCategory(change.LogicalResourceId), + change.ChangeSetId || fallbackChangeSetId, + change.PhysicalResourceId || parentStackArn, + ); } else { const resourceCategory = extractCategory(change.LogicalResourceId); blocks.push({ @@ -126,6 +137,7 @@ function collectDriftBlocks(phase1: CloudFormationDriftResults, phase2: Template type: 'template', templateChange: change, changeSetId: change.ChangeSetId || fallbackChangeSetId, + stackArn: parentStackArn, }); } } @@ -210,7 +222,7 @@ function formatBlock(block: DriftBlock): string { output += ` Template Drift: S3 and deployed templates differ\n`; if (block.changeSetId) { - const changesetUrl = cfnChangesetConsoleUrl(block.changeSetId); + const changesetUrl = cfnChangesetConsoleUrl(block.changeSetId, block.stackArn); output += ` Changeset Id: ${changesetUrl || block.changeSetId}\n`; } output += ` ${colorResourceLine(symbol, `${symbol} ${change.ResourceType || 'Unknown'}`)}\n`;