github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/stack.go (about)

     1  // Copyright 2016-2018, 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 backend
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"path/filepath"
    21  
    22  	"github.com/pulumi/pulumi/pkg/v3/operations"
    23  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    24  	"github.com/pulumi/pulumi/pkg/v3/secrets"
    25  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    26  	"github.com/pulumi/pulumi/sdk/v3/go/common/display"
    27  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    29  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/gitutil"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    33  )
    34  
    35  // Stack is used to manage stacks of resources against a pluggable backend.
    36  type Stack interface {
    37  	Ref() StackReference                                    // this stack's identity.
    38  	Snapshot(ctx context.Context) (*deploy.Snapshot, error) // the latest deployment snapshot.
    39  	Backend() Backend                                       // the backend this stack belongs to.
    40  	Tags() map[apitype.StackTagName]string                  // the stack's existing tags.
    41  
    42  	// Preview changes to this stack.
    43  	Preview(ctx context.Context, op UpdateOperation) (*deploy.Plan, display.ResourceChanges, result.Result)
    44  	// Update this stack.
    45  	Update(ctx context.Context, op UpdateOperation) (display.ResourceChanges, result.Result)
    46  	// Import resources into this stack.
    47  	Import(ctx context.Context, op UpdateOperation, imports []deploy.Import) (display.ResourceChanges, result.Result)
    48  	// Refresh this stack's state from the cloud provider.
    49  	Refresh(ctx context.Context, op UpdateOperation) (display.ResourceChanges, result.Result)
    50  	// Destroy this stack's resources.
    51  	Destroy(ctx context.Context, op UpdateOperation) (display.ResourceChanges, result.Result)
    52  	// Watch this stack.
    53  	Watch(ctx context.Context, op UpdateOperation, paths []string) result.Result
    54  
    55  	// remove this stack.
    56  	Remove(ctx context.Context, force bool) (bool, error)
    57  	// rename this stack.
    58  	Rename(ctx context.Context, newName tokens.QName) (StackReference, error)
    59  	// list log entries for this stack.
    60  	GetLogs(ctx context.Context, cfg StackConfiguration, query operations.LogQuery) ([]operations.LogEntry, error)
    61  	// export this stack's deployment.
    62  	ExportDeployment(ctx context.Context) (*apitype.UntypedDeployment, error)
    63  	// import the given deployment into this stack.
    64  	ImportDeployment(ctx context.Context, deployment *apitype.UntypedDeployment) error
    65  
    66  	// Return the default secrets manager to use for this stack.
    67  	DefaultSecretManager(configFile string) (secrets.Manager, error)
    68  }
    69  
    70  // RemoveStack returns the stack, or returns an error if it cannot.
    71  func RemoveStack(ctx context.Context, s Stack, force bool) (bool, error) {
    72  	return s.Backend().RemoveStack(ctx, s, force)
    73  }
    74  
    75  // RenameStack renames the stack, or returns an error if it cannot.
    76  func RenameStack(ctx context.Context, s Stack, newName tokens.QName) (StackReference, error) {
    77  	return s.Backend().RenameStack(ctx, s, newName)
    78  }
    79  
    80  // PreviewStack previews changes to this stack.
    81  func PreviewStack(
    82  	ctx context.Context,
    83  	s Stack,
    84  	op UpdateOperation) (*deploy.Plan, display.ResourceChanges, result.Result) {
    85  
    86  	return s.Backend().Preview(ctx, s, op)
    87  }
    88  
    89  // UpdateStack updates the target stack with the current workspace's contents (config and code).
    90  func UpdateStack(ctx context.Context, s Stack, op UpdateOperation) (display.ResourceChanges, result.Result) {
    91  	return s.Backend().Update(ctx, s, op)
    92  }
    93  
    94  // ImportStack updates the target stack with the current workspace's contents (config and code).
    95  func ImportStack(ctx context.Context, s Stack, op UpdateOperation,
    96  	imports []deploy.Import) (display.ResourceChanges, result.Result) {
    97  
    98  	return s.Backend().Import(ctx, s, op, imports)
    99  }
   100  
   101  // RefreshStack refresh's the stack's state from the cloud provider.
   102  func RefreshStack(ctx context.Context, s Stack, op UpdateOperation) (display.ResourceChanges, result.Result) {
   103  	return s.Backend().Refresh(ctx, s, op)
   104  }
   105  
   106  // DestroyStack destroys all of this stack's resources.
   107  func DestroyStack(ctx context.Context, s Stack, op UpdateOperation) (display.ResourceChanges, result.Result) {
   108  	return s.Backend().Destroy(ctx, s, op)
   109  }
   110  
   111  // WatchStack watches the projects working directory for changes and automatically updates the
   112  // active stack.
   113  func WatchStack(ctx context.Context, s Stack, op UpdateOperation, paths []string) result.Result {
   114  	return s.Backend().Watch(ctx, s, op, paths)
   115  }
   116  
   117  // GetLatestConfiguration returns the configuration for the most recent deployment of the stack.
   118  func GetLatestConfiguration(ctx context.Context, s Stack) (config.Map, error) {
   119  	return s.Backend().GetLatestConfiguration(ctx, s)
   120  }
   121  
   122  // GetStackLogs fetches a list of log entries for the current stack in the current backend.
   123  func GetStackLogs(ctx context.Context, s Stack, cfg StackConfiguration,
   124  	query operations.LogQuery) ([]operations.LogEntry, error) {
   125  	return s.Backend().GetLogs(ctx, s, cfg, query)
   126  }
   127  
   128  // ExportStackDeployment exports the given stack's deployment as an opaque JSON message.
   129  func ExportStackDeployment(
   130  	ctx context.Context,
   131  	s Stack) (*apitype.UntypedDeployment, error) {
   132  
   133  	return s.Backend().ExportDeployment(ctx, s)
   134  }
   135  
   136  // ImportStackDeployment imports the given deployment into the indicated stack.
   137  func ImportStackDeployment(ctx context.Context, s Stack, deployment *apitype.UntypedDeployment) error {
   138  	return s.Backend().ImportDeployment(ctx, s, deployment)
   139  }
   140  
   141  // UpdateStackTags updates the stacks's tags, replacing all existing tags.
   142  func UpdateStackTags(ctx context.Context, s Stack, tags map[apitype.StackTagName]string) error {
   143  	return s.Backend().UpdateStackTags(ctx, s, tags)
   144  }
   145  
   146  // GetMergedStackTags returns the stack's existing tags merged with fresh tags from the environment
   147  // and Pulumi.yaml file.
   148  func GetMergedStackTags(ctx context.Context, s Stack) (map[apitype.StackTagName]string, error) {
   149  	// Get the stack's existing tags.
   150  	tags := s.Tags()
   151  	if tags == nil {
   152  		tags = make(map[apitype.StackTagName]string)
   153  	}
   154  
   155  	// Get latest environment tags for the current stack.
   156  	envTags, err := GetEnvironmentTagsForCurrentStack()
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	// Add each new environment tag to the existing tags, overwriting existing tags with the
   162  	// latest values.
   163  	for k, v := range envTags {
   164  		tags[k] = v
   165  	}
   166  
   167  	return tags, nil
   168  }
   169  
   170  // GetEnvironmentTagsForCurrentStack returns the set of tags for the "current" stack, based on the environment
   171  // and Pulumi.yaml file.
   172  func GetEnvironmentTagsForCurrentStack() (map[apitype.StackTagName]string, error) {
   173  	tags := make(map[apitype.StackTagName]string)
   174  
   175  	// Tags based on Pulumi.yaml.
   176  	projPath, err := workspace.DetectProjectPath()
   177  	if err != nil {
   178  		// No current stack return empty
   179  		return make(map[apitype.StackTagName]string), nil
   180  	}
   181  	if projPath != "" {
   182  		proj, err := workspace.LoadProject(projPath)
   183  		if err != nil {
   184  			return nil, fmt.Errorf("error loading project %q: %w", projPath, err)
   185  		}
   186  		tags[apitype.ProjectNameTag] = proj.Name.String()
   187  		tags[apitype.ProjectRuntimeTag] = proj.Runtime.Name()
   188  		if proj.Description != nil {
   189  			tags[apitype.ProjectDescriptionTag] = *proj.Description
   190  		}
   191  
   192  		// Add the git metadata to the tags, ignoring any errors that come from it.
   193  		ignoredErr := addGitMetadataToStackTags(tags, projPath)
   194  		contract.IgnoreError(ignoredErr)
   195  	}
   196  
   197  	return tags, nil
   198  }
   199  
   200  // addGitMetadataToStackTags fetches the git repository from the directory, and attempts to detect
   201  // and add any relevant git metadata as stack tags.
   202  func addGitMetadataToStackTags(tags map[apitype.StackTagName]string, projPath string) error {
   203  	repo, err := gitutil.GetGitRepository(filepath.Dir(projPath))
   204  	if repo == nil {
   205  		return fmt.Errorf("no git repository found from %v", projPath)
   206  	}
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	remoteURL, err := gitutil.GetGitRemoteURL(repo, "origin")
   212  
   213  	if err != nil {
   214  		return err
   215  	}
   216  	if remoteURL == "" {
   217  		return nil
   218  	}
   219  
   220  	if vcsInfo, err := gitutil.TryGetVCSInfo(remoteURL); err == nil {
   221  		tags[apitype.VCSOwnerNameTag] = vcsInfo.Owner
   222  		tags[apitype.VCSRepositoryNameTag] = vcsInfo.Repo
   223  		tags[apitype.VCSRepositoryKindTag] = vcsInfo.Kind
   224  	} else {
   225  		return fmt.Errorf("detecting VCS info for stack tags for remote %v: %w", remoteURL, err)
   226  	}
   227  	// Set the old stack tags keys as GitHub so that the UI will continue to work,
   228  	// regardless of whether the remote URL is a GitHub URL or not.
   229  	// TODO remove these when the UI no longer needs them.
   230  	if tags[apitype.VCSOwnerNameTag] != "" {
   231  		tags[apitype.GitHubOwnerNameTag] = tags[apitype.VCSOwnerNameTag]
   232  		tags[apitype.GitHubRepositoryNameTag] = tags[apitype.VCSRepositoryNameTag]
   233  	}
   234  
   235  	return nil
   236  }