mirror of https://github.com/pulumi/pulumi.git
608 lines
19 KiB
Go
608 lines
19 KiB
Go
// Copyright 2024-2024, 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 (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
mapset "github.com/deckarep/golang-set/v2"
|
|
"github.com/pulumi/pulumi/pkg/v3/backend"
|
|
"github.com/pulumi/pulumi/pkg/v3/backend/display"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/graph"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/stack"
|
|
"github.com/pulumi/pulumi/pkg/v3/secrets"
|
|
pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/urn"
|
|
"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/workspace"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type stateMoveCmd struct {
|
|
Stdin io.Reader
|
|
Stdout io.Writer
|
|
Colorizer colors.Colorization
|
|
Yes bool
|
|
IncludeParents bool
|
|
|
|
ws pkgWorkspace.Context
|
|
}
|
|
|
|
func newStateMoveCommand() *cobra.Command {
|
|
var sourceStackName string
|
|
var destStackName string
|
|
var yes bool
|
|
var includeParents bool
|
|
stateMove := &stateMoveCmd{
|
|
Colorizer: cmdutil.GetGlobalColorization(),
|
|
}
|
|
cmd := &cobra.Command{
|
|
Use: "move [flags] <urn>...",
|
|
Short: "Move resources from one stack to another",
|
|
Long: `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.
|
|
`,
|
|
Args: cmdutil.MinimumNArgs(1),
|
|
Run: runCmdFunc(func(cmd *cobra.Command, args []string) error {
|
|
ctx := cmd.Context()
|
|
ws := pkgWorkspace.Instance
|
|
|
|
if sourceStackName == "" && destStackName == "" {
|
|
return errors.New("at least one of --source or --dest must be provided")
|
|
}
|
|
sourceStack, err := requireStack(ctx, ws, sourceStackName, stackLoadOnly, display.Options{
|
|
Color: cmdutil.GetGlobalColorization(),
|
|
IsInteractive: true,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destStack, err := requireStack(ctx, ws, destStackName, stackLoadOnly, display.Options{
|
|
Color: cmdutil.GetGlobalColorization(),
|
|
IsInteractive: true,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stateMove.Yes = yes
|
|
stateMove.IncludeParents = includeParents
|
|
|
|
sourceSecretsProvider := stack.NamedStackSecretsProvider{
|
|
StackName: sourceStack.Ref().FullyQualifiedName().String(),
|
|
}
|
|
destSecretsProvider := stack.NamedStackSecretsProvider{
|
|
StackName: destStack.Ref().FullyQualifiedName().String(),
|
|
}
|
|
|
|
return stateMove.Run(ctx, sourceStack, destStack, args, sourceSecretsProvider, destSecretsProvider)
|
|
}),
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&sourceStackName, "source", "", "", "The name of the stack to move resources from")
|
|
cmd.Flags().StringVarP(&destStackName, "dest", "", "", "The name of the stack to move resources to")
|
|
cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Automatically approve and perform the move")
|
|
cmd.Flags().BoolVarP(&includeParents, "include-parents", "", false,
|
|
"Include all the parents of the moved resources as well")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func (cmd *stateMoveCmd) Run(
|
|
ctx context.Context, source backend.Stack, dest backend.Stack, args []string,
|
|
sourceSecretsProvider secrets.Provider, destSecretsProvider secrets.Provider,
|
|
) error {
|
|
if cmd.Stdin == nil {
|
|
cmd.Stdin = os.Stdin
|
|
}
|
|
if cmd.Stdout == nil {
|
|
cmd.Stdout = os.Stdout
|
|
}
|
|
if cmd.ws == nil {
|
|
cmd.ws = pkgWorkspace.Instance
|
|
}
|
|
|
|
sourceSnapshot, err := source.Snapshot(ctx, sourceSecretsProvider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destSnapshot, err := dest.Snapshot(ctx, destSecretsProvider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = destSnapshot.VerifyIntegrity()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to verify integrity of destination snapshot: %w", err)
|
|
}
|
|
|
|
err = sourceSnapshot.VerifyIntegrity()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to verify integrity of source snapshot: %w", err)
|
|
}
|
|
|
|
if sourceSnapshot == nil {
|
|
return errors.New("source stack has no resources")
|
|
}
|
|
if destSnapshot == nil {
|
|
destSnapshot = &deploy.Snapshot{}
|
|
}
|
|
if destSnapshot.SecretsManager == nil {
|
|
// If the destination stack has no secret manager, we
|
|
// need to create one. This only works if the user is
|
|
// currently in the destination project directory. If
|
|
// we fail here this indicates that they are not, and
|
|
// we return an projectError explaining that.
|
|
//nolint:lll
|
|
projectError := errors.New("destination stack has no secret manager. To move resources either initialize the stack with a secret manager, or run the pulumi state move command from the destination project directory")
|
|
path, err := workspace.DetectProjectPath()
|
|
if err != nil {
|
|
return projectError
|
|
}
|
|
if path == "" {
|
|
return projectError
|
|
}
|
|
project, err := workspace.LoadProject(path)
|
|
if err != nil {
|
|
return projectError
|
|
}
|
|
if string(project.Name) != string(dest.Ref().FullyQualifiedName().Namespace().Name()) {
|
|
return projectError
|
|
}
|
|
|
|
// The user is in the right directory. If we fail below we will return the error of that failure.
|
|
err = createSecretsManager(ctx, cmd.ws, dest, "", false, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ps, err := loadProjectStack(project, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
destSecretManager, err := dest.DefaultSecretManager(ps)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destSnapshot.SecretsManager = destSecretManager
|
|
}
|
|
|
|
resourcesToMove := make(map[string]*resource.State)
|
|
providersToCopy := make(map[string]bool)
|
|
unmatchedArgs := mapset.NewSet(args...)
|
|
rootStackURN := ""
|
|
for _, res := range sourceSnapshot.Resources {
|
|
matchedArg := resourceMatches(res, args)
|
|
if matchedArg != "" {
|
|
if strings.HasPrefix(string(res.Type), "pulumi:providers:") {
|
|
//nolint:lll
|
|
return errors.New("cannot move providers. Only resources can be moved, and providers will be included automatically")
|
|
}
|
|
if res.Type == resource.RootStackType && res.Parent == "" {
|
|
rootStackURN = string(res.URN)
|
|
}
|
|
resourcesToMove[string(res.URN)] = res
|
|
providersToCopy[res.Provider] = true
|
|
unmatchedArgs.Remove(matchedArg)
|
|
}
|
|
}
|
|
|
|
for _, arg := range unmatchedArgs.ToSlice() {
|
|
fmt.Fprintf(cmd.Stdout, cmd.Colorizer.Colorize(colors.SpecWarning+"warning:"+
|
|
colors.Reset+" Resource %s not found in source stack\n"), arg)
|
|
}
|
|
|
|
if len(resourcesToMove) == 0 {
|
|
return errors.New("no resources found to move")
|
|
}
|
|
|
|
sourceDepGraph := graph.NewDependencyGraph(sourceSnapshot.Resources)
|
|
|
|
if cmd.IncludeParents {
|
|
for _, res := range resourcesToMove {
|
|
for _, parent := range sourceDepGraph.ParentsOf(res) {
|
|
if res.Type == resource.RootStackType && res.Parent == "" {
|
|
// We don't move the root stack explicitly, the code below will take care of dealing with that correctly.
|
|
continue
|
|
}
|
|
resourcesToMove[string(parent.URN)] = parent
|
|
providersToCopy[parent.Provider] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// include all children in the list of resources to move
|
|
for _, res := range resourcesToMove {
|
|
for _, dep := range sourceDepGraph.ChildrenOf(res) {
|
|
resourcesToMove[string(dep.URN)] = dep
|
|
providersToCopy[dep.Provider] = true
|
|
}
|
|
}
|
|
|
|
// We don't want to include the root stack in the list of resources to move. The root stack
|
|
// either already exists in the destination stack or will be created when we move the resources.
|
|
delete(resourcesToMove, rootStackURN)
|
|
|
|
// We want to move the resources in the order they appear in the source snapshot,
|
|
// so that resources with relationships are in the right order. Also check which
|
|
// resources are remaining in the source stack, now that we know all resources
|
|
// that are going to be moved.
|
|
remainingResources := make(map[string]*resource.State)
|
|
var resourcesToMoveOrdered []*resource.State
|
|
for _, res := range sourceSnapshot.Resources {
|
|
if _, ok := resourcesToMove[string(res.URN)]; ok {
|
|
resourcesToMoveOrdered = append(resourcesToMoveOrdered, res)
|
|
} else {
|
|
remainingResources[string(res.URN)] = res
|
|
}
|
|
}
|
|
|
|
// run through the source snapshot and find all the providers
|
|
// that need to be copied, remove all the resources that need
|
|
// to be removed, and break the dependencies that are no
|
|
// longer valid.
|
|
var providers []*resource.State
|
|
var brokenSourceDependencies []brokenDependency
|
|
i := 0
|
|
for _, res := range sourceSnapshot.Resources {
|
|
// Find providers that need to be copied
|
|
if _, ok := resourcesToMove[string(res.URN)]; ok {
|
|
continue
|
|
}
|
|
if providersToCopy[string(res.URN)+"::"+string(res.ID)] {
|
|
providers = append(providers, res)
|
|
}
|
|
|
|
sourceSnapshot.Resources[i] = res
|
|
i++
|
|
brokenSourceDependencies = append(brokenSourceDependencies, breakDependencies(res, resourcesToMove)...)
|
|
}
|
|
sourceSnapshot.Resources = sourceSnapshot.Resources[:i]
|
|
|
|
// Create a root stack if there is none
|
|
rootStack, err := stack.GetRootStackResource(destSnapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rootStack == nil {
|
|
projectName, ok := dest.Ref().Project()
|
|
if !ok {
|
|
return errors.New("failed to get project name of source stack")
|
|
}
|
|
rootStack = stack.CreateRootStackResource(
|
|
dest.Ref().Name().Q(), tokens.PackageName(projectName))
|
|
destSnapshot.Resources = append([]*resource.State{rootStack}, destSnapshot.Resources...)
|
|
}
|
|
|
|
destResMap := make(map[urn.URN]*resource.State)
|
|
for _, res := range destSnapshot.Resources {
|
|
destResMap[res.URN] = res
|
|
}
|
|
|
|
rewriteMap := make(map[string]string)
|
|
for _, res := range providers {
|
|
// 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()
|
|
if _, ok := resourcesToMove[string(r.Parent)]; !ok {
|
|
rootStack, err := stack.GetRootStackResource(destSnapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Parent = rootStack.URN
|
|
}
|
|
err = rewriteURNs(r, dest, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if destRes, ok := destResMap[r.URN]; ok {
|
|
// If the provider ID matches, we can assume that the provider has previously been copied and we can just copy it.
|
|
if destRes.ID == r.ID {
|
|
continue
|
|
}
|
|
// If all the inputs of the provider in the destination stack are the same as the provider in the source stack,
|
|
// we can assume that the provider is equal for the purpose of resources depending on it. We don't need to copy
|
|
// it, but we need to set the provider for all resources to the provider in the destination stack.
|
|
if destRes.Inputs.DeepEquals(r.Inputs) {
|
|
rewriteMap[fmt.Sprintf("%s::%s", res.URN, res.ID)] = fmt.Sprintf("%s::%s", destRes.URN, destRes.ID)
|
|
continue
|
|
}
|
|
return fmt.Errorf("provider %s already exists in destination stack", r.URN)
|
|
}
|
|
|
|
destSnapshot.Resources = append(destSnapshot.Resources, r)
|
|
}
|
|
|
|
fmt.Fprintf(cmd.Stdout, cmd.Colorizer.Colorize(
|
|
colors.SpecHeadline+"Planning to move the following resources from %s to %s:\n"+colors.Reset),
|
|
source.Ref().FullyQualifiedName(), dest.Ref().FullyQualifiedName())
|
|
|
|
fmt.Fprintf(cmd.Stdout, "\n")
|
|
for _, res := range resourcesToMoveOrdered {
|
|
fmt.Fprintf(cmd.Stdout, " - %s\n", res.URN)
|
|
}
|
|
fmt.Fprintf(cmd.Stdout, "\n")
|
|
|
|
var brokenDestDependencies []brokenDependency
|
|
for _, res := range resourcesToMoveOrdered {
|
|
// We need the original resources URNs later in case of errors, so make a copy here before modifying them.
|
|
r := res.Copy()
|
|
if _, ok := resourcesToMove[string(r.Parent)]; !ok {
|
|
rootStack, err := stack.GetRootStackResource(destSnapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Parent = rootStack.URN
|
|
}
|
|
|
|
brokenDestDependencies = append(brokenDestDependencies, breakDependencies(r, remainingResources)...)
|
|
err = rewriteURNs(r, dest, rewriteMap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, ok := destResMap[r.URN]; ok {
|
|
return fmt.Errorf("resource %s already exists in destination stack", r.URN)
|
|
}
|
|
|
|
destSnapshot.Resources = append(destSnapshot.Resources, r)
|
|
}
|
|
|
|
if len(brokenSourceDependencies) > 0 {
|
|
fmt.Fprintf(cmd.Stdout, cmd.Colorizer.Colorize(
|
|
colors.SpecWarning+"The following resources remaining in %s have dependencies on resources moved to %s:\n\n"+
|
|
colors.Reset), source.Ref().FullyQualifiedName(), dest.Ref().FullyQualifiedName())
|
|
}
|
|
|
|
cmd.printBrokenDependencyRelationships(brokenSourceDependencies)
|
|
|
|
if len(brokenDestDependencies) > 0 {
|
|
if len(brokenSourceDependencies) > 0 {
|
|
fmt.Fprintln(cmd.Stdout)
|
|
}
|
|
fmt.Fprintf(cmd.Stdout, cmd.Colorizer.Colorize(
|
|
colors.SpecWarning+"The following resources being moved to %s have dependencies on resources in %s:\n\n"+
|
|
colors.Reset), dest.Ref().FullyQualifiedName(), source.Ref().FullyQualifiedName())
|
|
}
|
|
|
|
cmd.printBrokenDependencyRelationships(brokenDestDependencies)
|
|
|
|
if len(brokenSourceDependencies) > 0 || len(brokenDestDependencies) > 0 {
|
|
fmt.Fprint(cmd.Stdout, cmd.Colorizer.Colorize(
|
|
colors.SpecInfo+"\nIf 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.\n\n"+
|
|
colors.Reset))
|
|
}
|
|
|
|
if !cmd.Yes {
|
|
yes := "yes"
|
|
no := "no"
|
|
msg := "Do you want to perform this move?"
|
|
options := []string{
|
|
yes,
|
|
no,
|
|
}
|
|
|
|
switch response := promptUser(msg, options, no, cmdutil.GetGlobalColorization()); response {
|
|
case yes:
|
|
// continue
|
|
case no:
|
|
fmt.Println("Confirmation denied, not proceeding with the state move")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
err = destSnapshot.VerifyIntegrity()
|
|
if err != nil {
|
|
return fmt.Errorf(`failed to verify integrity of destination snapshot: %w
|
|
|
|
This is a bug! We would appreciate a report: https://github.com/pulumi/pulumi/issues/`, err)
|
|
}
|
|
|
|
err = sourceSnapshot.VerifyIntegrity()
|
|
if err != nil {
|
|
return fmt.Errorf(`failed to verify integrity of source snapshot: %w
|
|
|
|
This is a bug! We would appreciate a report: https://github.com/pulumi/pulumi/issues/`, err)
|
|
}
|
|
|
|
// We're saving the destination snapshot first, so that if saving a snapshot fails
|
|
// the resources will always still be tracked. If the source snapshot fails the user
|
|
// will have to manually remove the resources from the source stack.
|
|
err = saveSnapshot(ctx, dest, destSnapshot, false)
|
|
if err != nil {
|
|
return fmt.Errorf(`failed to save destination snapshot: %w
|
|
|
|
None of the resources have been moved, it is safe to try again`, err)
|
|
}
|
|
|
|
err = saveSnapshot(ctx, source, sourceSnapshot, false)
|
|
if err != nil {
|
|
var deleteCommands string
|
|
// Iterate over the resources in reverse order, so resources with no dependencies will be deleted first.
|
|
for i := len(resourcesToMoveOrdered) - 1; i >= 0; i-- {
|
|
deleteCommands += fmt.Sprintf(
|
|
"\n pulumi state delete --stack %s '%s'",
|
|
source.Ref().FullyQualifiedName(),
|
|
resourcesToMoveOrdered[i].URN)
|
|
}
|
|
return fmt.Errorf(`failed to save source snapshot: %w
|
|
|
|
The resources being moved have already been appended to the destination stack, but will still also be in the
|
|
source stack. Please remove the resources from the source stack manually the following commands:%v
|
|
'`, err, deleteCommands)
|
|
}
|
|
|
|
fmt.Fprintf(cmd.Stdout, cmd.Colorizer.Colorize(
|
|
colors.SpecHeadline+"Successfully moved resources from %s to %s\n"+colors.Reset),
|
|
source.Ref().FullyQualifiedName(), dest.Ref().FullyQualifiedName())
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceMatches(res *resource.State, args []string) string {
|
|
for _, arg := range args {
|
|
if string(res.URN) == arg {
|
|
return arg
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type dependencyType int
|
|
|
|
const (
|
|
dependency dependencyType = iota
|
|
propertyDependency
|
|
deletedWith
|
|
)
|
|
|
|
type brokenDependency struct {
|
|
dependencyURN urn.URN
|
|
dependencyType dependencyType
|
|
propdepKey resource.PropertyKey
|
|
resourceURN urn.URN
|
|
}
|
|
|
|
func breakDependencies(res *resource.State, resourcesToMove map[string]*resource.State) []brokenDependency {
|
|
var brokenDeps []brokenDependency
|
|
j := 0
|
|
for _, dep := range res.Dependencies {
|
|
if _, ok := resourcesToMove[string(dep)]; !ok {
|
|
res.Dependencies[j] = dep
|
|
j++
|
|
} else {
|
|
brokenDeps = append(brokenDeps, brokenDependency{
|
|
dependencyURN: dep,
|
|
dependencyType: dependency,
|
|
resourceURN: res.URN,
|
|
})
|
|
}
|
|
}
|
|
res.Dependencies = res.Dependencies[:j]
|
|
for k, propDeps := range res.PropertyDependencies {
|
|
j = 0
|
|
for _, propDep := range propDeps {
|
|
if _, ok := resourcesToMove[string(propDep)]; !ok {
|
|
propDeps[j] = propDep
|
|
j++
|
|
} else {
|
|
brokenDeps = append(brokenDeps, brokenDependency{
|
|
dependencyURN: propDep,
|
|
dependencyType: propertyDependency,
|
|
propdepKey: k,
|
|
resourceURN: res.URN,
|
|
})
|
|
}
|
|
}
|
|
res.PropertyDependencies[k] = propDeps[:j]
|
|
}
|
|
if _, ok := resourcesToMove[string(res.DeletedWith)]; ok {
|
|
brokenDeps = append(brokenDeps, brokenDependency{
|
|
dependencyURN: res.DeletedWith,
|
|
dependencyType: deletedWith,
|
|
resourceURN: res.URN,
|
|
})
|
|
res.DeletedWith = ""
|
|
}
|
|
return brokenDeps
|
|
}
|
|
|
|
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, rewriteMap map[string]string) error {
|
|
var err error
|
|
res.URN, err = renameStackAndProject(res.URN, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if res.Provider != "" {
|
|
if newProviderURN, ok := rewriteMap[res.Provider]; ok {
|
|
res.Provider = newProviderURN
|
|
} else {
|
|
providerURN, err := renameStackAndProject(urn.URN(res.Provider), dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Provider = string(providerURN)
|
|
}
|
|
}
|
|
if res.Parent != "" {
|
|
parentURN, err := renameStackAndProject(res.Parent, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Parent = parentURN
|
|
}
|
|
for k, dep := range res.Dependencies {
|
|
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 {
|
|
depURN, err := renameStackAndProject(propDep, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.PropertyDependencies[k][j] = depURN
|
|
}
|
|
}
|
|
if res.DeletedWith != "" {
|
|
urn, err := renameStackAndProject(res.DeletedWith, dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.DeletedWith = urn
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cmd *stateMoveCmd) printBrokenDependencyRelationships(brokenDeps []brokenDependency) {
|
|
for _, res := range brokenDeps {
|
|
switch res.dependencyType {
|
|
case dependency:
|
|
fmt.Fprintf(cmd.Stdout, " - %s has a dependency on %s\n", res.resourceURN, res.dependencyURN)
|
|
case propertyDependency:
|
|
fmt.Fprintf(cmd.Stdout, " - %s (%s) has a property dependency on %s\n",
|
|
res.resourceURN, res.propdepKey, res.dependencyURN)
|
|
case deletedWith:
|
|
fmt.Fprintf(cmd.Stdout, " - %s is marked as deleted with %s\n", res.resourceURN, res.dependencyURN)
|
|
}
|
|
}
|
|
}
|