github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/filestate/backend.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 filestate
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"net/url"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/gofrs/uuid"
    32  
    33  	user "github.com/tweekmonster/luser"
    34  	"gocloud.dev/blob"
    35  	_ "gocloud.dev/blob/azureblob" // driver for azblob://
    36  	_ "gocloud.dev/blob/fileblob"  // driver for file://
    37  	"gocloud.dev/blob/gcsblob"     // driver for gs://
    38  	_ "gocloud.dev/blob/s3blob"    // driver for s3://
    39  	"gocloud.dev/gcerrors"
    40  
    41  	"github.com/pulumi/pulumi/pkg/v3/authhelpers"
    42  	"github.com/pulumi/pulumi/pkg/v3/backend"
    43  	"github.com/pulumi/pulumi/pkg/v3/backend/display"
    44  	"github.com/pulumi/pulumi/pkg/v3/engine"
    45  	"github.com/pulumi/pulumi/pkg/v3/operations"
    46  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    47  	"github.com/pulumi/pulumi/pkg/v3/resource/edit"
    48  	"github.com/pulumi/pulumi/pkg/v3/resource/stack"
    49  	"github.com/pulumi/pulumi/pkg/v3/util/validation"
    50  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    51  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
    52  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
    53  	sdkDisplay "github.com/pulumi/pulumi/sdk/v3/go/common/display"
    54  	"github.com/pulumi/pulumi/sdk/v3/go/common/encoding"
    55  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    56  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    57  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
    58  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    59  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
    60  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    61  )
    62  
    63  // PulumiFilestateGzipEnvVar is an env var that must be truthy
    64  // to enable gzip compression when using the filestate backend.
    65  const PulumiFilestateGzipEnvVar = "PULUMI_SELF_MANAGED_STATE_GZIP"
    66  
    67  // Backend extends the base backend interface with specific information about local backends.
    68  type Backend interface {
    69  	backend.Backend
    70  	local() // at the moment, no local specific info, so just use a marker function.
    71  }
    72  
    73  type localBackend struct {
    74  	d diag.Sink
    75  
    76  	// originalURL is the URL provided when the localBackend was initialized, for example
    77  	// "file://~". url is a canonicalized version that should be used when persisting data.
    78  	// (For example, replacing ~ with the home directory, making an absolute path, etc.)
    79  	originalURL string
    80  	url         string
    81  
    82  	bucket Bucket
    83  	mutex  sync.Mutex
    84  
    85  	lockID string
    86  
    87  	gzip bool
    88  }
    89  
    90  type localBackendReference struct {
    91  	name tokens.Name
    92  }
    93  
    94  func (r localBackendReference) String() string {
    95  	return string(r.name)
    96  }
    97  
    98  func (r localBackendReference) Name() tokens.Name {
    99  	return r.name
   100  }
   101  
   102  func IsFileStateBackendURL(urlstr string) bool {
   103  	u, err := url.Parse(urlstr)
   104  	if err != nil {
   105  		return false
   106  	}
   107  
   108  	return blob.DefaultURLMux().ValidBucketScheme(u.Scheme)
   109  }
   110  
   111  const FilePathPrefix = "file://"
   112  
   113  func New(d diag.Sink, originalURL string) (Backend, error) {
   114  	if !IsFileStateBackendURL(originalURL) {
   115  		return nil, fmt.Errorf("local URL %s has an illegal prefix; expected one of: %s",
   116  			originalURL, strings.Join(blob.DefaultURLMux().BucketSchemes(), ", "))
   117  	}
   118  
   119  	u, err := massageBlobPath(originalURL)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	p, err := url.Parse(u)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	blobmux := blob.DefaultURLMux()
   130  
   131  	// for gcp we want to support additional credentials
   132  	// schemes on top of go-cloud's default credentials mux.
   133  	if p.Scheme == gcsblob.Scheme {
   134  		blobmux, err = authhelpers.GoogleCredentialsMux(context.TODO())
   135  		if err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  
   140  	bucket, err := blobmux.OpenBucket(context.TODO(), u)
   141  	if err != nil {
   142  		return nil, fmt.Errorf("unable to open bucket %s: %w", u, err)
   143  	}
   144  
   145  	if !strings.HasPrefix(u, FilePathPrefix) {
   146  		bucketSubDir := strings.TrimLeft(p.Path, "/")
   147  		if bucketSubDir != "" {
   148  			if !strings.HasSuffix(bucketSubDir, "/") {
   149  				bucketSubDir += "/"
   150  			}
   151  
   152  			bucket = blob.PrefixedBucket(bucket, bucketSubDir)
   153  		}
   154  	}
   155  
   156  	isAcc, err := bucket.IsAccessible(context.TODO())
   157  	if err != nil {
   158  		return nil, fmt.Errorf("unable to check if bucket %s is accessible: %w", u, err)
   159  	}
   160  	if !isAcc {
   161  		return nil, fmt.Errorf("bucket %s is not accessible", u)
   162  	}
   163  
   164  	// Allocate a unique lock ID for this backend instance.
   165  	lockID, err := uuid.NewV4()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	gzipCompression := cmdutil.IsTruthy(os.Getenv(PulumiFilestateGzipEnvVar))
   171  
   172  	return &localBackend{
   173  		d:           d,
   174  		originalURL: originalURL,
   175  		url:         u,
   176  		bucket:      &wrappedBucket{bucket: bucket},
   177  		lockID:      lockID.String(),
   178  		gzip:        gzipCompression,
   179  	}, nil
   180  }
   181  
   182  // massageBlobPath takes the path the user provided and converts it to an appropriate form go-cloud
   183  // can support.  Importantly, s3/azblob/gs paths should not be be touched. This will only affect
   184  // file:// paths which have a few oddities around them that we want to ensure work properly.
   185  func massageBlobPath(path string) (string, error) {
   186  	if !strings.HasPrefix(path, FilePathPrefix) {
   187  		// Not a file:// path.  Keep this untouched and pass directly to gocloud.
   188  		return path, nil
   189  	}
   190  
   191  	// Strip off the "file://" portion so we can examine and determine what to do with the rest.
   192  	path = strings.TrimPrefix(path, FilePathPrefix)
   193  
   194  	// We need to specially handle ~.  The shell doesn't take care of this for us, and later
   195  	// functions we run into can't handle this either.
   196  	//
   197  	// From https://stackoverflow.com/questions/17609732/expand-tilde-to-home-directory
   198  	if strings.HasPrefix(path, "~") {
   199  		usr, err := user.Current()
   200  		if err != nil {
   201  			return "", fmt.Errorf("Could not determine current user to resolve `file://~` path.: %w", err)
   202  		}
   203  
   204  		if path == "~" {
   205  			path = usr.HomeDir
   206  		} else {
   207  			path = filepath.Join(usr.HomeDir, path[2:])
   208  		}
   209  	}
   210  
   211  	// For file:// backend, ensure a relative path is resolved. fileblob only supports absolute paths.
   212  	path, err := filepath.Abs(path)
   213  	if err != nil {
   214  		return "", fmt.Errorf("An IO error occurred while building the absolute path: %w", err)
   215  	}
   216  
   217  	// Using example from https://godoc.org/gocloud.dev/blob/fileblob#example-package--OpenBucket
   218  	// On Windows, convert "\" to "/" and add a leading "/". (See https://gocloud.dev/howto/blob/#local)
   219  	path = filepath.ToSlash(path)
   220  	if os.PathSeparator != '/' && !strings.HasPrefix(path, "/") {
   221  		path = "/" + path
   222  	}
   223  
   224  	return FilePathPrefix + path, nil
   225  }
   226  
   227  func Login(d diag.Sink, url string) (Backend, error) {
   228  	be, err := New(d, url)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	return be, workspace.StoreAccount(be.URL(), workspace.Account{}, true)
   233  }
   234  
   235  func (b *localBackend) local() {}
   236  
   237  func (b *localBackend) Name() string {
   238  	name, err := os.Hostname()
   239  	contract.IgnoreError(err)
   240  	if name == "" {
   241  		name = "local"
   242  	}
   243  	return name
   244  }
   245  
   246  func (b *localBackend) URL() string {
   247  	return b.originalURL
   248  }
   249  
   250  func (b *localBackend) StateDir() string {
   251  	return workspace.BookkeepingDir
   252  }
   253  
   254  func (b *localBackend) GetPolicyPack(ctx context.Context, policyPack string,
   255  	d diag.Sink) (backend.PolicyPack, error) {
   256  
   257  	return nil, fmt.Errorf("File state backend does not support resource policy")
   258  }
   259  
   260  func (b *localBackend) ListPolicyGroups(ctx context.Context, orgName string, _ backend.ContinuationToken) (
   261  	apitype.ListPolicyGroupsResponse, backend.ContinuationToken, error) {
   262  	return apitype.ListPolicyGroupsResponse{}, nil, fmt.Errorf("File state backend does not support resource policy")
   263  }
   264  
   265  func (b *localBackend) ListPolicyPacks(ctx context.Context, orgName string, _ backend.ContinuationToken) (
   266  	apitype.ListPolicyPacksResponse, backend.ContinuationToken, error) {
   267  	return apitype.ListPolicyPacksResponse{}, nil, fmt.Errorf("File state backend does not support resource policy")
   268  }
   269  
   270  func (b *localBackend) SupportsTags() bool {
   271  	return false
   272  }
   273  
   274  func (b *localBackend) SupportsOrganizations() bool {
   275  	return false
   276  }
   277  
   278  func (b *localBackend) ParseStackReference(stackRefName string) (backend.StackReference, error) {
   279  	if err := b.ValidateStackName(stackRefName); err != nil {
   280  		return nil, err
   281  	}
   282  	return localBackendReference{name: tokens.Name(stackRefName)}, nil
   283  }
   284  
   285  // ValidateStackName verifies the stack name is valid for the local backend. We use the same rules as the
   286  // httpstate backend.
   287  func (b *localBackend) ValidateStackName(stackName string) error {
   288  	if strings.Contains(stackName, "/") {
   289  		return errors.New("stack names may not contain slashes")
   290  	}
   291  
   292  	validNameRegex := regexp.MustCompile("^[A-Za-z0-9_.-]{1,100}$")
   293  	if !validNameRegex.MatchString(stackName) {
   294  		return errors.New(
   295  			"stack names are limited to 100 characters and may only contain alphanumeric, hyphens, underscores, or periods")
   296  	}
   297  
   298  	return nil
   299  }
   300  
   301  func (b *localBackend) DoesProjectExist(ctx context.Context, projectName string) (bool, error) {
   302  	// Local backends don't really have multiple projects, so just return false here.
   303  	return false, nil
   304  }
   305  
   306  func (b *localBackend) CreateStack(ctx context.Context, stackRef backend.StackReference,
   307  	opts interface{}) (backend.Stack, error) {
   308  
   309  	err := b.Lock(ctx, stackRef)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	defer b.Unlock(ctx, stackRef)
   314  
   315  	contract.Requiref(opts == nil, "opts", "local stacks do not support any options")
   316  
   317  	stackName := stackRef.Name()
   318  	if stackName == "" {
   319  		return nil, errors.New("invalid empty stack name")
   320  	}
   321  
   322  	if _, _, err := b.getStack(ctx, stackName); err == nil {
   323  		return nil, &backend.StackAlreadyExistsError{StackName: string(stackName)}
   324  	}
   325  
   326  	tags, err := backend.GetEnvironmentTagsForCurrentStack()
   327  	if err != nil {
   328  		return nil, fmt.Errorf("getting stack tags: %w", err)
   329  	}
   330  	if err = validation.ValidateStackProperties(string(stackName), tags); err != nil {
   331  		return nil, fmt.Errorf("validating stack properties: %w", err)
   332  	}
   333  
   334  	file, err := b.saveStack(stackName, nil, nil)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	stack := newStack(stackRef, file, nil, b)
   340  	fmt.Printf("Created stack '%s'\n", stack.Ref())
   341  
   342  	return stack, nil
   343  }
   344  
   345  func (b *localBackend) GetStack(ctx context.Context, stackRef backend.StackReference) (backend.Stack, error) {
   346  	stackName := stackRef.Name()
   347  	snapshot, path, err := b.getStack(ctx, stackName)
   348  
   349  	switch {
   350  	case gcerrors.Code(err) == gcerrors.NotFound:
   351  		return nil, nil
   352  	case err != nil:
   353  		return nil, err
   354  	default:
   355  		return newStack(stackRef, path, snapshot, b), nil
   356  	}
   357  }
   358  
   359  func (b *localBackend) ListStacks(
   360  	ctx context.Context, _ backend.ListStacksFilter, _ backend.ContinuationToken) (
   361  	[]backend.StackSummary, backend.ContinuationToken, error) {
   362  	stacks, err := b.getLocalStacks()
   363  	if err != nil {
   364  		return nil, nil, err
   365  	}
   366  
   367  	// Note that the provided stack filter is not honored, since fields like
   368  	// organizations and tags aren't persisted in the local backend.
   369  	var results []backend.StackSummary
   370  	for _, stackName := range stacks {
   371  		chk, err := b.getCheckpoint(stackName)
   372  		if err != nil {
   373  			return nil, nil, err
   374  		}
   375  		stackRef, err := b.ParseStackReference(string(stackName))
   376  		if err != nil {
   377  			return nil, nil, err
   378  		}
   379  		results = append(results, newLocalStackSummary(stackRef, chk))
   380  	}
   381  
   382  	return results, nil, nil
   383  }
   384  
   385  func (b *localBackend) RemoveStack(ctx context.Context, stack backend.Stack, force bool) (bool, error) {
   386  
   387  	err := b.Lock(ctx, stack.Ref())
   388  	if err != nil {
   389  		return false, err
   390  	}
   391  	defer b.Unlock(ctx, stack.Ref())
   392  
   393  	stackName := stack.Ref().Name()
   394  	snapshot, _, err := b.getStack(ctx, stackName)
   395  	if err != nil {
   396  		return false, err
   397  	}
   398  
   399  	// Don't remove stacks that still have resources.
   400  	if !force && snapshot != nil && len(snapshot.Resources) > 0 {
   401  		return true, errors.New("refusing to remove stack because it still contains resources")
   402  	}
   403  
   404  	return false, b.removeStack(stackName)
   405  }
   406  
   407  func (b *localBackend) RenameStack(ctx context.Context, stack backend.Stack,
   408  	newName tokens.QName) (backend.StackReference, error) {
   409  
   410  	err := b.Lock(ctx, stack.Ref())
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  	defer b.Unlock(ctx, stack.Ref())
   415  
   416  	// Get the current state from the stack to be renamed.
   417  	stackName := stack.Ref().Name()
   418  	snap, _, err := b.getStack(ctx, stackName)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  
   423  	// Ensure the new stack name is valid.
   424  	newRef, err := b.ParseStackReference(string(newName))
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  
   429  	newStackName := newRef.Name()
   430  
   431  	// Ensure the destination stack does not already exist.
   432  	hasExisting, err := b.bucket.Exists(ctx, b.stackPath(newStackName))
   433  	if err != nil {
   434  		return nil, err
   435  	}
   436  	if hasExisting {
   437  		return nil, fmt.Errorf("a stack named %s already exists", newName)
   438  	}
   439  
   440  	// If we have a snapshot, we need to rename the URNs inside it to use the new stack name.
   441  	if snap != nil {
   442  		if err = edit.RenameStack(snap, newStackName, ""); err != nil {
   443  			return nil, err
   444  		}
   445  	}
   446  
   447  	// Now save the snapshot with a new name (we pass nil to re-use the existing secrets manager from the snapshot).
   448  	if _, err = b.saveStack(newStackName, snap, nil); err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	// To remove the old stack, just make a backup of the file and don't write out anything new.
   453  	file := b.stackPath(stackName)
   454  	backupTarget(b.bucket, file, false)
   455  
   456  	// And rename the history folder as well.
   457  	if err = b.renameHistory(stackName, newStackName); err != nil {
   458  		return nil, err
   459  	}
   460  	return newRef, err
   461  }
   462  
   463  func (b *localBackend) GetLatestConfiguration(ctx context.Context,
   464  	stack backend.Stack) (config.Map, error) {
   465  
   466  	hist, err := b.GetHistory(ctx, stack.Ref(), 1 /*pageSize*/, 1 /*page*/)
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  	if len(hist) == 0 {
   471  		return nil, backend.ErrNoPreviousDeployment
   472  	}
   473  
   474  	return hist[0].Config, nil
   475  }
   476  
   477  func (b *localBackend) PackPolicies(
   478  	ctx context.Context, policyPackRef backend.PolicyPackReference,
   479  	cancellationScopes backend.CancellationScopeSource,
   480  	callerEventsOpt chan<- engine.Event) result.Result {
   481  
   482  	return result.Error("File state backend does not support resource policy")
   483  }
   484  
   485  func (b *localBackend) Preview(ctx context.Context, stack backend.Stack,
   486  	op backend.UpdateOperation) (*deploy.Plan, sdkDisplay.ResourceChanges, result.Result) {
   487  
   488  	// We can skip PreviewThenPromptThenExecute and just go straight to Execute.
   489  	opts := backend.ApplierOptions{
   490  		DryRun:   true,
   491  		ShowLink: true,
   492  	}
   493  	return b.apply(ctx, apitype.PreviewUpdate, stack, op, opts, nil /*events*/)
   494  }
   495  
   496  func (b *localBackend) Update(ctx context.Context, stack backend.Stack,
   497  	op backend.UpdateOperation) (sdkDisplay.ResourceChanges, result.Result) {
   498  
   499  	err := b.Lock(ctx, stack.Ref())
   500  	if err != nil {
   501  		return nil, result.FromError(err)
   502  	}
   503  	defer b.Unlock(ctx, stack.Ref())
   504  
   505  	return backend.PreviewThenPromptThenExecute(ctx, apitype.UpdateUpdate, stack, op, b.apply)
   506  }
   507  
   508  func (b *localBackend) Import(ctx context.Context, stack backend.Stack,
   509  	op backend.UpdateOperation, imports []deploy.Import) (sdkDisplay.ResourceChanges, result.Result) {
   510  
   511  	err := b.Lock(ctx, stack.Ref())
   512  	if err != nil {
   513  		return nil, result.FromError(err)
   514  	}
   515  	defer b.Unlock(ctx, stack.Ref())
   516  
   517  	op.Imports = imports
   518  	return backend.PreviewThenPromptThenExecute(ctx, apitype.ResourceImportUpdate, stack, op, b.apply)
   519  }
   520  
   521  func (b *localBackend) Refresh(ctx context.Context, stack backend.Stack,
   522  	op backend.UpdateOperation) (sdkDisplay.ResourceChanges, result.Result) {
   523  
   524  	err := b.Lock(ctx, stack.Ref())
   525  	if err != nil {
   526  		return nil, result.FromError(err)
   527  	}
   528  	defer b.Unlock(ctx, stack.Ref())
   529  
   530  	return backend.PreviewThenPromptThenExecute(ctx, apitype.RefreshUpdate, stack, op, b.apply)
   531  }
   532  
   533  func (b *localBackend) Destroy(ctx context.Context, stack backend.Stack,
   534  	op backend.UpdateOperation) (sdkDisplay.ResourceChanges, result.Result) {
   535  
   536  	err := b.Lock(ctx, stack.Ref())
   537  	if err != nil {
   538  		return nil, result.FromError(err)
   539  	}
   540  	defer b.Unlock(ctx, stack.Ref())
   541  
   542  	return backend.PreviewThenPromptThenExecute(ctx, apitype.DestroyUpdate, stack, op, b.apply)
   543  }
   544  
   545  func (b *localBackend) Query(ctx context.Context, op backend.QueryOperation) result.Result {
   546  
   547  	return b.query(ctx, op, nil /*events*/)
   548  }
   549  
   550  func (b *localBackend) Watch(ctx context.Context, stack backend.Stack,
   551  	op backend.UpdateOperation, paths []string) result.Result {
   552  	return backend.Watch(ctx, b, stack, op, b.apply, paths)
   553  }
   554  
   555  // apply actually performs the provided type of update on a locally hosted stack.
   556  func (b *localBackend) apply(
   557  	ctx context.Context, kind apitype.UpdateKind, stack backend.Stack,
   558  	op backend.UpdateOperation, opts backend.ApplierOptions,
   559  	events chan<- engine.Event) (*deploy.Plan, sdkDisplay.ResourceChanges, result.Result) {
   560  
   561  	stackRef := stack.Ref()
   562  	stackName := stackRef.Name()
   563  	actionLabel := backend.ActionLabel(kind, opts.DryRun)
   564  
   565  	if !(op.Opts.Display.JSONDisplay || op.Opts.Display.Type == display.DisplayWatch) {
   566  		// Print a banner so it's clear this is a local deployment.
   567  		fmt.Printf(op.Opts.Display.Color.Colorize(
   568  			colors.SpecHeadline+"%s (%s):"+colors.Reset+"\n"), actionLabel, stackRef)
   569  	}
   570  
   571  	// Start the update.
   572  	update, err := b.newUpdate(ctx, stackName, op)
   573  	if err != nil {
   574  		return nil, nil, result.FromError(err)
   575  	}
   576  
   577  	// Spawn a display loop to show events on the CLI.
   578  	displayEvents := make(chan engine.Event)
   579  	displayDone := make(chan bool)
   580  	go display.ShowEvents(
   581  		strings.ToLower(actionLabel), kind, stackName, op.Proj.Name,
   582  		displayEvents, displayDone, op.Opts.Display, opts.DryRun)
   583  
   584  	// Create a separate event channel for engine events that we'll pipe to both listening streams.
   585  	engineEvents := make(chan engine.Event)
   586  
   587  	scope := op.Scopes.NewScope(engineEvents, opts.DryRun)
   588  	eventsDone := make(chan bool)
   589  	go func() {
   590  		// Pull in all events from the engine and send them to the two listeners.
   591  		for e := range engineEvents {
   592  			displayEvents <- e
   593  
   594  			// If the caller also wants to see the events, stream them there also.
   595  			if events != nil {
   596  				events <- e
   597  			}
   598  		}
   599  
   600  		close(eventsDone)
   601  	}()
   602  
   603  	// Create the management machinery.
   604  	persister := b.newSnapshotPersister(stackName, op.SecretsManager)
   605  	manager := backend.NewSnapshotManager(persister, update.GetTarget().Snapshot)
   606  	engineCtx := &engine.Context{
   607  		Cancel:          scope.Context(),
   608  		Events:          engineEvents,
   609  		SnapshotManager: manager,
   610  		BackendClient:   backend.NewBackendClient(b),
   611  	}
   612  
   613  	// Perform the update
   614  	start := time.Now().Unix()
   615  	var plan *deploy.Plan
   616  	var changes sdkDisplay.ResourceChanges
   617  	var updateRes result.Result
   618  	switch kind {
   619  	case apitype.PreviewUpdate:
   620  		plan, changes, updateRes = engine.Update(update, engineCtx, op.Opts.Engine, true)
   621  	case apitype.UpdateUpdate:
   622  		_, changes, updateRes = engine.Update(update, engineCtx, op.Opts.Engine, opts.DryRun)
   623  	case apitype.ResourceImportUpdate:
   624  		_, changes, updateRes = engine.Import(update, engineCtx, op.Opts.Engine, op.Imports, opts.DryRun)
   625  	case apitype.RefreshUpdate:
   626  		_, changes, updateRes = engine.Refresh(update, engineCtx, op.Opts.Engine, opts.DryRun)
   627  	case apitype.DestroyUpdate:
   628  		_, changes, updateRes = engine.Destroy(update, engineCtx, op.Opts.Engine, opts.DryRun)
   629  	default:
   630  		contract.Failf("Unrecognized update kind: %s", kind)
   631  	}
   632  	end := time.Now().Unix()
   633  
   634  	// Wait for the display to finish showing all the events.
   635  	<-displayDone
   636  	scope.Close() // Don't take any cancellations anymore, we're shutting down.
   637  	close(engineEvents)
   638  	contract.IgnoreClose(manager)
   639  
   640  	// Make sure the goroutine writing to displayEvents and events has exited before proceeding.
   641  	<-eventsDone
   642  	close(displayEvents)
   643  
   644  	// Save update results.
   645  	backendUpdateResult := backend.SucceededResult
   646  	if updateRes != nil {
   647  		backendUpdateResult = backend.FailedResult
   648  	}
   649  	info := backend.UpdateInfo{
   650  		Kind:        kind,
   651  		StartTime:   start,
   652  		Message:     op.M.Message,
   653  		Environment: op.M.Environment,
   654  		Config:      update.GetTarget().Config,
   655  		Result:      backendUpdateResult,
   656  		EndTime:     end,
   657  		// IDEA: it would be nice to populate the *Deployment, so that addToHistory below doesn't need to
   658  		//     rudely assume it knows where the checkpoint file is on disk as it makes a copy of it.  This isn't
   659  		//     trivial to achieve today given the event driven nature of plan-walking, however.
   660  		ResourceChanges: changes,
   661  	}
   662  
   663  	var saveErr error
   664  	var backupErr error
   665  	if !opts.DryRun {
   666  		saveErr = b.addToHistory(stackName, info)
   667  		backupErr = b.backupStack(stackName)
   668  	}
   669  
   670  	if updateRes != nil {
   671  		// We swallow saveErr and backupErr as they are less important than the updateErr.
   672  		return plan, changes, updateRes
   673  	}
   674  
   675  	if saveErr != nil {
   676  		// We swallow backupErr as it is less important than the saveErr.
   677  		return plan, changes, result.FromError(fmt.Errorf("saving update info: %w", saveErr))
   678  	}
   679  
   680  	if backupErr != nil {
   681  		return plan, changes, result.FromError(fmt.Errorf("saving backup: %w", backupErr))
   682  	}
   683  
   684  	// Make sure to print a link to the stack's checkpoint before exiting.
   685  	if !op.Opts.Display.SuppressPermalink && opts.ShowLink && !op.Opts.Display.JSONDisplay {
   686  		// Note we get a real signed link for aws/azure/gcp links.  But no such option exists for
   687  		// file:// links so we manually create the link ourselves.
   688  		var link string
   689  		if strings.HasPrefix(b.url, FilePathPrefix) {
   690  			u, _ := url.Parse(b.url)
   691  			u.Path = filepath.ToSlash(path.Join(u.Path, b.stackPath(stackName)))
   692  			link = u.String()
   693  		} else {
   694  			link, err = b.bucket.SignedURL(context.TODO(), b.stackPath(stackName), nil)
   695  			if err != nil {
   696  				// set link to be empty to when there is an error to hide use of Permalinks
   697  				link = ""
   698  
   699  				// we log a warning here rather then returning an error to avoid exiting
   700  				// pulumi with an error code.
   701  				// printing a statefile perma link happens after all the providers have finished
   702  				// deploying the infrastructure, failing the pulumi update because there was a
   703  				// problem printing a statefile perma link can be missleading in automated CI environments.
   704  				cmdutil.Diag().Warningf(diag.Message("", "Unable to create signed url for current backend to "+
   705  					"create a Permalink. Please visit https://www.pulumi.com/docs/troubleshooting/ "+
   706  					"for more information\n"))
   707  			}
   708  		}
   709  
   710  		if link != "" {
   711  			fmt.Printf(op.Opts.Display.Color.Colorize(
   712  				colors.SpecHeadline+"Permalink: "+
   713  					colors.Underline+colors.BrightBlue+"%s"+colors.Reset+"\n"), link)
   714  		}
   715  	}
   716  
   717  	return plan, changes, nil
   718  }
   719  
   720  // query executes a query program against the resource outputs of a locally hosted stack.
   721  func (b *localBackend) query(ctx context.Context, op backend.QueryOperation,
   722  	callerEventsOpt chan<- engine.Event) result.Result {
   723  
   724  	return backend.RunQuery(ctx, b, op, callerEventsOpt, b.newQuery)
   725  }
   726  
   727  func (b *localBackend) GetHistory(
   728  	ctx context.Context,
   729  	stackRef backend.StackReference,
   730  	pageSize int,
   731  	page int) ([]backend.UpdateInfo, error) {
   732  	stackName := stackRef.Name()
   733  	updates, err := b.getHistory(stackName, pageSize, page)
   734  	if err != nil {
   735  		return nil, err
   736  	}
   737  	return updates, nil
   738  }
   739  
   740  func (b *localBackend) GetLogs(ctx context.Context, stack backend.Stack, cfg backend.StackConfiguration,
   741  	query operations.LogQuery) ([]operations.LogEntry, error) {
   742  
   743  	stackName := stack.Ref().Name()
   744  	target, err := b.getTarget(ctx, stackName, cfg.Config, cfg.Decrypter)
   745  	if err != nil {
   746  		return nil, err
   747  	}
   748  
   749  	return GetLogsForTarget(target, query)
   750  }
   751  
   752  // GetLogsForTarget fetches stack logs using the config, decrypter, and checkpoint in the given target.
   753  func GetLogsForTarget(target *deploy.Target, query operations.LogQuery) ([]operations.LogEntry, error) {
   754  	contract.Assert(target != nil)
   755  
   756  	if target.Snapshot == nil {
   757  		// If the stack has not been deployed yet, return no logs.
   758  		return nil, nil
   759  	}
   760  
   761  	config, err := target.Config.Decrypt(target.Decrypter)
   762  	if err != nil {
   763  		return nil, err
   764  	}
   765  
   766  	components := operations.NewResourceTree(target.Snapshot.Resources)
   767  	ops := components.OperationsProvider(config)
   768  	logs, err := ops.GetLogs(query)
   769  	if logs == nil {
   770  		return nil, err
   771  	}
   772  	return *logs, err
   773  }
   774  
   775  func (b *localBackend) ExportDeployment(ctx context.Context,
   776  	stk backend.Stack) (*apitype.UntypedDeployment, error) {
   777  
   778  	stackName := stk.Ref().Name()
   779  	snap, _, err := b.getStack(ctx, stackName)
   780  	if err != nil {
   781  		return nil, err
   782  	}
   783  
   784  	if snap == nil {
   785  		snap = deploy.NewSnapshot(deploy.Manifest{}, nil, nil, nil)
   786  	}
   787  
   788  	sdep, err := stack.SerializeDeployment(snap, snap.SecretsManager /* showSecrsts */, false)
   789  	if err != nil {
   790  		return nil, fmt.Errorf("serializing deployment: %w", err)
   791  	}
   792  
   793  	data, err := encoding.JSON.Marshal(sdep)
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  
   798  	return &apitype.UntypedDeployment{
   799  		Version:    3,
   800  		Deployment: json.RawMessage(data),
   801  	}, nil
   802  }
   803  
   804  func (b *localBackend) ImportDeployment(ctx context.Context, stk backend.Stack,
   805  	deployment *apitype.UntypedDeployment) error {
   806  
   807  	err := b.Lock(ctx, stk.Ref())
   808  	if err != nil {
   809  		return err
   810  	}
   811  	defer b.Unlock(ctx, stk.Ref())
   812  
   813  	stackName := stk.Ref().Name()
   814  	_, _, err = b.getStack(ctx, stackName)
   815  	if err != nil {
   816  		return err
   817  	}
   818  
   819  	snap, err := stack.DeserializeUntypedDeployment(ctx, deployment, stack.DefaultSecretsProvider)
   820  	if err != nil {
   821  		return err
   822  	}
   823  
   824  	_, err = b.saveStack(stackName, snap, snap.SecretsManager)
   825  	return err
   826  }
   827  
   828  func (b *localBackend) Logout() error {
   829  	return workspace.DeleteAccount(b.originalURL)
   830  }
   831  
   832  func (b *localBackend) LogoutAll() error {
   833  	return workspace.DeleteAllAccounts()
   834  }
   835  
   836  func (b *localBackend) CurrentUser() (string, []string, error) {
   837  	user, err := user.Current()
   838  	if err != nil {
   839  		return "", nil, err
   840  	}
   841  	return user.Username, nil, nil
   842  }
   843  
   844  func (b *localBackend) getLocalStacks() ([]tokens.Name, error) {
   845  	var stacks []tokens.Name
   846  
   847  	// Read the stack directory.
   848  	path := b.stackPath("")
   849  
   850  	files, err := listBucket(b.bucket, path)
   851  	if err != nil {
   852  		return nil, fmt.Errorf("error listing stacks: %w", err)
   853  	}
   854  
   855  	for _, file := range files {
   856  		// Ignore directories.
   857  		if file.IsDir {
   858  			continue
   859  		}
   860  
   861  		// Skip files without valid extensions (e.g., *.bak files).
   862  		stackfn := objectName(file)
   863  		ext := filepath.Ext(stackfn)
   864  		// But accept gzip compression
   865  		if ext == encoding.GZIPExt {
   866  			stackfn = strings.TrimSuffix(stackfn, encoding.GZIPExt)
   867  			ext = filepath.Ext(stackfn)
   868  		}
   869  
   870  		if _, has := encoding.Marshalers[ext]; !has {
   871  			continue
   872  		}
   873  
   874  		// Read in this stack's information.
   875  		name := tokens.Name(stackfn[:len(stackfn)-len(ext)])
   876  
   877  		stacks = append(stacks, name)
   878  	}
   879  
   880  	return stacks, nil
   881  }
   882  
   883  // UpdateStackTags updates the stacks's tags, replacing all existing tags.
   884  func (b *localBackend) UpdateStackTags(ctx context.Context,
   885  	stack backend.Stack, tags map[apitype.StackTagName]string) error {
   886  
   887  	// The local backend does not currently persist tags.
   888  	return errors.New("stack tags not supported in --local mode")
   889  }
   890  
   891  func (b *localBackend) CancelCurrentUpdate(ctx context.Context, stackRef backend.StackReference) error {
   892  	// Try to delete ALL the lock files
   893  	allFiles, err := listBucket(b.bucket, stackLockDir(stackRef.Name()))
   894  	if err != nil {
   895  		// Don't error if it just wasn't found
   896  		if gcerrors.Code(err) == gcerrors.NotFound {
   897  			return nil
   898  		}
   899  		return err
   900  	}
   901  
   902  	for _, file := range allFiles {
   903  		if file.IsDir {
   904  			continue
   905  		}
   906  
   907  		err := b.bucket.Delete(ctx, file.Key)
   908  		if err != nil {
   909  			// Race condition, don't error if the file was delete between us calling list and now
   910  			if gcerrors.Code(err) == gcerrors.NotFound {
   911  				return nil
   912  			}
   913  			return err
   914  		}
   915  	}
   916  
   917  	return nil
   918  }