Move Resources Between Stacks
There are certain scenarios in which you might need to move resources between different project stacks without recreating them, such as when refactoring a Pulumi project from a monolithic structure to micro-stacks. While it is possible to accomplish this by manually modifying Pulumi state files, doing so requires significant effort, can be error prone, and can be very time consuming.
In this tutorial, you will learn how to move your resources using the pulumi state move
command instead.
In this tutorial, you'll learn:
- How to identify a resource's URN
- How to move a resource between stacks
- Why and how to use aliases
Prerequisites:
- A Pulumi Cloud account and access token
- The Pulumi CLI
- An Amazon Web Services account
- The AWS CLI
- Your desired language runtime installed
Create a new project
To start, login to the Pulumi CLI and ensure it is configured to use your AWS account. Next, create a new project and replace the default program code with the following:
const pulumi = require("@pulumi/pulumi");
const random = require("@pulumi/random");
const aws = require("@pulumi/aws");
const petName = new random.RandomPet("my-pet-name");
const bucket = new aws.s3.Bucket("b");
const index = new aws.s3.BucketObject(
"index.html",
{
bucket: bucket.bucket,
content: "Thanks for using Pulumi!",
},
{ parent: bucket },
);
const randomSite = new aws.s3.BucketObject(
"random.html",
{
bucket: bucket.bucket,
content: petName.id,
},
{ parent: bucket },
);
exports.PetName = petName.id;
import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
import * as aws from "@pulumi/aws";
const petName = new random.RandomPet("my-pet-name");
const bucket = new aws.s3.Bucket("b");
const index = new aws.s3.BucketObject(
"index.html",
{
bucket: bucket.bucket,
content: "Thanks for using Pulumi!",
},
{ parent: bucket },
);
const randomSite = new aws.s3.BucketObject(
"random.html",
{
bucket: bucket.bucket,
content: petName.id,
},
{ parent: bucket },
);
export const PetName = petName.id;
import pulumi
import pulumi_random as random
import pulumi_aws as aws
pet_name = random.RandomPet('my-pet-name')
bucket = aws.s3.Bucket("b")
index = aws.s3.BucketObject("index.html",
bucket=bucket.bucket,
content="Thanks for using Pulumi!",
opts=pulumi.ResourceOptions(parent=bucket)
)
random_site = aws.s3.BucketObject("random.html",
bucket=bucket.bucket,
content=pet_name.id,
opts=pulumi.ResourceOptions(parent=bucket)
)
pulumi.export('PetName', pet_name.id)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
"github.com/pulumi/pulumi-random/sdk/v4/go/random"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
petName, err := random.NewRandomPet(ctx, "my-pet-name", nil)
if err != nil {
return err
}
bucket, err := s3.NewBucket(ctx, "b", nil)
if err != nil {
return err
}
_, err = s3.NewBucketObject(ctx, "index.html", &s3.BucketObjectArgs{
Bucket: bucket.ID(),
Content: pulumi.String("Thanks for using Pulumi!"),
}, pulumi.Parent(bucket))
if err != nil {
return err
}
_, err = s3.NewBucketObject(ctx, "random.html", &s3.BucketObjectArgs{
Bucket: bucket.ID(),
Content: petName.ID(),
}, pulumi.Parent(bucket))
if err != nil {
return err
}
ctx.Export("PetName", petName.ID())
return nil
})
}
using Pulumi;
using Pulumi.Aws.S3;
using Pulumi.Random;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var petName = new RandomPet("my-pet-name");
var bucket = new Bucket("b");
var index = new BucketObject("index.html", new BucketObjectArgs
{
Bucket = bucket.BucketName,
Content = "Thanks for using Pulumi!"
}, new CustomResourceOptions { Parent = bucket });
var randomSite = new BucketObject("random.html", new BucketObjectArgs
{
Bucket = bucket.BucketName,
Content = petName.Id
}, new CustomResourceOptions { Parent = bucket });
return new Dictionary<string, object?>
{
["PetName"] = petName.Id
};
});
name: aws-s3bucket-s3objects-random-yaml
runtime: yaml
description: An example that deploys an S3 bucket, S3 bucket objects, and a Pulumi random resource.
resources:
pet-name:
type: random:RandomPet
my-bucket:
type: aws:s3:Bucket
index:
type: aws:s3:BucketObject
properties:
bucket: ${my-bucket.bucket}
content: "Thanks for using Pulumi!"
options:
parent: ${my-bucket}
random-site:
type: aws:s3:BucketObject
properties:
bucket: ${my-bucket.bucket}
content: ${pet-name.id}
options:
parent: ${my-bucket}
outputs:
PetName: ${pet-name.id}
This code example creates the following resources:
- A random object from the Pulumi Random provider
- An S3 bucket resource
- Two S3 bucket objects
It also includes one export that will output the name of the Pulumi random object.
Given that this example program makes use of the Pulumi Random provider, you will also need to make sure to install this dependency into your project. Once that is installed, run the pulumi up
command to deploy your resources before moving onto the next steps.
Review the pulumi state move
command
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 the --dest
and --source
flags can be either stacks in the current project, or stacks in across different projects. The resources being moved have to be specified by their full Uniform Resource Name (URN), and multiple URNs can be passed at once for scenarios in which you need to move multiple resources at once.
Move a resource between stacks
Let’s say you have a situation where your program code has grown too large, and you want to group the AWS resources separately from the Random resource. This means you want to move the S3 bucket and its bucket objects to a separate stack. In this section, you will learn how to move these resource into the state file of a different stack.
Create an additional stack
To start, create a second Pulumi project to where you will move your AWS resources.
From within the original project folder, run the pulumi stack ls --all
command to verify the existence of your stacks:
$ pulumi stack ls
NAME LAST UPDATE RESOURCE COUNT URL
source* 37 minutes ago 7 https://app.pulumi.com/v-torian-pulumi-corp/pulumi-state-move-tutorial/source
v-torian-pulumi-corp/pulumi-state-move-dest/dest-project n/a n/a https://app.pulumi.com/v-torian-pulumi-corp/pulumi-state-move-dest/dest-project
Retrieve fully qualified stack names
Next, you will need to collect the fully qualified stack names of the source and destination stacks. The format of a stack’s fully qualified name is <organization>/<project>/<stack>
. In the URL
column of the output in the above section, you can see the value of each stack’s fully qualified name after the https://app.pulumi.com/
part of the URL. An example of a fully qualified stack name is shown below:
v-torian-pulumi-corp/pulumi-state-move-dest/dest-project
Retrieve resource URN
Next, you will need to obtain the full URN of the S3 bucket resource. To do so, run the pulumi stack --show-urns
command, and copy the value next to the URN
parameter under the aws:s3/bucket:Bucket
resource:
$ pulumi stack --show-urns
Current stack is source:
Owner: v-torian-pulumi-corp
Last updated: 28 seconds ago (2024-08-30 08:07:56.461286624 +0000 UTC)
Pulumi version used: v3.130.0
Current stack resources (7):
TYPE NAME
pulumi:pulumi:Stack pulumi-state-move-tutorial-source
│ URN: urn:pulumi:source::pulumi-state-move-tutorial::pulumi:pulumi:Stack::pulumi-state-move-tutorial-source
├─ random:index/randomPet:RandomPet my-pet-name
│ URN: urn:pulumi:source::pulumi-state-move-tutorial::random:index/randomPet:RandomPet::my-pet-name
├─ aws:s3/bucket:Bucket b
│ │ URN: urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket::b
│ ├─ aws:s3/bucketObject:BucketObject index.html
│ │ URN: urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::index.html
│ └─ aws:s3/bucketObject:BucketObject random.html
│ URN: urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html
├─ pulumi:providers:random default_4_16_3
│ URN: urn:pulumi:source::pulumi-state-move-tutorial::pulumi:providers:random::default_4_16_3
└─ pulumi:providers:aws default_6_50_1
URN: urn:pulumi:source::pulumi-state-move-tutorial::pulumi:providers:aws::default_6_50_1
Current stack outputs (1):
OUTPUT VALUE
PetName main-longhorn
In this scenario, you only need the URN of the S3 bucket resource and not the URNs for the bucket objects. This is because the bucket objects are children of the S3 bucket resource, and when running the pulumi state move
command against resources that have children, all of the children of that resource will also be moved.
Move the resource
Now run the following command, making sure to replace <SOURCE_STACK>
with the fully qualified stack name of the source stack, <DEST_STACK>
with the fully qualified stack name of the destination stack, and <URN>
with the value of the S3 bucket URN.
pulumi state move \
--source <SOURCE_STACK> \
--dest <DEST_STACK> \
<URN>
Since URNs can contain characters that get interpreted by the shell, it is always a good idea to wrap them in single quotes (’) when passing them as arguments as shown in the example below:
$ pulumi state move \
--source v-torian-pulumi-corp/pulumi-state-move-tutorial/source \
--dest v-torian-pulumi-corp/pulumi-state-move-dest/dest-project \
'urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket::b'
Planning to move the following resources from v-torian-pulumi-corp/pulumi-state-move-tutorial/source to v-torian-pulumi-corp/pulumi-state-move-dest/dest-project:
- urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket::b
- urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::index.html
- urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html
The following resources being moved to v-torian-pulumi-corp/pulumi-state-move-dest/dest-project have dependencies on resources in v-torian-pulumi-corp/pulumi-state-move-tutorial/source:
- urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html has a dependency on urn:pulumi:source::pulumi-state-move-tutorial::random:index/randomPet:RandomPet::my-pet-name
- urn:pulumi:source::pulumi-state-move-tutorial::aws:s3/bucket:Bucket$aws:s3/bucketObject:BucketObject::random.html (content) has a property dependency on urn:pulumi:source::pulumi-state-move-tutorial::random:index/randomPet:RandomPet::my-pet-name
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-torian-pulumi-corp/pulumi-state-move-tutorial/source to v-torian-pulumi-corp/pulumi-state-move-dest/dest-project
There are a few things to note about the example above:
- When moving resources from the currently selected stack, you can omit the
--source
argument and use only--dest
argument. - When moving resources between stacks across different projects, you will need to use the fully qualified stack name. When moving resources between stacks within the same project, you can pass just the name of the stack.
- Before the resources are moved, the command gives a list of resources that will be moved. You will notice that even though you have only provided the URN for the S3 bucket as an argument, the command indicates that both the S3 bucket resource as well as the two child bucket objects will be moved.
- The output also warns you about any dependencies that will not be transferred to the destination stack. When dealing with cross-project stacks, you will need to create the appropriate inputs and outputs in the program of the destination stack, and you will learn how to do this in the next section.
Update program code
The pulumi state move
only modifies the state file of the source and destination stacks. It does not modify the code of your program directly. In the case of moving resources between stacks across different programs, you will need to 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 program files.
Additionally, 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 by recreating the inputs in the program.
Update source program code
First, start by removing the AWS resources from your source program code. You can copy and paste these resource definitions safely to the side as you will be adding them to the destination program code in a later step. Update your source program code so that it resembles the following:
const pulumi = require("@pulumi/pulumi");
const random = require("@pulumi/random");
const petName = new random.RandomPet("my-pet-name");
exports.PetName = petName.id;
import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
const petName = new random.RandomPet("my-pet-name");
export const PetName = petName.id;
import pulumi
import pulumi_random as random
pet_name = random.RandomPet('my-pet-name')
pulumi.export('PetName', pet_name.id)
package main
import (
"github.com/pulumi/pulumi-random/sdk/v4/go/random"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
petName, err := random.NewRandomPet(ctx, "my-pet-name", nil)
if err != nil {
return err
}
ctx.Export("PetName", petName.ID())
return nil
})
}
using Pulumi;
using Pulumi.Random;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var petName = new RandomPet("my-pet-name");
return new Dictionary<string, object?>
{
["PetName"] = petName.Id
};
});
name: aws-s3bucket-s3objects-random-yaml
runtime: yaml
description: An example that deploys an S3 bucket, S3 bucket objects, and a Pulumi random resource.
resources:
pet-name:
type: random:RandomPet
outputs:
PetName: ${pet-name.id}
Update destination program code
In your destination program code, you will need to add your AWS resource definitions. Additionally, you will need to add a stack reference to make use of the PetName
export from the source program. Update your destination program code so that it resembles the following, making sure to replace the value of the fully qualified stack name with the name of your own source stack:
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
const stackRef = new pulumi.StackReference(`v-torian-pulumi-corp/pulumi-state-move-tutorial/source`)
const bucket = new aws.s3.Bucket("b");
const index = new aws.s3.BucketObject(
"index.html",
{
bucket: bucket.bucket,
content: "Thanks for using Pulumi!",
},
{ parent: bucket },
);
const randomSite = new aws.s3.BucketObject(
"random.html",
{
bucket: bucket.bucket,
content: stackRef.getOutput("PetName"),
},
{ parent: bucket },
);
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const stackRef = new pulumi.StackReference(`v-torian-pulumi-corp/pulumi-state-move-tutorial/source`)
const bucket = new aws.s3.Bucket("b");
const index = new aws.s3.BucketObject(
"index.html",
{
bucket: bucket.bucket,
content: "Thanks for using Pulumi!",
},
{ parent: bucket },
);
const randomSite = new aws.s3.BucketObject(
"random.html",
{
bucket: bucket.bucket,
content: stackRef.getOutput("PetName"),
},
{ parent: bucket },
);
import pulumi
import pulumi_aws as aws
stack_ref = pulumi.StackReference("v-torian-pulumi-corp/pulumi-state-move-tutorial/source")
bucket = aws.s3.Bucket("b")
index = aws.s3.BucketObject("index.html",
bucket=bucket.bucket,
content="Thanks for using Pulumi!",
opts=pulumi.ResourceOptions(parent=bucket)
)
random_site = aws.s3.BucketObject("random.html",
bucket=bucket.bucket,
content=stack_ref.get_output("PetName")
opts=pulumi.ResourceOptions(parent=bucket)
)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
stackRef, err := pulumi.NewStackReference(ctx, "v-torian-pulumi-corp/pulumi-state-move-tutorial/source", nil)
if err != nil {
return err
}
bucket, err := s3.NewBucket(ctx, "b", nil)
if err != nil {
return err
}
_, err = s3.NewBucketObject(ctx, "index.html", &s3.BucketObjectArgs{
Bucket: bucket.ID(),
Content: pulumi.String("Thanks for using Pulumi!"),
}, pulumi.Parent(bucket))
if err != nil {
return err
}
_, err = s3.NewBucketObject(ctx, "random.html", &s3.BucketObjectArgs{
Bucket: bucket.ID(),
Content: stackRef.GetOutput("PetName"),
}, pulumi.Parent(bucket))
if err != nil {
return err
}
})
}
using Pulumi;
using Pulumi.Aws.S3;
using System.Collections.Generic;
return await Deployment.RunAsync(() =>
{
var stackRef = new StackReference("v-torian-pulumi-corp/pulumi-state-move-tutorial/source");
var bucket = new Bucket("b");
var index = new BucketObject("index.html", new BucketObjectArgs
{
Bucket = bucket.BucketName,
Content = "Thanks for using Pulumi!"
}, new CustomResourceOptions { Parent = bucket });
var randomSite = new BucketObject("random.html", new BucketObjectArgs
{
Bucket = bucket.BucketName,
Content = stackRef.GetOutput("PetName")
}, new CustomResourceOptions { Parent = bucket });
});
name: aws-s3bucket-s3objects-random-yaml
runtime: yaml
resources:
stack-ref:
type: pulumi:pulumi:StackReference
properties:
name: v-torian-pulumi-corp/pulumi-state-move-tutorial/source
my-bucket:
type: aws:s3:Bucket
index:
type: aws:s3:BucketObject
properties:
bucket: ${my-bucket.bucket}
content: "Thanks for using Pulumi!"
options:
parent: ${my-bucket}
random-site:
type: aws:s3:BucketObject
properties:
bucket: ${my-bucket.bucket}
content: ${stack-ref.outputs["PetName"]}
options:
parent: ${my-bucket}
Clean-up
Before moving on, tear down the resources that are part of your stack to avoid incurring any charges.
- Run
pulumi destroy
to tear down all resources. You'll be prompted to make sure you really want to delete these resources. A destroy operation may take some time, since Pulumi waits for the resources to finish shutting down before it considers the destroy operation to be complete. - To delete the stack itself, run
pulumi stack rm
. Note that this command deletes all deployment history from the Pulumi Service.
Next steps
In this tutorial, you created a Pulumi Random resource and AWS S3 resources. You used the pulumi state move
command to migrate the AWS resources from the state file of a source project to a destination project. You also updated both the source and destination project code to match the changes made.
To learn more about creating and managing resources in Pulumi, take a look a the following resources:
- Learn more about Pulumi state and backends in the Managing Pulumi State and Backend Options documentation.
- Learn more about the
pulumi state
command and its subcommands in the Pulumi State CLI documentation. - Learn more about Pulumi stacks in the Stacks concept documentation.
- Learn more about the
pulumi stack
command and its subcommands in the Pulumi Stack CLI documentation.