#24045·pulumi

Component state migration

Author: julienpCreated Jul 23, 2026Updated Sep 17, 2026
Labelskind/enhancementarea/component-packagespulumi/pulumi

A component’s implementation can change substantially between versions. Its logical resource graph may move from N resources to M resources even though the physical cloud objects remain unchanged. For example AWS Bucket vs BucketV2 with sidecar resources. Another example is moving from AWSX components to plain AWS resources.

Aliases only handle one-to-one resource renames and reparenting and users currently need to edit state manually, do complicated imports or accept unnecessary replacements.

We want to introduce an API that allows these migrations. The initial version will provide an API allow raw state modifications. A follow up might provide a more declarative API.

Draft developer docs on how migrations work

AWSX -> AWS example

Version 1 uses awsx.ecr.Repository. Its state contains three resources:

awsx:ecr:Repository "repo"
├── aws:ecr/repository:Repository "repo"
└── aws:ecr/lifecyclePolicy:LifecyclePolicy "repo"

Version 2 removes the AWSX dependency and registers the underlying resources directly:

aws:ecr/repository:Repository "repository"
└── aws:ecr/lifecyclePolicy:LifecyclePolicy "lifecycle-policy"

The migration adopts the existing repository and lifecycle-policy states, preserving their provider IDs, turning the V1 state into V2:

python
def migrate_awsx_repository(args):
    old_component, old_repository, old_policy = args.old_state

    # Already migrated.
    if old_component["type"] != "awsx:ecr:Repository":
        return None

    repository = copy.deepcopy(old_repository)
    repository["urn"] = args.urn
    repository["parent"] = old_component.get("parent")

    policy = copy.deepcopy(old_policy)
    policy["urn"] = lifecycle_policy_urn
    policy["parent"] = args.urn

    return pulumi.StateMigrationResult(
        new_state=[repository, policy],
        successors={
            # The component and its repository child both become the new
            # directly managed repository.
            old_component["urn"]: args.urn,
            old_repository["urn"]: args.urn,

            # The old policy becomes the directly managed policy.
            old_policy["urn"]: lifecycle_policy_urn,
        },
    )

# Register the AWS resources, with the migration attached to the `aws.ecr.Repository` resource

repository = aws.ecr.Repository(
    "repository",
    opts=pulumi.ResourceOptions(
        aliases=[
            pulumi.Alias(
                name="repo",
                type_="awsx:ecr:Repository",
            ),
        ],
        state_migrations=[migrate_awsx_repository],
    ),
)

lifecycle_policy = aws.ecr.LifecyclePolicy(
    "lifecycle-policy",
    repository=repository.name,
    policy=lifecycle_policy_json,
    opts=pulumi.ResourceOptions(parent=repository),
)