github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/httpstate/diffs.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 httpstate 16 17 import ( 18 "context" 19 "crypto/sha256" 20 "encoding/hex" 21 "encoding/json" 22 "fmt" 23 "sync" 24 25 "github.com/hexops/gotextdiff/myers" 26 "github.com/hexops/gotextdiff/span" 27 28 "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" 29 30 opentracing "github.com/opentracing/opentracing-go" 31 ) 32 33 type deploymentDiffState struct { 34 lastSavedDeployment json.RawMessage 35 sequenceNumber int 36 noChecksums bool 37 strictMode bool 38 minimalDiffSize int 39 } 40 41 type deploymentDiff struct { 42 sequenceNumber int 43 checkpointHash string 44 deploymentDelta json.RawMessage 45 } 46 47 func newDeploymentDiffState() *deploymentDiffState { 48 return &deploymentDiffState{sequenceNumber: 1} 49 } 50 51 func (dds *deploymentDiffState) SequenceNumber() int { 52 return dds.sequenceNumber 53 } 54 55 func (dds *deploymentDiffState) CanDiff() bool { 56 return dds.lastSavedDeployment != nil 57 } 58 59 // Size-based heuristics trying to estimate if the diff method will be 60 // worth it and take less time than sending the entire deployment. 61 func (dds *deploymentDiffState) ShouldDiff(new *apitype.UntypedDeployment) bool { 62 if !dds.CanDiff() { 63 return false 64 } 65 small := dds.minimalDiffSize 66 if small == 0 { 67 small = 1024 * 32 68 } 69 if len(dds.lastSavedDeployment) < small { 70 return false 71 } 72 if len(new.Deployment) < small { 73 return false 74 } 75 return true 76 } 77 78 func (dds *deploymentDiffState) Diff(ctx context.Context, 79 deployment *apitype.UntypedDeployment) (deploymentDiff, error) { 80 81 if !dds.CanDiff() { 82 return deploymentDiff{}, fmt.Errorf("Diff() cannot be called before Saved()") 83 } 84 85 if deployment.Version == 0 { 86 return deploymentDiff{}, fmt.Errorf("deployment.Version should be set") 87 } 88 89 tracingSpan, childCtx := opentracing.StartSpanFromContext(ctx, "Diff") 90 defer tracingSpan.Finish() 91 92 before := dds.lastSavedDeployment 93 94 after, err := marshalUntypedDeployment(deployment) 95 if err != nil { 96 return deploymentDiff{}, fmt.Errorf("marshalUntypedDeployment failed: %v", err) 97 } 98 99 var checkpointHash string 100 checkpointHashReady := &sync.WaitGroup{} 101 102 if !dds.noChecksums { 103 checkpointHashReady.Add(1) 104 go func() { 105 defer checkpointHashReady.Done() 106 checkpointHash = dds.computeHash(childCtx, after) 107 }() 108 } 109 110 delta, err := dds.computeEdits(childCtx, string(before), string(after)) 111 if err != nil { 112 return deploymentDiff{}, fmt.Errorf("Cannot marshal the edits: %v", err) 113 } 114 115 checkpointHashReady.Wait() 116 117 tracingSpan.SetTag("before", len(before)) 118 tracingSpan.SetTag("after", len(after)) 119 tracingSpan.SetTag("diff", len(delta)) 120 tracingSpan.SetTag("compression", 100.0*float64(len(delta))/float64(len(after))) 121 tracingSpan.SetTag("hash", checkpointHash) 122 123 diff := deploymentDiff{ 124 checkpointHash: checkpointHash, 125 deploymentDelta: delta, 126 sequenceNumber: dds.sequenceNumber, 127 } 128 129 return diff, nil 130 } 131 132 // Indicates that a deployment was just saved to the service. 133 func (dds *deploymentDiffState) Saved(ctx context.Context, deployment *apitype.UntypedDeployment) error { 134 d, err := marshalUntypedDeployment(deployment) 135 if err != nil { 136 return err 137 } 138 dds.lastSavedDeployment = d 139 dds.sequenceNumber++ 140 141 return nil 142 } 143 144 func (*deploymentDiffState) computeHash(ctx context.Context, deployment json.RawMessage) string { 145 tracingSpan, _ := opentracing.StartSpanFromContext(ctx, "computeHash") 146 defer tracingSpan.Finish() 147 hash := sha256.Sum256(deployment) 148 return hex.EncodeToString(hash[:]) 149 } 150 151 func (*deploymentDiffState) computeEdits(ctx context.Context, before, after string) (json.RawMessage, error) { 152 tracingSpan, _ := opentracing.StartSpanFromContext(ctx, "computeEdits") 153 defer tracingSpan.Finish() 154 155 edits := myers.ComputeEdits(span.URIFromURI(""), before, after) 156 157 delta, err := json.Marshal(edits) 158 if err != nil { 159 return nil, fmt.Errorf("Cannot marshal the edits: %v", err) 160 } 161 162 return delta, nil 163 }