Add pulumi stack rename
`pulumi stack rename` allows you to change the name of an existing stack. This operation is non-distructive, however it is possible that the next update will show additional changes to resources, if the pulumi program uses the value of `getStack()` as part of a resource name.
This commit is contained in:
parent
0a4bf7b991
commit
a1bb16407d
|
@ -2,6 +2,8 @@
|
|||
|
||||
### Improvements
|
||||
|
||||
- A new command, `pulumi stack rename` was added. This allows you to change the name of an existing stack in a project. Note: When a stack is renamed, the `pulumi.getStack` function in the SDK will now return a new value. If a stack name is used as part of a resource name, the next `pulumi update` will not understand that the old and new resources are logically the same. We plan to support adding aliases to individual resources so you can handle these cases. See [pulumi/pulumi#458](https://github.com/pulumi/pulumi/issues/458) for discussion on this new feature. For now, if you are unwilling to have `pulumi update` create and destroy these resources, you can rename your stack back to the old name. (fixes [pulumi/pulumi#2402](https://github.com/pulumi/pulumi/issues/2402))
|
||||
|
||||
## 0.17.2 (Released March 15, 2019)
|
||||
|
||||
### Improvements
|
||||
|
|
|
@ -174,6 +174,7 @@ func newStackCmd() *cobra.Command {
|
|||
cmd.AddCommand(newStackRmCmd())
|
||||
cmd.AddCommand(newStackSelectCmd())
|
||||
cmd.AddCommand(newStackTagCmd())
|
||||
cmd.AddCommand(newStackRenameCmd())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
86
cmd/stack_rename.go
Normal file
86
cmd/stack_rename.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2016-2018, Pulumi Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/backend/display"
|
||||
"github.com/pulumi/pulumi/pkg/backend/state"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
"github.com/pulumi/pulumi/pkg/workspace"
|
||||
)
|
||||
|
||||
func newStackRenameCmd() *cobra.Command {
|
||||
var stack string
|
||||
var cmd = &cobra.Command{
|
||||
Use: "rename <new-stack-name>",
|
||||
Args: cmdutil.ExactArgs(1),
|
||||
Short: "Rename an existing stack",
|
||||
Long: "Rename an existing stack.\n" +
|
||||
"\n" +
|
||||
"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.",
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
opts := display.Options{
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
}
|
||||
|
||||
s, err := requireStack(stack, false, opts, true /*setCurrent*/)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldConfigPath, err := workspace.DetectProjectStackPath(s.Ref().Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newConfigPath, err := workspace.DetectProjectStackPath(tokens.QName(args[0]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Rename(commandContext(), tokens.QName(args[0])); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(oldConfigPath, newConfigPath); err != nil {
|
||||
return errors.Wrapf(err, "renaming %s to %s", filepath.Base(oldConfigPath), filepath.Base(newConfigPath))
|
||||
}
|
||||
|
||||
if err := state.SetCurrentStack(args[0]); err != nil {
|
||||
return errors.Wrap(err, "setting current stack")
|
||||
}
|
||||
|
||||
fmt.Printf("Renamed %s\n", s.Ref().String())
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(
|
||||
&stack, "stack", "s", "",
|
||||
"The name of the stack to operate on. Defaults to the current stack")
|
||||
return cmd
|
||||
}
|
|
@ -96,6 +96,8 @@ type Backend interface {
|
|||
// ListStacks returns a list of stack summaries for all known stacks in the target backend.
|
||||
ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]StackSummary, error)
|
||||
|
||||
RenameStack(ctx context.Context, stackRef StackReference, newName tokens.QName) error
|
||||
|
||||
// GetStackCrypter returns an encrypter/decrypter for the given stack's secret config values.
|
||||
GetStackCrypter(stackRef StackReference) (config.Crypter, error)
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/pulumi/pulumi/pkg/operations"
|
||||
"github.com/pulumi/pulumi/pkg/resource/config"
|
||||
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
||||
"github.com/pulumi/pulumi/pkg/resource/edit"
|
||||
"github.com/pulumi/pulumi/pkg/resource/stack"
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
|
@ -214,6 +215,41 @@ func (b *localBackend) RemoveStack(ctx context.Context, stackRef backend.StackRe
|
|||
return false, b.removeStack(stackName)
|
||||
}
|
||||
|
||||
func (b *localBackend) RenameStack(ctx context.Context, stackRef backend.StackReference, newName tokens.QName) error {
|
||||
stackName := stackRef.Name()
|
||||
cfg, snap, _, err := b.getStack(stackName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the destination stack does not already exist.
|
||||
_, err = os.Stat(b.stackPath(newName))
|
||||
if err == nil {
|
||||
return errors.Errorf("a stack named %s already exists", newName)
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rewrite the checkpoint and save it with the new name.
|
||||
if err = edit.RenameStack(snap, newName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = b.saveStack(newName, cfg, snap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// To remove the old stack, just make a backup of the file and don't write out anything new.
|
||||
file := b.stackPath(stackName)
|
||||
backupTarget(file)
|
||||
|
||||
// And move the history over as well.
|
||||
oldHistoryDir := b.historyDirectory(stackName)
|
||||
newHistoryDir := b.historyDirectory(newName)
|
||||
|
||||
return os.Rename(oldHistoryDir, newHistoryDir)
|
||||
}
|
||||
|
||||
func (b *localBackend) GetStackCrypter(stackRef backend.StackReference) (config.Crypter, error) {
|
||||
return symmetricCrypter(stackRef.Name(), b.stackConfigFile)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/backend"
|
||||
"github.com/pulumi/pulumi/pkg/engine"
|
||||
|
@ -62,6 +64,10 @@ func (s *localStack) Remove(ctx context.Context, force bool) (bool, error) {
|
|||
return backend.RemoveStack(ctx, s, force)
|
||||
}
|
||||
|
||||
func (s *localStack) Rename(ctx context.Context, newName tokens.QName) error {
|
||||
return backend.RenameStack(ctx, s, newName)
|
||||
}
|
||||
|
||||
func (s *localStack) Preview(ctx context.Context, op backend.UpdateOperation) (engine.ResourceChanges, error) {
|
||||
return backend.PreviewStack(ctx, s, op)
|
||||
}
|
||||
|
|
|
@ -591,6 +591,15 @@ func (b *cloudBackend) RemoveStack(ctx context.Context, stackRef backend.StackRe
|
|||
return b.client.DeleteStack(ctx, stack, force)
|
||||
}
|
||||
|
||||
func (b *cloudBackend) RenameStack(ctx context.Context, stackRef backend.StackReference, newName tokens.QName) error {
|
||||
stack, err := b.getCloudStackIdentifier(stackRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.client.RenameStack(ctx, stack, string(newName))
|
||||
}
|
||||
|
||||
// cloudCrypter is an encrypter/decrypter that uses the Pulumi cloud to encrypt/decrypt a stack's secrets.
|
||||
type cloudCrypter struct {
|
||||
backend *cloudBackend
|
||||
|
|
|
@ -411,6 +411,15 @@ func (pc *Client) CreateUpdate(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (pc *Client) RenameStack(ctx context.Context, stack StackIdentifier, newName string) error {
|
||||
req := apitype.StackRenameRequest{
|
||||
NewName: newName,
|
||||
}
|
||||
var resp apitype.ImportStackResponse
|
||||
|
||||
return pc.restCall(ctx, "POST", getStackPath(stack, "rename"), nil, &req, &resp)
|
||||
}
|
||||
|
||||
// StartUpdate starts the indicated update. It returns the new version of the update's target stack and the token used
|
||||
// to authenticate operations on the update if any. Replaces the stack's tags with the updated set.
|
||||
func (pc *Client) StartUpdate(ctx context.Context, update UpdateIdentifier,
|
||||
|
|
|
@ -127,6 +127,10 @@ func (s *cloudStack) Remove(ctx context.Context, force bool) (bool, error) {
|
|||
return backend.RemoveStack(ctx, s, force)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Rename(ctx context.Context, newName tokens.QName) error {
|
||||
return backend.RenameStack(ctx, s, newName)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Preview(ctx context.Context, op backend.UpdateOperation) (engine.ResourceChanges, error) {
|
||||
return backend.PreviewStack(ctx, s, op)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/tokens"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -50,6 +52,8 @@ type Stack interface {
|
|||
|
||||
// remove this stack.
|
||||
Remove(ctx context.Context, force bool) (bool, error)
|
||||
// rename this stack.
|
||||
Rename(ctx context.Context, newName tokens.QName) error
|
||||
// list log entries for this stack.
|
||||
GetLogs(ctx context.Context, query operations.LogQuery) ([]operations.LogEntry, error)
|
||||
// export this stack's deployment.
|
||||
|
@ -63,6 +67,10 @@ func RemoveStack(ctx context.Context, s Stack, force bool) (bool, error) {
|
|||
return s.Backend().RemoveStack(ctx, s.Ref(), force)
|
||||
}
|
||||
|
||||
func RenameStack(ctx context.Context, s Stack, newName tokens.QName) error {
|
||||
return s.Backend().RenameStack(ctx, s.Ref(), newName)
|
||||
}
|
||||
|
||||
// PreviewStack previews changes to this stack.
|
||||
func PreviewStack(ctx context.Context, s Stack, op UpdateOperation) (engine.ResourceChanges, error) {
|
||||
return s.Backend().Preview(ctx, s.Ref(), op)
|
||||
|
|
Loading…
Reference in a new issue