diff --git a/CHANGELOG.md b/CHANGELOG.md index 823084039..66ba500f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ CHANGELOG - Fetch version information from the Homebrew JSON API for CLIs installed using `brew`. [#3290](https://github.com/pulumi/pulumi/pull/3290) +- Support renaming stack projects via `pulumi stack rename`. + [#3292](https://github.com/pulumi/pulumi/pull/3292) + ## 1.2.0 (2019-09-26) - Support emitting high-level execution trace data to a file and add a debug-only command to view trace data. diff --git a/cmd/stack_rename.go b/cmd/stack_rename.go index 835537443..ab9621632 100644 --- a/cmd/stack_rename.go +++ b/cmd/stack_rename.go @@ -41,7 +41,11 @@ func newStackRenameCmd() *cobra.Command { "Note: Because renaming a stack will change the value of `getStack()` inside a Pulumi program, if this\n" + "name is used as part of a resource's name, the next `pulumi up` will want to delete the old resource and\n" + "create a new copy. For now, if you don't want these changes to be applied, you should rename your stack\n" + - "back to its previous name.", + "back to its previous name." + + "\n" + + "You can also rename the stack's project by passing a fully-qualified stack name as well. For example:\n" + + "'robot-co/new-project-name/production'. However in order to update the stack again, you would also need\n" + + "to update the name field of Pulumi.yaml, so the project names match.", Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { opts := display.Options{ Color: cmdutil.GetGlobalColorization(), @@ -66,10 +70,20 @@ func newStackRenameCmd() *cobra.Command { return err } - if err := os.Rename(oldConfigPath, newConfigPath); err != nil { - return errors.Wrapf(err, "renaming %s to %s", filepath.Base(oldConfigPath), filepath.Base(newConfigPath)) + // Move the configuration data stored in Pulumi..yaml. + _, configStatErr := os.Stat(oldConfigPath) + switch { + case os.IsNotExist(configStatErr): + // Stack doesn't have any configuration, ignore. + case configStatErr == nil: + if err := os.Rename(oldConfigPath, newConfigPath); err != nil { + return errors.Wrapf(err, "renaming configuration file to %s", filepath.Base(newConfigPath)) + } + default: + return errors.Wrapf(err, "checking current configuration file %v", oldConfigPath) } + // Update the current workspace state to have selected the new stack. if err := state.SetCurrentStack(args[0]); err != nil { return errors.Wrap(err, "setting current stack") } diff --git a/pkg/backend/filestate/backend.go b/pkg/backend/filestate/backend.go index 178014d41..a06b9bca6 100644 --- a/pkg/backend/filestate/backend.go +++ b/pkg/backend/filestate/backend.go @@ -333,7 +333,7 @@ func (b *localBackend) RenameStack(ctx context.Context, stackRef backend.StackRe } } - // Now save the snapshot with a new name (we pass nil to re-use the existing secrets manager from the snapshot) + // Now save the snapshot with a new name (we pass nil to re-use the existing secrets manager from the snapshot). if _, err = b.saveStack(newName, snap, nil); err != nil { return err } diff --git a/pkg/backend/httpstate/backend.go b/pkg/backend/httpstate/backend.go index 2238b225b..bea71adf8 100644 --- a/pkg/backend/httpstate/backend.go +++ b/pkg/backend/httpstate/backend.go @@ -625,7 +625,27 @@ func (b *cloudBackend) RenameStack(ctx context.Context, stackRef backend.StackRe return err } - return b.client.RenameStack(ctx, stack, string(newName)) + // Support a qualified stack name, which would also rename the stack's project too. + // e.g. if you want to change the project name on the Pulumi Console to reflect a + // new value in Pulumi.yaml. + newRef, err := b.ParseStackReference(string(newName)) + if err != nil { + return err + } + newIdentity, err := b.getCloudStackIdentifier(newRef) + if err != nil { + return err + } + + // Transferring stack ownership is available to Pulumi admins, but isn't quite ready + // for general use yet. + if stack.Owner != newIdentity.Owner { + errMsg := "You cannot transfer stack ownership via a rename. If you wish to transfer ownership\n" + + "of a stack to another organization, please contact support@pulumi.com." + return errors.Errorf(errMsg) + } + + return b.client.RenameStack(ctx, stack, newIdentity) } func getStack(ctx context.Context, b *cloudBackend, stackRef backend.StackReference) (backend.Stack, error) { diff --git a/pkg/backend/httpstate/client/client.go b/pkg/backend/httpstate/client/client.go index bfa787ff5..4aea2b4f7 100644 --- a/pkg/backend/httpstate/client/client.go +++ b/pkg/backend/httpstate/client/client.go @@ -441,13 +441,15 @@ func (pc *Client) CreateUpdate( }, updateResponse.RequiredPolicies, nil } -func (pc *Client) RenameStack(ctx context.Context, stack StackIdentifier, newName string) error { +// RenameStack renames the provided stack to have the new identifier. +func (pc *Client) RenameStack(ctx context.Context, currentID, newID StackIdentifier) error { req := apitype.StackRenameRequest{ - NewName: newName, + NewName: newID.Stack, + NewProject: newID.Project, } var resp apitype.ImportStackResponse - return pc.restCall(ctx, "POST", getStackPath(stack, "rename"), nil, &req, &resp) + return pc.restCall(ctx, "POST", getStackPath(currentID, "rename"), nil, &req, &resp) } // StartUpdate starts the indicated update. It returns the new version of the update's target stack and the token used