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 }