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 }