github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/deploy/snapshot.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 deploy
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
    21  	"github.com/pulumi/pulumi/pkg/v3/secrets"
    22  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    23  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    24  )
    25  
    26  // Snapshot is a view of a collection of resources in an stack at a point in time.  It describes resources; their
    27  // IDs, names, and properties; their dependencies; and more.  A snapshot is a diffable entity and can be used to create
    28  // or apply an infrastructure deployment plan in order to make reality match the snapshot state.
    29  type Snapshot struct {
    30  	Manifest          Manifest             // a deployment manifest of versions, checksums, and so on.
    31  	SecretsManager    secrets.Manager      // the manager to use use when seralizing this snapshot.
    32  	Resources         []*resource.State    // fetches all resources and their associated states.
    33  	PendingOperations []resource.Operation // all currently pending resource operations.
    34  }
    35  
    36  // NewSnapshot creates a snapshot from the given arguments.  The resources must be in topologically sorted order.
    37  // This property is not checked; for verification, please refer to the VerifyIntegrity function below.
    38  func NewSnapshot(manifest Manifest, secretsManager secrets.Manager,
    39  	resources []*resource.State, ops []resource.Operation) *Snapshot {
    40  
    41  	return &Snapshot{
    42  		Manifest:          manifest,
    43  		SecretsManager:    secretsManager,
    44  		Resources:         resources,
    45  		PendingOperations: ops,
    46  	}
    47  }
    48  
    49  // NormalizeURNReferences fixes up all URN references in a snapshot to use the new URNs instead of potentially-aliased
    50  // URNs.  This will affect resources that are "old", and which would be expected to be updated to refer to the new names
    51  // later in the deployment.  But until they are, we still want to ensure that any serialization of the snapshot uses URN
    52  // references which do not need to be indirected through any alias lookups, and which instead refer directly to the URN
    53  // of a resource in the resources map.
    54  //
    55  // Note: This method does not modify the snapshot (and resource.States
    56  // in the snapshot) in-place, but returns an independent structure,
    57  // with minimal copying necessary.
    58  func (snap *Snapshot) NormalizeURNReferences() (*Snapshot, error) {
    59  	if snap == nil {
    60  		return nil, nil
    61  	}
    62  
    63  	aliased := make(map[resource.URN]resource.URN)
    64  	for _, state := range snap.Resources {
    65  		// Add to aliased maps
    66  		for _, alias := range state.Aliases {
    67  			// For ease of implementation, some SDKs may end up creating the same alias to the
    68  			// same resource multiple times.  That's fine, only error if we see the same alias,
    69  			// but it maps to *different* resources.
    70  			if otherUrn, has := aliased[alias]; has && otherUrn != state.URN {
    71  				return nil, fmt.Errorf("Two resources ('%s' and '%s') aliased to the same: '%s'", otherUrn, state.URN, alias)
    72  			}
    73  			aliased[alias] = state.URN
    74  		}
    75  	}
    76  
    77  	fixUrn := func(urn resource.URN) resource.URN {
    78  		if newUrn, has := aliased[urn]; has {
    79  			// TODO should this recur to see if newUrn is simiarly aliased?
    80  			return newUrn
    81  		}
    82  		return urn
    83  	}
    84  
    85  	fixProvider := func(provider string) string {
    86  		ref, err := providers.ParseReference(provider)
    87  		contract.AssertNoError(err)
    88  		ref, err = providers.NewReference(fixUrn(ref.URN()), ref.ID())
    89  		contract.AssertNoError(err)
    90  		return ref.String()
    91  	}
    92  
    93  	fixResource := func(old *resource.State) *resource.State {
    94  		return newStateBuilder(old).
    95  			withUpdatedParent(fixUrn).
    96  			withUpdatedDependencies(fixUrn).
    97  			withUpdatedPropertyDependencies(fixUrn).
    98  			withUpdatedProvider(fixProvider).
    99  			build()
   100  	}
   101  
   102  	return snap.withUpdatedResources(fixResource), nil
   103  }
   104  
   105  // VerifyIntegrity checks a snapshot to ensure it is well-formed.  Because of the cost of this operation,
   106  // integrity verification is only performed on demand, and not automatically during snapshot construction.
   107  //
   108  // This function verifies a number of invariants:
   109  //  1. Provider resources must be referenceable (i.e. they must have a valid URN and ID)
   110  //  2. A resource's provider must precede the resource in the resource list
   111  //  3. Parents must precede children in the resource list
   112  //  4. Dependents must precede their dependencies in the resource list
   113  //  5. For every URN in the snapshot, there must be at most one resource with that URN that is not pending deletion
   114  //  6. The magic manifest number should change every time the snapshot is mutated
   115  func (snap *Snapshot) VerifyIntegrity() error {
   116  	if snap != nil {
   117  		// Ensure the magic cookie checks out.
   118  		if snap.Manifest.Magic != snap.Manifest.NewMagic() {
   119  			return fmt.Errorf("magic cookie mismatch; possible tampering/corruption detected")
   120  		}
   121  
   122  		// Now check the resources.  For now, we just verify that parents come before children, and that there aren't
   123  		// any duplicate URNs.
   124  		urns := make(map[resource.URN]*resource.State)
   125  		provs := make(map[providers.Reference]struct{})
   126  		for i, state := range snap.Resources {
   127  			urn := state.URN
   128  
   129  			if providers.IsProviderType(state.Type) {
   130  				ref, err := providers.NewReference(urn, state.ID)
   131  				if err != nil {
   132  					return fmt.Errorf("provider %s is not referenceable: %v", urn, err)
   133  				}
   134  				provs[ref] = struct{}{}
   135  			}
   136  			if provider := state.Provider; provider != "" {
   137  				ref, err := providers.ParseReference(provider)
   138  				if err != nil {
   139  					return fmt.Errorf("failed to parse provider reference for resource %s: %v", urn, err)
   140  				}
   141  				if _, has := provs[ref]; !has {
   142  					return fmt.Errorf("resource %s refers to unknown provider %s", urn, ref)
   143  				}
   144  			}
   145  
   146  			if par := state.Parent; par != "" {
   147  				if _, has := urns[par]; !has {
   148  					// The parent isn't there; to give a good error message, see whether it's missing entirely, or
   149  					// whether it comes later in the snapshot (neither of which should ever happen).
   150  					for _, other := range snap.Resources[i+1:] {
   151  						if other.URN == par {
   152  							return fmt.Errorf("child resource %s's parent %s comes after it", urn, par)
   153  						}
   154  					}
   155  					return fmt.Errorf("child resource %s refers to missing parent %s", urn, par)
   156  				}
   157  			}
   158  
   159  			for _, dep := range state.Dependencies {
   160  				if _, has := urns[dep]; !has {
   161  					// same as above - doing this for better error messages
   162  					for _, other := range snap.Resources[i+1:] {
   163  						if other.URN == dep {
   164  							return fmt.Errorf("resource %s's dependency %s comes after it", urn, other.URN)
   165  						}
   166  					}
   167  
   168  					return fmt.Errorf("resource %s dependency %s refers to missing resource", urn, dep)
   169  				}
   170  			}
   171  
   172  			if _, has := urns[urn]; has && !state.Delete {
   173  				// The only time we should have duplicate URNs is when all but one of them are marked for deletion.
   174  				return fmt.Errorf("duplicate resource %s (not marked for deletion)", urn)
   175  			}
   176  
   177  			urns[urn] = state
   178  		}
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  // Applies a non-mutating modification for every resource.State in the
   185  // Snapshot, returns the edited Snapshot.
   186  func (snap *Snapshot) withUpdatedResources(update func(*resource.State) *resource.State) *Snapshot {
   187  	old := snap.Resources
   188  	new := []*resource.State{}
   189  	edited := false
   190  	for _, s := range old {
   191  		n := update(s)
   192  		if n != s {
   193  			edited = true
   194  		}
   195  		new = append(new, n)
   196  	}
   197  	if !edited {
   198  		return snap
   199  	}
   200  	newSnap := *snap // shallow copy
   201  	newSnap.Resources = new
   202  	return &newSnap
   203  }