github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/edit/operations.go (about)

     1  // Copyright 2016-2022, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package edit
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    21  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
    22  	"github.com/pulumi/pulumi/pkg/v3/resource/graph"
    23  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    24  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    25  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    26  )
    27  
    28  // OperationFunc is the type of functions that edit resources within a snapshot. The edits are made in-place to the
    29  // given snapshot and pertain to the specific passed-in resource.
    30  type OperationFunc func(*deploy.Snapshot, *resource.State) error
    31  
    32  // DeleteResource deletes a given resource from the snapshot, if it is possible to do so.
    33  //
    34  // If targetDependents is true, dependents will also be deleted. Otherwise an error
    35  // instance of `ResourceHasDependenciesError` will be returned.
    36  //
    37  // If non-nil, onProtected will be called on all protected resources planed for deletion.
    38  //
    39  // If a resource is marked protected after onProtected is called, an error instance of
    40  // `ResourceHasDependenciesError` will be returned.
    41  func DeleteResource(
    42  	snapshot *deploy.Snapshot, condemnedRes *resource.State,
    43  	onProtected func(*resource.State) error, targetDependents bool,
    44  ) error {
    45  	contract.Require(snapshot != nil, "snapshot")
    46  	contract.Require(condemnedRes != nil, "state")
    47  
    48  	handleProtected := func(res *resource.State) error {
    49  		if !res.Protect {
    50  			return nil
    51  		}
    52  		var err error
    53  		if onProtected != nil {
    54  			err = onProtected(res)
    55  		}
    56  		if err == nil && res.Protect {
    57  			err = ResourceProtectedError{res}
    58  		}
    59  		return err
    60  	}
    61  
    62  	if err := handleProtected(condemnedRes); err != nil {
    63  		return err
    64  	}
    65  
    66  	dg := graph.NewDependencyGraph(snapshot.Resources)
    67  	deleteSet := map[resource.URN]bool{
    68  		condemnedRes.URN: true,
    69  	}
    70  
    71  	if dependencies := dg.DependingOn(condemnedRes, nil, true); len(dependencies) != 0 {
    72  		if !targetDependents {
    73  			return ResourceHasDependenciesError{Condemned: condemnedRes, Dependencies: dependencies}
    74  		}
    75  		for _, dep := range dependencies {
    76  			err := handleProtected(dep)
    77  			if err != nil {
    78  				return err
    79  			}
    80  			deleteSet[dep.URN] = true
    81  		}
    82  	}
    83  
    84  	// If there are no resources that depend on condemnedRes, iterate through the snapshot and keep everything that's
    85  	// not condemnedRes.
    86  	var newSnapshot []*resource.State
    87  	var children []*resource.State
    88  	for _, res := range snapshot.Resources {
    89  		// While iterating, keep track of the set of resources that are parented to our
    90  		// condemned resource. This acts as a check on DependingOn, preventing a bug from
    91  		// introducing state corruption.
    92  		if res.Parent == condemnedRes.URN && !deleteSet[res.URN] {
    93  			children = append(children, res)
    94  		}
    95  
    96  		if !deleteSet[res.URN] {
    97  			newSnapshot = append(newSnapshot, res)
    98  		}
    99  	}
   100  
   101  	// If there exists a resource that is the child of condemnedRes, we can't delete it.
   102  	contract.Assertf(len(children) == 0, "unexpected children in resource dependency list")
   103  
   104  	// Otherwise, we're good to go. Writing the new resource list into the snapshot persists the mutations that we have
   105  	// made above.
   106  	snapshot.Resources = newSnapshot
   107  	return nil
   108  }
   109  
   110  // UnprotectResource unprotects a resource.
   111  func UnprotectResource(_ *deploy.Snapshot, res *resource.State) error {
   112  	res.Protect = false
   113  	return nil
   114  }
   115  
   116  // LocateResource returns all resources in the given snapshot that have the given URN.
   117  func LocateResource(snap *deploy.Snapshot, urn resource.URN) []*resource.State {
   118  	// If there is no snapshot then return no resources
   119  	if snap == nil {
   120  		return nil
   121  	}
   122  
   123  	var resources []*resource.State
   124  	for _, res := range snap.Resources {
   125  		if res.URN == urn {
   126  			resources = append(resources, res)
   127  		}
   128  	}
   129  
   130  	return resources
   131  }
   132  
   133  // RenameStack changes the `stackName` component of every URN in a snapshot. In addition, it rewrites the name of
   134  // the root Stack resource itself. May optionally change the project/package name as well.
   135  func RenameStack(snap *deploy.Snapshot, newName tokens.Name, newProject tokens.PackageName) error {
   136  	contract.Require(snap != nil, "snap")
   137  
   138  	rewriteUrn := func(u resource.URN) resource.URN {
   139  		project := u.Project()
   140  		if newProject != "" {
   141  			project = newProject
   142  		}
   143  
   144  		// The pulumi:pulumi:Stack resource's name component is of the form `<project>-<stack>` so we want
   145  		// to rename the name portion as well.
   146  		if u.QualifiedType() == "pulumi:pulumi:Stack" {
   147  			return resource.NewURN(newName.Q(), project, "", u.QualifiedType(), tokens.QName(project)+"-"+newName.Q())
   148  		}
   149  
   150  		return resource.NewURN(newName.Q(), project, "", u.QualifiedType(), u.Name())
   151  	}
   152  
   153  	rewriteState := func(res *resource.State) {
   154  		contract.Assert(res != nil)
   155  
   156  		res.URN = rewriteUrn(res.URN)
   157  
   158  		if res.Parent != "" {
   159  			res.Parent = rewriteUrn(res.Parent)
   160  		}
   161  
   162  		for depIdx, dep := range res.Dependencies {
   163  			res.Dependencies[depIdx] = rewriteUrn(dep)
   164  		}
   165  
   166  		for _, propDeps := range res.PropertyDependencies {
   167  			for depIdx, dep := range propDeps {
   168  				propDeps[depIdx] = rewriteUrn(dep)
   169  			}
   170  		}
   171  
   172  		if res.Provider != "" {
   173  			providerRef, err := providers.ParseReference(res.Provider)
   174  			contract.AssertNoErrorf(err, "failed to parse provider reference from validated checkpoint")
   175  
   176  			providerRef, err = providers.NewReference(rewriteUrn(providerRef.URN()), providerRef.ID())
   177  			contract.AssertNoErrorf(err, "failed to generate provider reference from valid reference")
   178  
   179  			res.Provider = providerRef.String()
   180  		}
   181  	}
   182  
   183  	if err := snap.VerifyIntegrity(); err != nil {
   184  		return fmt.Errorf("checkpoint is invalid: %w", err)
   185  	}
   186  
   187  	for _, res := range snap.Resources {
   188  		rewriteState(res)
   189  	}
   190  
   191  	for _, ops := range snap.PendingOperations {
   192  		rewriteState(ops.Resource)
   193  	}
   194  
   195  	return nil
   196  }