mirror of https://github.com/pulumi/pulumi.git
250 lines
8.1 KiB
Go
250 lines
8.1 KiB
Go
// Copyright 2016-2022, 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 main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/backend/display"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/edit"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func updateDependencies(dependencies []resource.URN, oldUrn resource.URN, newUrn resource.URN) []resource.URN {
|
|
var updatedDependencies []resource.URN
|
|
for _, dependency := range dependencies {
|
|
if dependency == oldUrn {
|
|
// replace old URN with new URN
|
|
updatedDependencies = append(updatedDependencies, newUrn)
|
|
} else {
|
|
updatedDependencies = append(updatedDependencies, dependency)
|
|
}
|
|
}
|
|
return updatedDependencies
|
|
}
|
|
|
|
// stateReurnOperation changes the URN for a resource and mutates/rewrites references to it in the snapshot.
|
|
func stateReurnOperation(
|
|
oldURN resource.URN, newURN resource.URN, opts display.Options, snap *deploy.Snapshot,
|
|
) error {
|
|
contract.Requiref(oldURN != "", "oldURN", "must not be empty")
|
|
contract.Requiref(newURN != "", "newURN", "must not be empty")
|
|
|
|
// Check whether the input URN corresponds to an existing resource
|
|
existingResources := edit.LocateResource(snap, oldURN)
|
|
if len(existingResources) != 1 {
|
|
return errors.New("The input URN does not correspond to an existing resource")
|
|
}
|
|
|
|
// If the URN hasn't changed then there's nothing to do.
|
|
if oldURN == newURN {
|
|
return nil
|
|
}
|
|
|
|
inputResource := existingResources[0]
|
|
contract.Assertf(inputResource.URN == oldURN, "The input resource does not match the input URN")
|
|
// Check whether the new URN _does not_ correspond to an existing resource
|
|
candidateResources := edit.LocateResource(snap, newURN)
|
|
if len(candidateResources) > 0 {
|
|
return errors.New("The chosen new urn for the state corresponds to an already existing resource")
|
|
}
|
|
|
|
// Update the URN of the input resource
|
|
inputResource.URN = newURN
|
|
// Update the dependants of the input resource
|
|
for _, existingResource := range snap.Resources {
|
|
// update resources other than the input resource
|
|
if existingResource.URN != inputResource.URN {
|
|
// Update dependencies
|
|
existingResource.Dependencies = updateDependencies(existingResource.Dependencies, oldURN, newURN)
|
|
// Update property dependencies
|
|
for property, dependencies := range existingResource.PropertyDependencies {
|
|
existingResource.PropertyDependencies[property] = updateDependencies(dependencies, oldURN, newURN)
|
|
}
|
|
|
|
// Update deleted with relationships, if any.
|
|
if existingResource.DeletedWith == oldURN {
|
|
existingResource.DeletedWith = newURN
|
|
}
|
|
|
|
// Update parent, if any.
|
|
if existingResource.Parent == oldURN {
|
|
existingResource.Parent = newURN
|
|
// We also need to update this resources URN now
|
|
oldChildURN := existingResource.URN
|
|
newChildURN := resource.NewURN(
|
|
oldChildURN.Stack(), oldChildURN.Project(),
|
|
newURN.QualifiedType(), oldChildURN.Type(),
|
|
oldChildURN.Name())
|
|
err := stateReurnOperation(oldChildURN, newChildURN, opts, snap)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update %s with new parent %s: %w", oldChildURN, newURN, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
updateProvider := func(newRef providers.Reference) error {
|
|
// Loop through all resources and rename references to the provider.
|
|
for _, curResource := range snap.Resources {
|
|
|
|
if curResource.Provider == "" {
|
|
// Skip resources that don't use a provider.
|
|
continue
|
|
}
|
|
curResourceProviderRef, err := providers.ParseReference(curResource.Provider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Skip resources that don't use the renamed provider.
|
|
if curResourceProviderRef.URN() != oldURN {
|
|
continue
|
|
}
|
|
|
|
// Update the provider.
|
|
curResource.Provider = newRef.String()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// If the renamed resource is a Provider, fix all resources referring to the old name.
|
|
if providers.IsProviderType(inputResource.Type) {
|
|
newRef, err := providers.NewReference(newURN, inputResource.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return updateProvider(newRef)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// stateRenameOperation renames a resource (or provider) and mutates/rewrites references to it in the snapshot.
|
|
func stateRenameOperation(
|
|
urn resource.URN, newResourceName tokens.QName, opts display.Options, snap *deploy.Snapshot,
|
|
) error {
|
|
contract.Assertf(tokens.IsQName(string(newResourceName)),
|
|
"QName must be valid")
|
|
// update the URN with only the name part changed
|
|
newUrn := urn.Rename(string(newResourceName))
|
|
return stateReurnOperation(urn, newUrn, opts, snap)
|
|
}
|
|
|
|
//nolint:lll
|
|
func newStateRenameCommand() *cobra.Command {
|
|
var stack string
|
|
var yes bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "rename [resource URN] [new name]",
|
|
Short: "Renames a resource from a stack's state",
|
|
Long: `Renames a resource from a stack's state
|
|
|
|
This command renames a resource from a stack's state. The resource is specified
|
|
by its Pulumi URN and the new name of the resource.
|
|
|
|
Make sure that URNs are single-quoted to avoid having characters unexpectedly interpreted by the shell.
|
|
|
|
To see the list of URNs in a stack, use ` + "`pulumi stack --show-urns`" + `.
|
|
`,
|
|
Example: "pulumi state rename 'urn:pulumi:stage::demo::eks:index:Cluster$pulumi:providers:kubernetes::eks-provider' new-name-here",
|
|
Args: cmdutil.MaximumNArgs(2),
|
|
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
|
ctx := cmd.Context()
|
|
yes = yes || skipConfirmations()
|
|
|
|
if len(args) < 2 && !cmdutil.Interactive() {
|
|
return missingNonInteractiveArg("resource URN", "new name")
|
|
}
|
|
|
|
var urn resource.URN
|
|
var newResourceName tokens.QName
|
|
switch len(args) {
|
|
case 0: // We got neither the URN nor the name.
|
|
var snap *deploy.Snapshot
|
|
err := surveyStack(
|
|
func() (err error) {
|
|
urn, err = getURNFromState(ctx, stack, &snap, "Select a resource to rename:")
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to select resource: %w", err)
|
|
}
|
|
return
|
|
},
|
|
func() (err error) {
|
|
newResourceName, err = getNewResourceName()
|
|
return
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case 1: // We got the urn but not the name
|
|
urn = resource.URN(args[0])
|
|
if !urn.IsValid() {
|
|
return errors.New("The provided input URN is not valid")
|
|
}
|
|
var err error
|
|
newResourceName, err = getNewResourceName()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case 2: // We got the URN and the name.
|
|
urn = resource.URN(args[0])
|
|
if !urn.IsValid() {
|
|
return errors.New("The provided input URN is not valid")
|
|
}
|
|
rName := args[1]
|
|
if !tokens.IsQName(rName) {
|
|
reason := "resource names may only contain alphanumerics, underscores, hyphens, dots, and slashes"
|
|
return fmt.Errorf("invalid name %q: %s", rName, reason)
|
|
}
|
|
newResourceName = tokens.QName(rName)
|
|
}
|
|
|
|
// Show the confirmation prompt if the user didn't pass the --yes parameter to skip it.
|
|
showPrompt := !yes
|
|
|
|
err := runTotalStateEdit(ctx, stack, showPrompt,
|
|
func(opts display.Options, snap *deploy.Snapshot) error {
|
|
return stateRenameOperation(urn, newResourceName, opts, snap)
|
|
})
|
|
if err != nil {
|
|
// an error occurred
|
|
// return it
|
|
return err
|
|
}
|
|
|
|
fmt.Println("Resource renamed")
|
|
return nil
|
|
}),
|
|
}
|
|
|
|
cmd.PersistentFlags().StringVarP(
|
|
&stack, "stack", "s", "",
|
|
"The name of the stack to operate on. Defaults to the current stack")
|
|
|
|
cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip confirmation prompts")
|
|
return cmd
|
|
}
|