github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/httpstate/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 httpstate 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "os" 22 23 "github.com/pulumi/pulumi/pkg/v3/backend" 24 "github.com/pulumi/pulumi/pkg/v3/backend/httpstate/client" 25 "github.com/pulumi/pulumi/pkg/v3/resource/deploy" 26 "github.com/pulumi/pulumi/pkg/v3/resource/stack" 27 "github.com/pulumi/pulumi/pkg/v3/secrets" 28 "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" 29 "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" 30 "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" 31 ) 32 33 // cloudSnapshotPersister persists snapshots to the Pulumi service. 34 type cloudSnapshotPersister struct { 35 context context.Context // The context to use for client requests. 36 update client.UpdateIdentifier // The UpdateIdentifier for this update sequence. 37 tokenSource tokenSourceCapability // A token source for interacting with the service. 38 backend *cloudBackend // A backend for communicating with the service 39 sm secrets.Manager 40 deploymentDiffState *deploymentDiffState 41 } 42 43 func (persister *cloudSnapshotPersister) SecretsManager() secrets.Manager { 44 return persister.sm 45 } 46 47 func (persister *cloudSnapshotPersister) Save(snapshot *deploy.Snapshot) error { 48 ctx := persister.context 49 50 token, err := persister.tokenSource.GetToken() 51 if err != nil { 52 return err 53 } 54 55 deploymentV3, err := stack.SerializeDeployment(snapshot, persister.sm, false /* showSecrets */) 56 if err != nil { 57 return fmt.Errorf("serializing deployment: %w", err) 58 } 59 60 // Diff capability can be nil because of feature flagging. 61 if persister.deploymentDiffState == nil { 62 // Continue with how deployments were saved before diff. 63 return persister.backend.client.PatchUpdateCheckpoint( 64 persister.context, persister.update, deploymentV3, token) 65 } 66 67 deployment, err := persister.marshalDeployment(deploymentV3) 68 if err != nil { 69 return err 70 } 71 72 differ := persister.deploymentDiffState 73 74 // If there is no baseline to diff against, or diff is predicted to be inefficient, use saveFull. 75 if !differ.ShouldDiff(deployment) { 76 if err := persister.saveFullVerbatim(ctx, differ, deployment, token); err != nil { 77 return err 78 } 79 } else { // Otherwise can use saveDiff. 80 diff, err := differ.Diff(ctx, deployment) 81 if err != nil { 82 return err 83 } 84 if err := persister.saveDiff(ctx, diff, token); err != nil { 85 if differ.strictMode { 86 return err 87 } 88 if logging.V(3) { 89 logging.V(3).Infof("ignoring error saving checkpoint "+ 90 "with PatchUpdateCheckpointDelta, falling back to "+ 91 "PatchUpdateCheckpoint: %v", err) 92 } 93 if err := persister.saveFullVerbatim(ctx, differ, deployment, token); err != nil { 94 return err 95 } 96 } 97 } 98 99 return persister.deploymentDiffState.Saved(ctx, deployment) 100 } 101 102 func (persister *cloudSnapshotPersister) saveDiff(ctx context.Context, 103 diff deploymentDiff, token string) error { 104 return persister.backend.client.PatchUpdateCheckpointDelta( 105 persister.context, persister.update, 106 diff.sequenceNumber, diff.checkpointHash, diff.deploymentDelta, token) 107 } 108 109 func (persister *cloudSnapshotPersister) saveFullVerbatim(ctx context.Context, 110 differ *deploymentDiffState, deployment *apitype.UntypedDeployment, token string) error { 111 untypedDeploymentBytes, err := marshalUntypedDeployment(deployment) 112 if err != nil { 113 return err 114 } 115 return persister.backend.client.PatchUpdateCheckpointVerbatim( 116 persister.context, persister.update, differ.SequenceNumber(), 117 untypedDeploymentBytes, token) 118 } 119 120 func (persister *cloudSnapshotPersister) marshalDeployment( 121 deployment *apitype.DeploymentV3) (*apitype.UntypedDeployment, error) { 122 raw, err := json.MarshalIndent(deployment, "", "") 123 if err != nil { 124 return nil, fmt.Errorf("serializing deployment to json: %w", err) 125 } 126 return &apitype.UntypedDeployment{Deployment: raw, Version: 3}, nil 127 } 128 129 var _ backend.SnapshotPersister = (*cloudSnapshotPersister)(nil) 130 131 func (cb *cloudBackend) newSnapshotPersister(ctx context.Context, update client.UpdateIdentifier, 132 tokenSource tokenSourceCapability, sm secrets.Manager) *cloudSnapshotPersister { 133 134 p := &cloudSnapshotPersister{ 135 context: ctx, 136 update: update, 137 tokenSource: tokenSource, 138 backend: cb, 139 sm: sm, 140 } 141 142 if cmdutil.IsTruthy(os.Getenv("PULUMI_OPTIMIZED_CHECKPOINT_PATCH")) { 143 p.deploymentDiffState = newDeploymentDiffState() 144 145 if cmdutil.IsTruthy(os.Getenv("PULUMI_OPTIMIZED_CHECKPOINT_PATCH_STRICT")) { 146 p.deploymentDiffState.strictMode = true 147 } 148 149 if cmdutil.IsTruthy(os.Getenv("PULUMI_OPTIMIZED_CHECKPOINT_PATCH_NO_CHECKSUMS")) { 150 p.deploymentDiffState.noChecksums = true 151 } 152 } 153 154 return p 155 }