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 }