Skip to content

fix(gen2-migration): retain GraphQL model tables during decommission#14662

Merged
sai-ray merged 11 commits intogen2-migrationfrom
sai/fix-decommission-dynamoDB-deletion-gen2-migration
Apr 9, 2026
Merged

fix(gen2-migration): retain GraphQL model tables during decommission#14662
sai-ray merged 11 commits intogen2-migrationfrom
sai/fix-decommission-dynamoDB-deletion-gen2-migration

Conversation

@sai-ray
Copy link
Copy Markdown
Contributor

@sai-ray sai-ray commented Mar 11, 2026

Fixes: #14524

Description of changes

Running amplify gen2-migration decommission after migrating an Amplify app with api DynamoDB tables, fails with Validations failed: Decommission will delete stateful resources. The refactor step moves auth, storage, and analytics resources to Gen2 but not GraphQL model tables. Gen2 imports those via modelNameToTableNameMapping. So when decommission tries to delete the Gen1 stack, those tables are still there.

Changes

Lock step (lock.ts):

  • Added findApiCategoryStacks(): identifies API nested stacks using extractCategory on logical resource IDs from DescribeStackResources.
  • Added setDeletionPolicyRetainOnDynamoTables() for each API stack: lists its model nested stacks, fetches each template via GetTemplate, sets DeletionPolicy: Retain on AWS::DynamoDB::Table resources.
  • Added changeset validation before applying the update: creates a changeset, validates that every change is a Modify on AWS::DynamoDB::Table or AWS::IAM::Policy (known side effect), aborts if unexpected changes are found. The changeset is deleted after validation, then the update is applied via UpdateStackCommand + waitUntilStackUpdateComplete.
  • Replaced tryUpdateStack (removed in fix(gen2-migration): refactor breaks for user pool groups #14698) with inline UpdateStackCommand + SDK waiter, matching the Cfn.update() pattern.

Validation (validations.ts):

  • In getStatefulResources, when a AWS::DynamoDB::Table is found in a nested stack, fetches the stack template and checks DeletionPolicy. If Retain, skips flagging it. Tables without Retain are still flagged.

By submitting this pull request, I confirm that my contribution is
made under the terms of the Apache 2.0 license.

Issue #, if available

Description of how you validated changes

  • Ran the updated lock and decomission commands on a sample Gen1 app.
  • Added unit tests.

Checklist

  • PR description included
  • yarn test passes
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)
  • New AWS SDK calls or CloudFormation actions have been added to relevant test and service IAM policies
  • Pull request labels are added

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@sai-ray sai-ray requested a review from a team as a code owner March 11, 2026 08:32
@sai-ray sai-ray changed the title fix(gen2-migration): set DeletionPolicy Retain on GraphQL model tables during lock fix(gen2-migration): retain GraphQL model tables during decommission Mar 11, 2026
@sai-ray sai-ray requested a review from 9pace March 24, 2026 16:44
@9pace
Copy link
Copy Markdown

9pace commented Mar 25, 2026

For lock --rollback, what do we do? Leave them with the retain policy?

Also, this would modify our deployed cfn template. The proposed rollback validation is running template drift detection which would conflict here. Just flagging for discussion.

Copy link
Copy Markdown

@9pace 9pace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before tryUpdateStack, we should add a changeset validation step similar to what we do in the refactor step.

Right now, setDeletionPolicyRetainOnDynamoTables modifies the template and calls tryUpdateStack directly without verifying that the only changes CloudFormation will apply are DeletionPolicy updates.
for the lock step, we should implement:

 1. Before calling tryUpdateStack, create a changeset on the stack
 2. Describe the changeset and inspect Changes
 3. Validate that every change is a Modify on a AWS::DynamoDB::Table resource, scoped to a deletion policy update that we want
 4. If any unexpected changes are present, abort with a clear error

This is the same pattern as in _validations.ts. See:

const report = await this.createChangeSetReport(source);
return [
{
resource: this.resource,
validate: () => ({
description: `Ensure no unexpected changes to ${sourceStackName}`,
run: async () => ({ valid: report === undefined, report }),
}),

@sai-ray
Copy link
Copy Markdown
Contributor Author

sai-ray commented Mar 30, 2026

Before tryUpdateStack, we should add a changeset validation step similar to what we do in the refactor step.

Right now, setDeletionPolicyRetainOnDynamoTables modifies the template and calls tryUpdateStack directly without verifying that the only changes CloudFormation will apply are DeletionPolicy updates. for the lock step, we should implement:

 1. Before calling tryUpdateStack, create a changeset on the stack  2. Describe the changeset and inspect Changes  3. Validate that every change is a Modify on a AWS::DynamoDB::Table resource, scoped to a deletion policy update that we want  4. If any unexpected changes are present, abort with a clear error

This is the same pattern as in _validations.ts. See:

const report = await this.createChangeSetReport(source);
return [
{
resource: this.resource,
validate: () => ({
description: `Ensure no unexpected changes to ${sourceStackName}`,
run: async () => ({ valid: report === undefined, report }),
}),

@9pace
Added changeset validation before the stack update in setDeletionPolicyRetainOnDynamoTables.

The flow is now:

  1. Create a changeset with the modified template
  2. Wait for creation to complete
  3. Describe the changeset and validate the changes
  4. Delete the changeset (inspection only, not executed)
  5. Apply the update via UpdateStackCommand + waitUntilStackUpdateComplete

The validation allows only Modify actions on AWS::DynamoDB::Table (our intended DeletionPolicy change) and AWS::IAM::Policy (known side effect, CloudFormation recalculates the IAM policy because it references the table). Any other action type (Add, Remove) or resource type (AppSync resolvers, IAM roles, etc.) aborts with a descriptive error including a resolution field.

Also replaced the tryUpdateStack import (removed in #14698) with inline UpdateStackCommand + SDK waiter, matching the Cfn.update() pattern.

Added 4 tests:

  1. Happy path: DynamoDB table + IAM policy Modify, proceeds
  2. Add action, aborts
  3. Remove action, aborts
  4. Modify on unexpected resource type (AppSync resolver), aborts

Validated on a deployed app, lock completes successfully with the changeset validation passing.

@9pace
Copy link
Copy Markdown

9pace commented Mar 31, 2026

Still interested in #14662 (comment)
I will handle the drift scenario, but what about rollback?

@sai-ray sai-ray merged commit d4f8c2f into gen2-migration Apr 9, 2026
4 checks passed
@sai-ray sai-ray deleted the sai/fix-decommission-dynamoDB-deletion-gen2-migration branch April 9, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants