Skip to content

Commit f89613a

Browse files
author
Benjamin Pace
committed
fix(drift): use correct CloudFormation console URL format for changeset links
The changeset console URL was using an incorrect path-based format that doesn't resolve in the CloudFormation console. Switch to the standard query-parameter format: #/stacks/changesets/changes?stackId=&changeSetId= Also thread the stack ARN through flattenChanges so changeset URLs include the stackId parameter needed for proper navigation.
1 parent 764dbe1 commit f89613a

2 files changed

Lines changed: 25 additions & 10 deletions

File tree

packages/amplify-cli/src/__tests__/commands/drift-detection/services/drift-formatter.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ describe('createUnifiedCategoryView', () => {
150150
ResourceType: 'AWS::CloudFormation::Stack',
151151
Action: ChangeAction.Modify,
152152
ChangeSetId: nestedChangeSetId,
153+
PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123:stack/nested-api-stack/abc',
153154
nestedChanges: [
154155
{
155156
LogicalResourceId: 'Schema',
@@ -173,12 +174,12 @@ describe('createUnifiedCategoryView', () => {
173174
"
174175
API Schema
175176
Template Drift: S3 and deployed templates differ
176-
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
177+
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
177178
~ AWS::AppSync::GraphQLSchema
178179
179180
API NewResolver
180181
Template Drift: S3 and deployed templates differ
181-
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
182+
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
182183
+ AWS::AppSync::Resolver
183184
184185
"
@@ -374,12 +375,14 @@ describe('createUnifiedCategoryView', () => {
374375
ResourceType: 'AWS::CloudFormation::Stack',
375376
Action: ChangeAction.Modify,
376377
ChangeSetId: outerChangeSetId,
378+
PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123:stack/outer-stack/abc',
377379
nestedChanges: [
378380
{
379381
LogicalResourceId: 'apiMyGraphQLGraphQLAPI',
380382
ResourceType: 'AWS::CloudFormation::Stack',
381383
Action: ChangeAction.Modify,
382384
ChangeSetId: deepChangeSetId,
385+
PhysicalResourceId: 'arn:aws:cloudformation:us-east-1:123:stack/deep-stack/ghi',
383386
nestedChanges: [
384387
{
385388
LogicalResourceId: 'Schema',
@@ -399,7 +402,7 @@ describe('createUnifiedCategoryView', () => {
399402
"
400403
API Schema
401404
Template Drift: S3 and deployed templates differ
402-
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
405+
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
403406
~ AWS::AppSync::GraphQLSchema
404407
405408
"

packages/amplify-cli/src/commands/drift-detection/services/drift-formatter.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface DriftBlock {
2121
driftDetectionId?: string;
2222
templateChange?: ResourceChangeWithNested;
2323
changeSetId?: string;
24+
stackArn?: string;
2425
}
2526

2627
/**
@@ -76,12 +77,12 @@ function cfnDriftConsoleUrl(stackArn: string): string | undefined {
7677
/**
7778
* Build CloudFormation console URL for a changeset details page
7879
*/
79-
function cfnChangesetConsoleUrl(changeSetArn: string): string | undefined {
80+
function cfnChangesetConsoleUrl(changeSetArn: string, stackArn?: string): string | undefined {
8081
const region = regionFromArn(changeSetArn);
8182
if (!region) return undefined;
82-
return `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/changesets/details?changeSetId=${encodeURIComponent(
83-
changeSetArn,
84-
)}`;
83+
const encodedStackId = encodeURIComponent(stackArn || '');
84+
const encodedChangeSetId = encodeURIComponent(changeSetArn);
85+
return `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/changesets/changes?stackId=${encodedStackId}&changeSetId=${encodedChangeSetId}`;
8586
}
8687

8788
/**
@@ -114,10 +115,20 @@ function collectDriftBlocks(phase1: CloudFormationDriftResults, phase2: Template
114115

115116
// Phase 2: One block per template change (flatten nested stacks to leaf resources)
116117
if (!phase2.skipped && phase2.changes.length > 0) {
117-
const flattenChanges = (changes: ResourceChangeWithNested[], fallbackCategory: string, fallbackChangeSetId?: string): void => {
118+
const flattenChanges = (
119+
changes: ResourceChangeWithNested[],
120+
fallbackCategory: string,
121+
fallbackChangeSetId?: string,
122+
parentStackArn?: string,
123+
): void => {
118124
for (const change of changes) {
119125
if (change.ResourceType === 'AWS::CloudFormation::Stack' && change.nestedChanges && change.nestedChanges.length > 0) {
120-
flattenChanges(change.nestedChanges, extractCategory(change.LogicalResourceId), change.ChangeSetId || fallbackChangeSetId);
126+
flattenChanges(
127+
change.nestedChanges,
128+
extractCategory(change.LogicalResourceId),
129+
change.ChangeSetId || fallbackChangeSetId,
130+
change.PhysicalResourceId || parentStackArn,
131+
);
121132
} else {
122133
const resourceCategory = extractCategory(change.LogicalResourceId);
123134
blocks.push({
@@ -126,6 +137,7 @@ function collectDriftBlocks(phase1: CloudFormationDriftResults, phase2: Template
126137
type: 'template',
127138
templateChange: change,
128139
changeSetId: change.ChangeSetId || fallbackChangeSetId,
140+
stackArn: parentStackArn,
129141
});
130142
}
131143
}
@@ -210,7 +222,7 @@ function formatBlock(block: DriftBlock): string {
210222

211223
output += ` Template Drift: S3 and deployed templates differ\n`;
212224
if (block.changeSetId) {
213-
const changesetUrl = cfnChangesetConsoleUrl(block.changeSetId);
225+
const changesetUrl = cfnChangesetConsoleUrl(block.changeSetId, block.stackArn);
214226
output += ` Changeset Id: ${changesetUrl || block.changeSetId}\n`;
215227
}
216228
output += ` ${colorResourceLine(symbol, `${symbol} ${change.ResourceType || 'Unknown'}`)}\n`;

0 commit comments

Comments
 (0)