Introducing `pulumi state move`: Move Resources Between Stacks or Projects

Posted on

Today we’re announcing the pulumi state move command, which can be used to move resources that are managed by Pulumi between different stacks and/or projects. With the pulumi state move command, you can refactor your Pulumi Infrastructure as Code without any disruption to your deployed cloud infrastructure, enabling you to evolve and scale with confidence.

When refactoring a Pulumi project from a monolithic structure to micro stacks, you might need to move resources between different projects or stacks, without recreating them. While this has been possible with some significant manual effort to hand-modify Pulumi state files (our users came up with automated solutions for this!), doing so can be error prone and time consuming. Having a simpler, integrated solution for this problem was a much requested feature. The new pulumi state move command, combined with the aliases command for refactoring resources within stacks, offers the richest collection of tools for refactoring IaC at scale available in the industry today.

The pulumi state move command works as follows:

$ pulumi state move --help
Move resources from one stack to another

This command can be used to move resources from one stack to another. This can be useful when
splitting a stack into multiple stacks or when merging multiple stacks into one.

Usage:
  pulumi state move [flags] <urn>...

Flags:
      --dest string       The name of the stack to move resources to
  -h, --help              help for move
      --include-parents   Include all the parents of the moved resources as well
      --source string     The name of the stack to move resources from
  -y, --yes               Automatically approve and perform the move

Both dest and source can be either stacks in the current project, or stacks in a different project, using the fully qualified stack names. Note that this works only for stacks within the same backend, it is currently not possible to move a resource between different backends (though you can move stacks between backends using other existing tools).

The resources being moved have to be specified by their full URN, and multiple URNs can be passed at once. For each resource being moved, all the children of that resource will also be moved, and the relationships between all resources being moved is preserved. Resources with other types of dependencies will however not be moved to the target stack by default. The easiest way to get the full URN of the resources is to use pulumi stack --show-urns. Note that URNs can contain characters that get interpreted by the shell, so it is always a good idea to wrap them in single quotes (') when passing them as arguments.

The pulumi state move command won’t modify the code of your program directly, it will only modify the state of the source and destination stacks. So following running the command, you should ensure that you modify both programs to match the changes you have made. This can typically be accomplished by copy/pasting source code for the resources and/or components between the two codebases. Inputs and outputs of resources that were moved may need to be adjusted as part of this process. This can be done either by using stack references, or recreating the inputs in the program.

Example

To demonstrate how the command works in practice, let’s go through an example. Let’s assume we have the following program:

const randomPet = new random.RandomPet("a-random-pet");

const b = new aws.s3.Bucket("b");

const index = new aws.s3.BucketObject("index.html", {
    bucket: b.bucket,
    content: "Thanks for using Pulumi!",
}, {
    parent: b,
});

const randomSite = new aws.s3.BucketObject("random.html", {
    bucket: b.bucket,
    content: randomPet.id,
}, {
    parent: b,
});

We create a bucket, and two bucket objects, both of them having the bucket as their parent. One of the objects has a random pet name as its content. At some point we decide we’d rather group all the AWS resources in a separate program, because this one grew too large. We can do that using the pulumi state move command, and then adjusting the code.

First we can find out which stacks already exist and we can move the resources to:

$ pulumi stack ls --all
NAME                                            LAST UPDATE     RESOURCE COUNT  URL
v-thomas-pulumi-corp/pulumi-demo-move-aws/dev   23 minutes ago  0               <url>
v-thomas-pulumi-corp/pulumi-demo-move/dev       6 minutes ago   7               <url>

Next we can find the URN of the resources using pulumi stack --show-urns:

$ pulumi stack --show-urns
Current stack is dev:
    Owner: v-thomas-pulumi-corp
    Last updated: 13 seconds ago (2024-07-23 13:52:17.58884965 +0200 CEST)
    Pulumi version used: 3.125.1-dev.0
Current stack resources (7):
    TYPE                                    NAME
    pulumi:pulumi:Stack                     pulumi-demo-move-dev
    │  URN: urn:pulumi:dev::pulumi-demo-move::pulumi:pulumi:Stack::pulumi-demo-move-dev
    ├─ random:index/randomPet:RandomPet     a-random-pet
    │     URN: urn:pulumi:dev::pulumi-demo-move::random:index/randomPet:RandomPet::a-random-pet
    ├─ aws:s3/bucket:Bucket                 b
    │  │  URN: urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket::b
    │  ├─ aws:s3/bucketObject:BucketObject  random.html
    │  │     URN: urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html
    │  └─ aws:s3/bucketObject:BucketObject  index.html
    │        URN: urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::index.html
    ├─ pulumi:providers:random              default_4_16_3
    │     URN: urn:pulumi:dev::pulumi-demo-move::pulumi:providers:random::default_4_16_3
    └─ pulumi:providers:aws                 default_6_45_0
          URN: urn:pulumi:dev::pulumi-demo-move::pulumi:providers:aws::default_6_45_0

Next we can actually move the resources. Since we’re moving the resources from the currently selected stack, we can omit the --source argument:

$ pulumi state move --dest v-thomas-pulumi-corp/pulumi-demo-move-aws/dev 'urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket::b'
Planning to move the following resources from v-thomas-pulumi-corp/pulumi-demo-move/dev to v-thomas-pulumi-corp/pulumi-demo-move-aws/dev:

  - urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket::b
  - urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html
  - urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::index.html

The following resources being moved to v-thomas-pulumi-corp/pulumi-demo-move-aws/dev have dependencies on resources in v-thomas-pulumi-corp/pulumi-demo-move/dev:

  - urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html has a dependency on urn:pulumi:dev::pulumi-demo-move::random:index/randomPet:RandomPet::a-random-pet
  - urn:pulumi:dev::pulumi-demo-move::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html (content) has a property dependency on urn:pulumi:dev::pulumi-demo-move::random:index/randomPet:RandomPet::a-random-pet

If you go ahead with moving these dependencies, it will be necessary to create the appropriate inputs and outputs in the program for the stack the resources are moved to.

Do you want to perform this move? yes
Successfully moved resources from v-thomas-pulumi-corp/pulumi-demo-move/dev to v-thomas-pulumi-corp/pulumi-demo-move-aws/dev

There’s a few things worth explaining above:

  • We’re moving the resources to a different project, so we need to use the fully qualified stack name. If the resources are being moved between different stacks in the same project, the argument can be only the stack name.
  • Before actually moving the resources, the command gives a list of resources that is being moved. Since the bucket objects are children of the bucket, they will be included in the move. However the random pet is not a child resource, so it will not be moved.
  • The command also warns us about dependencies that will not be transferred to the destination stack. Since we didn’t want to include the pet, but the bucket has a dependency on it, pulumi will warn us about that.
  • Before going ahead with the move, we get prompted to confirm whether we want to go ahead with it. In this example we do want to go ahead with the move, so we confirm the move, and the resources will be moved for us.

Now that the resources are moved, we need to adjust the program to take advantage of that. The source program will now look as follows:

const randomPet = new random.RandomPet("a-random-pet");
export const randomPetName = randomPet.id;

Only the random pet remains here. We also export its name, so we can continue using it in the destination program. Meanwhile the destination program is mostly just a copy of the original AWS resources, but there is an important change:

const stackRef = new pulumi.StackReference(`v-thomas-pulumi-com/pulumi-demo-move/dev`)

const b = new aws.s3.Bucket("b");

const index = new aws.s3.BucketObject("index.html", {
    bucket: b.bucket,
    content: "Thanks for using Pulumi!",
}, {
    parent: b,
});

const randomSite = new aws.s3.BucketObject("random.html", {
    bucket: b.bucket,
    content: stackRef.getOutput("randomPetName"),
}, {
    parent: b,
});

Note how we now need to specify the content of random.html, since we no longer have the random pet in the same program. In this case we use a stack reference to reference the output from the source program. It is of course up to the user how to re-create the output. It could also come from config, or be hardcoded depending on the use-case.

As always, we would love to hear your feedback in the community slack. If you encounter any issues with the command, please open an issue.