// 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 edit import ( "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/pkg/v3/resource/graph" "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/contract" ) // OperationFunc is the type of functions that edit resources within a snapshot. The edits are made in-place to the // given snapshot and pertain to the specific passed-in resource. type OperationFunc func(*deploy.Snapshot, *resource.State) error // DeleteResource deletes a given resource from the snapshot, if it is possible to do so. A resource can only be deleted // from a stack if there do not exist any resources that depend on it or descend from it. If such a resource does exist, // DeleteResource will return an error instance of `ResourceHasDependenciesError`. func DeleteResource(snapshot *deploy.Snapshot, condemnedRes *resource.State) error { contract.Require(snapshot != nil, "snapshot") contract.Require(condemnedRes != nil, "state") if condemnedRes.Protect { return ResourceProtectedError{condemnedRes} } dg := graph.NewDependencyGraph(snapshot.Resources) dependencies := dg.DependingOn(condemnedRes, nil, false) if len(dependencies) != 0 { return ResourceHasDependenciesError{Condemned: condemnedRes, Dependencies: dependencies} } // If there are no resources that depend on condemnedRes, iterate through the snapshot and keep everything that's // not condemnedRes. var newSnapshot []*resource.State var children []*resource.State for _, res := range snapshot.Resources { // While iterating, keep track of the set of resources that are parented to our condemned resource. We'll only // actually perform the deletion if this set is empty, otherwise it is not legal to delete the resource. if res.Parent == condemnedRes.URN { children = append(children, res) } if res != condemnedRes { newSnapshot = append(newSnapshot, res) } } // If there exists a resource that is the child of condemnedRes, we can't delete it. if len(children) != 0 { return ResourceHasDependenciesError{Condemned: condemnedRes, Dependencies: children} } // Otherwise, we're good to go. Writing the new resource list into the snapshot persists the mutations that we have // made above. snapshot.Resources = newSnapshot return nil } // UnprotectResource unprotects a resource. func UnprotectResource(_ *deploy.Snapshot, res *resource.State) error { res.Protect = false return nil } // LocateResource returns all resources in the given snapshot that have the given URN. func LocateResource(snap *deploy.Snapshot, urn resource.URN) []*resource.State { // If there is no snapshot then return no resources if snap == nil { return nil } var resources []*resource.State for _, res := range snap.Resources { if res.URN == urn { resources = append(resources, res) } } return resources } // RenameStack changes the `stackName` component of every URN in a snapshot. In addition, it rewrites the name of // the root Stack resource itself. May optionally change the project/package name as well. func RenameStack(snap *deploy.Snapshot, newName tokens.QName, newProject tokens.PackageName) error { contract.Require(snap != nil, "snap") rewriteUrn := func(u resource.URN) resource.URN { project := u.Project() if newProject != "" { project = newProject } // The pulumi:pulumi:Stack resource's name component is of the form `-` so we want // to rename the name portion as well. if u.QualifiedType() == "pulumi:pulumi:Stack" { return resource.NewURN(newName, project, "", u.QualifiedType(), tokens.QName(project)+"-"+newName) } return resource.NewURN(newName, project, "", u.QualifiedType(), u.Name()) } rewriteState := func(res *resource.State) { contract.Assert(res != nil) res.URN = rewriteUrn(res.URN) if res.Parent != "" { res.Parent = rewriteUrn(res.Parent) } for depIdx, dep := range res.Dependencies { res.Dependencies[depIdx] = rewriteUrn(dep) } for _, propDeps := range res.PropertyDependencies { for depIdx, dep := range propDeps { propDeps[depIdx] = rewriteUrn(dep) } } if res.Provider != "" { providerRef, err := providers.ParseReference(res.Provider) contract.AssertNoErrorf(err, "failed to parse provider reference from validated checkpoint") providerRef, err = providers.NewReference(rewriteUrn(providerRef.URN()), providerRef.ID()) contract.AssertNoErrorf(err, "failed to generate provider reference from valid reference") res.Provider = providerRef.String() } } if err := snap.VerifyIntegrity(); err != nil { return errors.Wrap(err, "checkpoint is invalid") } for _, res := range snap.Resources { rewriteState(res) } for _, ops := range snap.PendingOperations { rewriteState(ops.Resource) } return nil }