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  }