Skip to content

Commit

Permalink
rewrite the URN when resources are being moved between projects (#16523)
Browse files Browse the repository at this point in the history
When a resource is moved between stacks in different projects we also
need to rewrite project part of the URNs. Do that here. Moving between
different projects already works by virtue of the requireStack function
supporting that when providing the fully qualified name of the stack.

There is some awkwardness here as old diy backends may not return a
project name, in which case we error out here. Curious if anyone has
thought about what to do here. Is erroring out the best we can do? What
happens if we don't rewrite the project name in this case?

---------

Co-authored-by: Will Jones <[email protected]>
  • Loading branch information
tgummerer and lunaris authored Jul 1, 2024
1 parent a9621cf commit 2f4b26b
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 14 deletions.
57 changes: 46 additions & 11 deletions pkg/cmd/pulumi/state_move.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,13 @@ EXPERIMENTAL: this feature is currently in development.
if sourceStackName == "" && destStackName == "" {
return errors.New("at least one of --source or --dest must be provided")
}
// TODO: make sure to load the source stack even if it is from a different project
sourceStack, err := requireStack(ctx, sourceStackName, stackLoadOnly, display.Options{
Color: cmdutil.GetGlobalColorization(),
IsInteractive: true,
})
if err != nil {
return err
}
// TODO: make sure to load the dest stack even if it is from a different project.
destStack, err := requireStack(ctx, destStackName, stackLoadOnly, display.Options{
Color: cmdutil.GetGlobalColorization(),
IsInteractive: true,
Expand Down Expand Up @@ -152,13 +150,19 @@ func (cmd *stateMoveCmd) Run(
// Providers stay in the source stack, so we need a copy of the provider to be able to
// rewrite the URNs of the resource.
r := res.Copy()
rewriteURNs(r, dest)
err = rewriteURNs(r, dest)
if err != nil {
return err
}
destSnapshot.Resources = append(destSnapshot.Resources, r)
}

for _, res := range resourcesToMove {
breakDependencies(res, remainingResources)
rewriteURNs(res, dest)
err = rewriteURNs(res, dest)
if err != nil {
return err
}
if _, ok := resourcesToMove[string(res.Parent)]; !ok {
rootStack, err := stack.GetRootStackResource(destSnapshot)
if err != nil {
Expand Down Expand Up @@ -225,21 +229,52 @@ func breakDependencies(res *resource.State, resourcesToMove map[string]*resource
}
}

func rewriteURNs(res *resource.State, dest backend.Stack) {
// TODO: rewrite project name
res.URN = res.URN.RenameStack(dest.Ref().Name())
func renameStackAndProject(urn urn.URN, stack backend.Stack) (urn.URN, error) {
newURN := urn.RenameStack(stack.Ref().Name())
if project, ok := stack.Ref().Project(); ok {
newURN = newURN.RenameProject(tokens.PackageName(project))
} else {
return "", errors.New("cannot get project name. " +
"Please upgrade your project with `pulumi state upgrade` to solve this.")
}
return newURN, nil
}

func rewriteURNs(res *resource.State, dest backend.Stack) error {
var err error
res.URN, err = renameStackAndProject(res.URN, dest)
if err != nil {
return err
}
if res.Provider != "" {
res.Provider = string(urn.URN(res.Provider).RenameStack(dest.Ref().Name()))
providerURN, err := renameStackAndProject(urn.URN(res.Provider), dest)
if err != nil {
return err
}
res.Provider = string(providerURN)
}
for k, dep := range res.Dependencies {
res.Dependencies[k] = dep.RenameStack(dest.Ref().Name())
depURN, err := renameStackAndProject(dep, dest)
if err != nil {
return err
}
res.Dependencies[k] = depURN
}
for k, propDeps := range res.PropertyDependencies {
for j, propDep := range propDeps {
res.PropertyDependencies[k][j] = propDep.RenameStack(dest.Ref().Name())
depURN, err := renameStackAndProject(propDep, dest)
if err != nil {
return err
}
res.PropertyDependencies[k][j] = depURN
}
}
if res.DeletedWith != "" {
res.DeletedWith = res.DeletedWith.RenameStack(dest.Ref().Name())
urn, err := renameStackAndProject(res.DeletedWith, dest)
if err != nil {
return err
}
res.DeletedWith = urn
}
return nil
}
24 changes: 21 additions & 3 deletions sdk/go/common/resource/urn/urn.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,7 @@ func (urn URN) Rename(newName string) URN {
return New(
urn.Stack(),
urn.Project(),
// parent type is empty because
// assuming the qualified type already includes it
// parent type is empty because the qualified type already includes it
"",
urn.QualifiedType(),
newName,
Expand All @@ -201,5 +200,24 @@ func (urn URN) Rename(newName string) URN {

// Returns a new URN with an updated stack part
func (urn URN) RenameStack(stack tokens.StackName) URN {
return New(stack.Q(), urn.Project(), urn.QualifiedType(), urn.Type(), urn.Name())
return New(
stack.Q(),
urn.Project(),
// parent type is empty because the qualified type already includes it
"",
urn.QualifiedType(),
urn.Name(),
)
}

// Returns a new URN with an updated project part
func (urn URN) RenameProject(project tokens.PackageName) URN {
return New(
urn.Stack(),
project,
// parent type is empty because the qualified type already includes it
"",
urn.QualifiedType(),
urn.Name(),
)
}
36 changes: 36 additions & 0 deletions sdk/go/common/resource/urn/urn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,39 @@ func TestRename(t *testing.T) {
urn.New(stack, proj, parentType, typ, "a-better-resource"),
renamed)
}

func TestRenameStack(t *testing.T) {
t.Parallel()

stack := tokens.QName("stack")
proj := tokens.PackageName("foo/bar/baz")
parentType := tokens.Type("parent$type")
typ := tokens.Type("bang:boom/fizzle:MajorResource")
name := "a-swell-resource"

oldURN := urn.New(stack, proj, parentType, typ, name)
renamed := oldURN.RenameStack(tokens.MustParseStackName("a-better-stack"))

assert.NotEqual(t, oldURN, renamed)
assert.Equal(t,
urn.New("a-better-stack", proj, parentType, typ, name),
renamed)
}

func TestRenameProject(t *testing.T) {
t.Parallel()

stack := tokens.QName("stack")
proj := tokens.PackageName("foo/bar/baz")
parentType := tokens.Type("parent$type")
typ := tokens.Type("bang:boom/fizzle:MajorResource")
name := "a-swell-resource"

oldURN := urn.New(stack, proj, parentType, typ, name)
renamed := oldURN.RenameProject(tokens.PackageName("a-better-project"))

assert.NotEqual(t, oldURN, renamed)
assert.Equal(t,
urn.New(stack, "a-better-project", parentType, typ, name),
renamed)
}

0 comments on commit 2f4b26b

Please sign in to comment.