mirror of https://github.com/pulumi/pulumi.git
215 lines
6.7 KiB
Go
215 lines
6.7 KiB
Go
// Copyright 2016-2023, 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/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/apitype"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
|
|
"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.
|
|
//
|
|
// If targetDependents is true, dependents will also be deleted. Otherwise an error
|
|
// instance of `ResourceHasDependenciesError` will be returned.
|
|
//
|
|
// If non-nil, onProtected will be called on all protected resources planed for deletion.
|
|
//
|
|
// If a resource is marked protected after onProtected is called, an error instance of
|
|
// `ResourceHasDependenciesError` will be returned.
|
|
func DeleteResource(
|
|
snapshot *deploy.Snapshot, condemnedRes *resource.State,
|
|
onProtected func(*resource.State) error, targetDependents bool,
|
|
) error {
|
|
contract.Requiref(snapshot != nil, "snapshot", "must not be nil")
|
|
contract.Requiref(condemnedRes != nil, "condemnedRes", "must not be nil")
|
|
|
|
handleProtected := func(res *resource.State) error {
|
|
if !res.Protect {
|
|
return nil
|
|
}
|
|
var err error
|
|
if onProtected != nil {
|
|
err = onProtected(res)
|
|
}
|
|
if err == nil && res.Protect {
|
|
err = ResourceProtectedError{res}
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := handleProtected(condemnedRes); err != nil {
|
|
return err
|
|
}
|
|
|
|
var numSameURN int
|
|
for _, res := range snapshot.Resources {
|
|
if res.URN != condemnedRes.URN {
|
|
continue
|
|
}
|
|
numSameURN++
|
|
}
|
|
isUniqueURN := numSameURN <= 1
|
|
|
|
deleteSet := make(map[resource.URN][]*resource.State)
|
|
dg := graph.NewDependencyGraph(snapshot.Resources)
|
|
|
|
deps := dg.OnlyDependsOn(condemnedRes)
|
|
if len(deps) != 0 {
|
|
if !targetDependents {
|
|
return ResourceHasDependenciesError{Condemned: condemnedRes, Dependencies: deps}
|
|
}
|
|
for _, dep := range deps {
|
|
if err := handleProtected(dep); err != nil {
|
|
return err
|
|
}
|
|
deleteSet[dep.URN] = append(deleteSet[dep.URN], dep)
|
|
}
|
|
}
|
|
|
|
// If there are no resources that depend on condemnedRes, iterate through the snapshot and keep everything that's
|
|
// not condemnedRes.
|
|
newSnapshot := slice.Prealloc[*resource.State](len(snapshot.Resources))
|
|
var children []*resource.State
|
|
search:
|
|
for _, res := range snapshot.Resources {
|
|
if res == condemnedRes {
|
|
// Skip condemned resource.
|
|
continue
|
|
}
|
|
|
|
for _, v := range deleteSet[res.URN] {
|
|
if v == res {
|
|
continue search
|
|
}
|
|
}
|
|
|
|
// While iterating, keep track of the set of resources that are parented to our
|
|
// condemned resource. This acts as a check on DependingOn, preventing a bug from
|
|
// introducing state corruption.
|
|
if res.Parent == condemnedRes.URN {
|
|
children = append(children, res)
|
|
}
|
|
|
|
newSnapshot = append(newSnapshot, res)
|
|
}
|
|
|
|
// If condemnedRes is unique and there exists a resource that is the child of condemnedRes,
|
|
// we can't delete it.
|
|
contract.Assertf(!isUniqueURN || len(children) == 0, "unexpected children in resource dependency list")
|
|
|
|
// 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 deployment. In addition, it rewrites the name of
|
|
// the root Stack resource itself. May optionally change the project/package name as well.
|
|
func RenameStack(deployment *apitype.DeploymentV3, newName tokens.StackName, newProject tokens.PackageName) error {
|
|
contract.Requiref(deployment != nil, "deployment", "must not be nil")
|
|
|
|
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 `<project>-<stack>` so we want
|
|
// to rename the name portion as well.
|
|
if u.QualifiedType() == resource.RootStackType {
|
|
return resource.NewURN(newName.Q(), project, "", u.QualifiedType(), string(tokens.QName(project)+"-"+newName.Q()))
|
|
}
|
|
|
|
return resource.NewURN(tokens.QName(newName.String()), project, "", u.QualifiedType(), u.Name())
|
|
}
|
|
|
|
rewriteState := func(res *apitype.ResourceV3) {
|
|
contract.Assertf(res != nil, "resource state must not be 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.DeletedWith != "" {
|
|
res.DeletedWith = rewriteUrn(res.DeletedWith)
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
for i := range deployment.Resources {
|
|
rewriteState(&deployment.Resources[i])
|
|
}
|
|
|
|
for i := range deployment.PendingOperations {
|
|
rewriteState(&deployment.PendingOperations[i].Resource)
|
|
}
|
|
|
|
return nil
|
|
}
|