pulumi/sdk/go/auto/local_workspace.go

1431 lines
52 KiB
Go
Raw Normal View History

// Copyright 2016-2023, Pulumi Corporation.
2020-08-27 17:43:23 +00:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2020-08-19 18:13:42 +00:00
package auto
import (
"context"
2020-08-20 04:23:53 +00:00
"encoding/json"
"errors"
2020-08-19 18:13:42 +00:00
"fmt"
"io"
2020-08-19 18:13:42 +00:00
"os"
"path/filepath"
"strings"
"github.com/blang/semver"
"github.com/pulumi/pulumi/sdk/v3/go/auto/optremove"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/env"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
2020-08-19 18:13:42 +00:00
)
2020-08-28 21:21:56 +00:00
// LocalWorkspace is a default implementation of the Workspace interface.
2020-08-25 18:16:54 +00:00
// A Workspace is the execution context containing a single Pulumi project, a program,
// and multiple stacks. Workspaces are used to manage the execution environment,
// providing various utilities such as plugin installation, environment configuration
// ($PULUMI_HOME), and creation, deletion, and listing of Stacks.
2020-08-28 21:21:56 +00:00
// LocalWorkspace relies on Pulumi.yaml and Pulumi.<stack>.yaml as the intermediate format
2020-08-25 18:16:54 +00:00
// for Project and Stack settings. Modifying ProjectSettings will
2020-08-28 21:21:56 +00:00
// alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.<stack>.yaml file.
2020-08-25 18:16:54 +00:00
// This is identical to the behavior of Pulumi CLI driven workspaces.
2020-08-19 18:13:42 +00:00
type LocalWorkspace struct {
workDir string
pulumiHome string
program pulumi.RunFunc
envvars map[string]string
secretsProvider string
repo *GitRepo
remote bool
remoteEnvVars map[string]EnvVarValue
preRunCommands []string
remoteSkipInstallDependencies bool
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
remoteInheritSettings bool
pulumiCommand PulumiCommand
[auto/go] Support for remote deployment executor image (#15697) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Adds Go Automation API support for specifying custom executor images for remote deployments. Part of: https://github.com/pulumi/pulumi/issues/15693 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Thomas Gummerer <t.gummerer@gmail.com>
2024-03-15 06:43:06 +00:00
remoteExecutorImage *ExecutorImage
2020-08-19 18:13:42 +00:00
}
var settingsExtensions = []string{".yaml", ".yml", ".json"}
2020-08-25 18:16:54 +00:00
// ProjectSettings returns the settings object for the current project if any
2020-08-28 21:21:56 +00:00
// LocalWorkspace reads settings from the Pulumi.yaml in the workspace.
2020-08-25 18:16:54 +00:00
// A workspace can contain only a single project at a time.
func (l *LocalWorkspace) ProjectSettings(ctx context.Context) (*workspace.Project, error) {
return readProjectSettingsFromDir(ctx, l.WorkDir())
2020-08-19 18:13:42 +00:00
}
2020-08-29 02:10:39 +00:00
// SaveProjectSettings overwrites the settings object in the current project.
2020-08-25 18:16:54 +00:00
// There can only be a single project per workspace. Fails is new project name does not match old.
2020-08-28 21:21:56 +00:00
// LocalWorkspace writes this value to a Pulumi.yaml file in Workspace.WorkDir().
2020-08-29 02:10:39 +00:00
func (l *LocalWorkspace) SaveProjectSettings(ctx context.Context, settings *workspace.Project) error {
2020-08-21 16:49:46 +00:00
pulumiYamlPath := filepath.Join(l.WorkDir(), "Pulumi.yaml")
2020-08-19 18:13:42 +00:00
return settings.Save(pulumiYamlPath)
}
// StackSettings returns the settings object for the stack matching the specified stack name if any.
2020-08-28 21:21:56 +00:00
// LocalWorkspace reads this from a Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) StackSettings(ctx context.Context, stackName string) (*workspace.ProjectStack, error) {
project, err := l.ProjectSettings(ctx)
if err != nil {
return nil, err
}
name := getStackSettingsName(stackName)
2020-08-19 18:13:42 +00:00
for _, ext := range settingsExtensions {
stackPath := filepath.Join(l.WorkDir(), fmt.Sprintf("Pulumi.%s%s", name, ext))
2021-08-31 03:56:08 +00:00
if _, err := os.Stat(stackPath); err == nil {
proj, err := workspace.LoadProjectStack(project, stackPath)
2020-08-19 18:13:42 +00:00
if err != nil {
return nil, fmt.Errorf("found stack settings, but failed to load: %w", err)
2020-08-19 18:13:42 +00:00
}
return proj, nil
}
}
return nil, fmt.Errorf("unable to find stack settings in workspace for %s", stackName)
2020-08-19 18:13:42 +00:00
}
// SaveStackSettings overwrites the settings object for the stack matching the specified stack name.
2020-08-28 21:21:56 +00:00
// LocalWorkspace writes this value to a Pulumi.<stack>.yaml file in Workspace.WorkDir()
2020-08-29 02:10:39 +00:00
func (l *LocalWorkspace) SaveStackSettings(
ctx context.Context,
stackName string,
settings *workspace.ProjectStack,
) error {
name := getStackSettingsName(stackName)
stackYamlPath := filepath.Join(l.WorkDir(), fmt.Sprintf("Pulumi.%s.yaml", name))
err := settings.Save(stackYamlPath)
2020-08-19 18:13:42 +00:00
if err != nil {
return fmt.Errorf("failed to save stack setttings for %s: %w", stackName, err)
2020-08-19 18:13:42 +00:00
}
return nil
}
2020-08-25 18:16:54 +00:00
// SerializeArgsForOp is hook to provide additional args to every CLI commands before they are executed.
// Provided with stack name,
2020-08-25 18:16:54 +00:00
// returns a list of args to append to an invoked command ["--config=...", ]
// LocalWorkspace does not utilize this extensibility point.
func (l *LocalWorkspace) SerializeArgsForOp(ctx context.Context, stackName string) ([]string, error) {
2020-08-19 18:13:42 +00:00
// not utilized for LocalWorkspace
return nil, nil
}
// PostCommandCallback is a hook executed after every command. Called with the stack name.
2020-08-29 02:10:39 +00:00
// An extensibility point to perform workspace cleanup (CLI operations may create/modify a Pulumi.stack.yaml)
2020-08-25 18:16:54 +00:00
// LocalWorkspace does not utilize this extensibility point.
func (l *LocalWorkspace) PostCommandCallback(ctx context.Context, stackName string) error {
2020-08-19 18:13:42 +00:00
// not utilized for LocalWorkspace
return nil
}
[Automation API / Go] - Environment functions (#14785) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Add go automation API support for adding and removing environments for stack configuration. Fixes https://github.com/pulumi/pulumi/issues/14794 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-11 17:29:13 +00:00
// AddEnvironments adds environments to the end of a stack's import list. Imported environments are merged in order
// per the ESC merge rules. The list of environments behaves as if it were the import list in an anonymous
// environment.
func (l *LocalWorkspace) AddEnvironments(ctx context.Context, stackName string, envs ...string) error {
// 3.95 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.95.0)
if l.pulumiCommand.Version().LT(semver.Version{Major: 3, Minor: 95}) {
return errors.New("AddEnvironments requires Pulumi CLI version >= 3.95.0")
[Automation API / Go] - Environment functions (#14785) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Add go automation API support for adding and removing environments for stack configuration. Fixes https://github.com/pulumi/pulumi/issues/14794 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-11 17:29:13 +00:00
}
args := []string{"config", "env", "add"}
args = append(args, envs...)
args = append(args, "--yes", "--stack", stackName)
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return newAutoError(fmt.Errorf("unable to add environments: %w", err), stdout, stderr, errCode)
}
return nil
}
Automation API support for listing environments (#14995) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Automation API support for `pulumi config env ls` Fixes https://github.com/pulumi/pulumi/issues/14797 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-22 05:18:14 +00:00
// ListEnvironments returns the list of environments from the provided stack's configuration.
func (l *LocalWorkspace) ListEnvironments(ctx context.Context, stackName string) ([]string, error) {
// 3.99 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.99.0)
if l.pulumiCommand.Version().LT(semver.Version{Major: 3, Minor: 99}) {
return nil, errors.New("ListEnvironments requires Pulumi CLI version >= 3.99.0")
Automation API support for listing environments (#14995) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Automation API support for `pulumi config env ls` Fixes https://github.com/pulumi/pulumi/issues/14797 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-22 05:18:14 +00:00
}
args := []string{"config", "env", "ls", "--stack", stackName, "--json"}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return nil, newAutoError(fmt.Errorf("unable to list environments: %w", err), stdout, stderr, errCode)
}
var envs []string
err = json.Unmarshal([]byte(stdout), &envs)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal environments: %w", err)
}
return envs, nil
}
[Automation API / Go] - Environment functions (#14785) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Add go automation API support for adding and removing environments for stack configuration. Fixes https://github.com/pulumi/pulumi/issues/14794 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-11 17:29:13 +00:00
// RemoveEnvironment removes an environment from a stack's configuration.
func (l *LocalWorkspace) RemoveEnvironment(ctx context.Context, stackName string, env string) error {
// 3.95 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.95.0)
if l.pulumiCommand.Version().LT(semver.Version{Major: 3, Minor: 95}) {
return errors.New("RemoveEnvironments requires Pulumi CLI version >= 3.95.0")
[Automation API / Go] - Environment functions (#14785) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Add go automation API support for adding and removing environments for stack configuration. Fixes https://github.com/pulumi/pulumi/issues/14794 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-11 17:29:13 +00:00
}
args := []string{"config", "env", "rm", env, "--yes", "--stack", stackName}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return newAutoError(fmt.Errorf("unable to remove environment: %w", err), stdout, stderr, errCode)
}
return nil
}
// GetConfig returns the value associated with the specified stack name and key,
2020-08-29 02:10:39 +00:00
// scoped to the current workspace. LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
func (l *LocalWorkspace) GetConfig(ctx context.Context, stackName string, key string) (ConfigValue, error) {
return l.GetConfigWithOptions(ctx, stackName, key, nil)
}
// GetConfigWithOptions returns the value associated with the specified stack name and key,
// using the optional ConfigOptions, scoped to the current workspace.
// LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
func (l *LocalWorkspace) GetConfigWithOptions(
ctx context.Context, stackName string, key string, opts *ConfigOptions,
) (ConfigValue, error) {
2020-08-20 04:23:53 +00:00
var val ConfigValue
args := []string{"config", "get"}
if opts != nil {
if opts.Path {
args = append(args, "--path")
}
}
args = append(args, key, "--json", "--stack", stackName)
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
2020-08-20 04:23:53 +00:00
if err != nil {
return val, newAutoError(fmt.Errorf("unable to read config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
err = json.Unmarshal([]byte(stdout), &val)
if err != nil {
return val, fmt.Errorf("unable to unmarshal config value: %w", err)
2020-08-20 04:23:53 +00:00
}
2020-08-19 18:13:42 +00:00
return val, nil
}
// GetAllConfig returns the config map for the specified stack name, scoped to the current workspace.
2020-08-29 02:10:39 +00:00
// LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
func (l *LocalWorkspace) GetAllConfig(ctx context.Context, stackName string) (ConfigMap, error) {
2020-08-20 04:23:53 +00:00
var val ConfigMap
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "config", "--show-secrets", "--json", "--stack", stackName)
2020-08-20 04:23:53 +00:00
if err != nil {
return val, newAutoError(fmt.Errorf("unable to read config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
err = json.Unmarshal([]byte(stdout), &val)
if err != nil {
return val, fmt.Errorf("unable to unmarshal config value: %w", err)
2020-08-20 04:23:53 +00:00
}
return val, nil
}
// SetConfig sets the specified key-value pair on the provided stack name.
2020-08-28 21:21:56 +00:00
// LocalWorkspace writes this value to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) SetConfig(ctx context.Context, stackName string, key string, val ConfigValue) error {
return l.SetConfigWithOptions(ctx, stackName, key, val, nil)
}
// SetConfigWithOptions sets the specified key-value pair on the provided stack name using the optional ConfigOptions.
// LocalWorkspace writes this value to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) SetConfigWithOptions(
ctx context.Context, stackName string, key string, val ConfigValue, opts *ConfigOptions,
) error {
args := []string{"config", "set", "--stack", stackName}
if opts != nil {
if opts.Path {
args = append(args, "--path")
}
}
2020-08-20 04:23:53 +00:00
secretArg := "--plaintext"
if val.Secret {
secretArg = "--secret"
}
args = append(args, key, secretArg, "--non-interactive", "--", val.Value)
2020-08-20 04:23:53 +00:00
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("unable to set config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
return nil
2020-08-19 18:13:42 +00:00
}
// SetAllConfig sets all values in the provided config map for the specified stack name.
2020-08-28 21:21:56 +00:00
// LocalWorkspace writes the config to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) SetAllConfig(ctx context.Context, stackName string, config ConfigMap) error {
return l.SetAllConfigWithOptions(ctx, stackName, config, nil)
}
// SetAllConfigWithOptions sets all values in the provided config map for the specified stack name
// using the optional ConfigOptions.
// LocalWorkspace writes the config to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) SetAllConfigWithOptions(
ctx context.Context, stackName string, config ConfigMap, opts *ConfigOptions,
) error {
args := []string{"config", "set-all", "--stack", stackName}
if opts != nil {
if opts.Path {
args = append(args, "--path")
}
}
2020-08-20 04:23:53 +00:00
for k, v := range config {
secretArg := "--plaintext"
if v.Secret {
secretArg = "--secret"
2020-08-20 04:23:53 +00:00
}
args = append(args, secretArg, fmt.Sprintf("%s=%s", k, v.Value))
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return newAutoError(fmt.Errorf("unable to set config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
2020-08-19 18:13:42 +00:00
return nil
}
// RemoveConfig removes the specified key-value pair on the provided stack name.
2020-08-28 21:21:56 +00:00
// It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) RemoveConfig(ctx context.Context, stackName string, key string) error {
return l.RemoveConfigWithOptions(ctx, stackName, key, nil)
}
// RemoveConfigWithOptions removes the specified key-value pair on the provided stack name.
// It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) RemoveConfigWithOptions(
ctx context.Context, stackName string, key string, opts *ConfigOptions,
) error {
args := []string{"config", "rm"}
if opts != nil {
if opts.Path {
args = append(args, "--path")
}
}
args = append(args, key, "--stack", stackName)
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("could not remove config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
2020-08-19 18:13:42 +00:00
return nil
}
// RemoveAllConfig removes all values in the provided key list for the specified stack name
2020-08-28 21:21:56 +00:00
// It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) RemoveAllConfig(ctx context.Context, stackName string, keys []string) error {
return l.RemoveAllConfigWithOptions(ctx, stackName, keys, nil)
}
// RemoveAllConfigWithOptions removes all values in the provided key list for the specified stack name
// using the optional ConfigOptions
// It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) RemoveAllConfigWithOptions(
ctx context.Context, stackName string, keys []string, opts *ConfigOptions,
) error {
args := []string{"config", "rm-all", "--stack", stackName}
if opts != nil {
if opts.Path {
args = append(args, "--path")
}
}
args = append(args, keys...)
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return newAutoError(fmt.Errorf("unable to set config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
return nil
}
// RefreshConfig gets and sets the config map used with the last Update for Stack matching stack name.
2020-08-28 21:21:56 +00:00
// It will overwrite all configuration in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
func (l *LocalWorkspace) RefreshConfig(ctx context.Context, stackName string) (ConfigMap, error) {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "config", "refresh", "--force", "--stack", stackName)
2020-08-20 04:23:53 +00:00
if err != nil {
return nil, newAutoError(fmt.Errorf("could not refresh config: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
cfg, err := l.GetAllConfig(ctx, stackName)
2020-08-20 04:23:53 +00:00
if err != nil {
return nil, fmt.Errorf("could not fetch config after refresh: %w", err)
2020-08-20 04:23:53 +00:00
}
return cfg, nil
2020-08-19 18:13:42 +00:00
}
// GetTag returns the value associated with the specified stack name and key.
func (l *LocalWorkspace) GetTag(ctx context.Context, stackName string, key string) (string, error) {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "tag", "get", key, "--stack", stackName)
if err != nil {
return stdout, newAutoError(fmt.Errorf("unable to read tag: %w", err), stdout, stderr, errCode)
}
return strings.TrimSpace(stdout), nil
}
// SetTag sets the specified key-value pair on the provided stack name.
func (l *LocalWorkspace) SetTag(ctx context.Context, stackName string, key string, value string) error {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "tag", "set", key, value, "--stack", stackName)
if err != nil {
return newAutoError(fmt.Errorf("unable to set tag: %w", err), stdout, stderr, errCode)
}
return nil
}
// RemoveTag removes the specified key-value pair on the provided stack name.
func (l *LocalWorkspace) RemoveTag(ctx context.Context, stackName string, key string) error {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "tag", "rm", key, "--stack", stackName)
if err != nil {
return newAutoError(fmt.Errorf("could not remove tag: %w", err), stdout, stderr, errCode)
}
return nil
}
// ListTags Returns the tag map for the specified stack name.
func (l *LocalWorkspace) ListTags(ctx context.Context, stackName string) (map[string]string, error) {
var vals map[string]string
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "tag", "ls", "--json", "--stack", stackName)
if err != nil {
return vals, newAutoError(fmt.Errorf("unable to read tags: %w", err), stdout, stderr, errCode)
}
err = json.Unmarshal([]byte(stdout), &vals)
if err != nil {
return vals, fmt.Errorf("unable to unmarshal tag values: %w", err)
}
return vals, nil
}
// GetEnvVars returns the environment values scoped to the current workspace.
func (l *LocalWorkspace) GetEnvVars() map[string]string {
if l.envvars == nil {
return nil
}
return l.envvars
}
// SetEnvVars sets the specified map of environment values scoped to the current workspace.
// These values will be passed to all Workspace and Stack level commands.
func (l *LocalWorkspace) SetEnvVars(envvars map[string]string) error {
return setEnvVars(l, envvars)
}
func setEnvVars(l *LocalWorkspace, envvars map[string]string) error {
if envvars == nil {
return errors.New("unable to set nil environment values")
}
if l.envvars == nil {
l.envvars = map[string]string{}
}
for k, v := range envvars {
l.envvars[k] = v
}
return nil
}
// SetEnvVar sets the specified environment value scoped to the current workspace.
// This value will be passed to all Workspace and Stack level commands.
func (l *LocalWorkspace) SetEnvVar(key, value string) {
if l.envvars == nil {
l.envvars = map[string]string{}
}
l.envvars[key] = value
}
// UnsetEnvVar unsets the specified environment value scoped to the current workspace.
// This value will be removed from all Workspace and Stack level commands.
func (l *LocalWorkspace) UnsetEnvVar(key string) {
if l.envvars == nil {
return
}
delete(l.envvars, key)
}
2020-08-25 18:16:54 +00:00
// WorkDir returns the working directory to run Pulumi CLI commands.
// LocalWorkspace expects that this directory contains a Pulumi.yaml file.
2020-08-28 21:21:56 +00:00
// For "Inline" Pulumi programs created from NewStackInlineSource, a Pulumi.yaml
2020-08-25 18:16:54 +00:00
// is created on behalf of the user if none is specified.
2020-08-19 18:13:42 +00:00
func (l *LocalWorkspace) WorkDir() string {
return l.workDir
}
// PulumiCommand returns the PulumiCommand instance that is used to execute commands.
func (l *LocalWorkspace) PulumiCommand() PulumiCommand {
return l.pulumiCommand
}
2020-08-25 18:16:54 +00:00
// PulumiHome returns the directory override for CLI metadata if set.
2020-08-29 02:10:39 +00:00
// This customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed.
func (l *LocalWorkspace) PulumiHome() string {
2020-08-19 18:13:42 +00:00
return l.pulumiHome
}
// PulumiVersion returns the version of the underlying Pulumi CLI/Engine.
func (l *LocalWorkspace) PulumiVersion() string {
return l.pulumiCommand.Version().String()
}
2020-08-25 18:16:54 +00:00
// WhoAmI returns the currently authenticated user
func (l *LocalWorkspace) WhoAmI(ctx context.Context) (string, error) {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "whoami")
2020-08-20 04:23:53 +00:00
if err != nil {
return "", newAutoError(fmt.Errorf("could not determine authenticated user: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
return strings.TrimSpace(stdout), nil
2020-08-19 18:13:42 +00:00
}
// WhoAmIDetails returns detailed information about the currently
// logged-in Pulumi identity.
func (l *LocalWorkspace) WhoAmIDetails(ctx context.Context) (WhoAmIResult, error) {
// 3.58 added the --json flag (https://github.com/pulumi/pulumi/releases/tag/v3.58.0)
if l.pulumiCommand.Version().GTE(semver.Version{Major: 3, Minor: 58}) {
var whoAmIDetailedInfo WhoAmIResult
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "whoami", "--json")
if err != nil {
return whoAmIDetailedInfo, newAutoError(
fmt.Errorf("could not retrieve WhoAmIDetailedInfo: %w", err), stdout, stderr, errCode)
}
err = json.Unmarshal([]byte(stdout), &whoAmIDetailedInfo)
if err != nil {
return whoAmIDetailedInfo, fmt.Errorf("unable to unmarshal WhoAmIDetailedInfo: %w", err)
}
return whoAmIDetailedInfo, nil
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "whoami")
if err != nil {
return WhoAmIResult{}, newAutoError(
fmt.Errorf("could not determine authenticated user: %w", err), stdout, stderr, errCode)
}
return WhoAmIResult{User: strings.TrimSpace(stdout)}, nil
}
2020-08-25 18:16:54 +00:00
// Stack returns a summary of the currently selected stack, if any.
func (l *LocalWorkspace) Stack(ctx context.Context) (*StackSummary, error) {
stacks, err := l.ListStacks(ctx)
2020-08-20 04:23:53 +00:00
if err != nil {
return nil, fmt.Errorf("could not determine selected stack: %w", err)
2020-08-20 04:23:53 +00:00
}
for _, s := range stacks {
if s.Current {
return &s, nil
}
}
2020-08-19 18:13:42 +00:00
return nil, nil
}
// ChangeStackSecretsProvider edits the secrets provider for the given stack.
func (l *LocalWorkspace) ChangeStackSecretsProvider(
ctx context.Context, stackName, newSecretsProvider string, opts *ChangeSecretsProviderOptions,
) error {
args := []string{"stack", "change-secrets-provider", "--stack", stackName, newSecretsProvider}
var reader io.Reader
if newSecretsProvider == "passphrase" {
if opts == nil || opts.NewPassphrase == nil {
return errors.New("new passphrase must be provided")
}
reader = strings.NewReader(*opts.NewPassphrase)
}
stdout, stderr, errCode, err := l.runPulumiInputCmdSync(ctx, reader, args...)
if err != nil {
return newAutoError(fmt.Errorf("failed to change secrets provider: %w", err), stdout, stderr, errCode)
}
return nil
}
// CreateStack creates and sets a new stack with the stack name, failing if one already exists.
func (l *LocalWorkspace) CreateStack(ctx context.Context, stackName string) error {
args := []string{"stack", "init", stackName}
if l.secretsProvider != "" {
args = append(args, "--secrets-provider", l.secretsProvider)
}
if l.remote {
args = append(args, "--no-select")
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("failed to create stack: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
return nil
2020-08-19 18:13:42 +00:00
}
// SelectStack selects and sets an existing stack matching the stack name, failing if none exists.
func (l *LocalWorkspace) SelectStack(ctx context.Context, stackName string) error {
// If this is a remote workspace, we don't want to actually select the stack (which would modify global state);
// but we will ensure the stack exists by calling `pulumi stack`.
args := []string{"stack"}
if !l.remote {
args = append(args, "select")
}
args = append(args, "--stack", stackName)
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("failed to select stack: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
return nil
2020-08-19 18:13:42 +00:00
}
2020-08-25 18:16:54 +00:00
// RemoveStack deletes the stack and all associated configuration and history.
2021-08-02 19:54:46 +00:00
func (l *LocalWorkspace) RemoveStack(ctx context.Context, stackName string, opts ...optremove.Option) error {
args := []string{"stack", "rm", "--yes", stackName}
optRemoveOpts := &optremove.Options{}
for _, o := range opts {
o.ApplyOption(optRemoveOpts)
}
if optRemoveOpts.Force {
args = append(args, "--force")
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("failed to remove stack: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
return nil
}
2020-08-25 18:16:54 +00:00
// ListStacks returns all Stacks created under the current Project.
2020-08-28 21:21:56 +00:00
// This queries underlying backend and may return stacks not present in the Workspace (as Pulumi.<stack>.yaml files).
func (l *LocalWorkspace) ListStacks(ctx context.Context) ([]StackSummary, error) {
2020-08-20 04:23:53 +00:00
var stacks []StackSummary
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "ls", "--json")
2020-08-20 04:23:53 +00:00
if err != nil {
return stacks, newAutoError(fmt.Errorf("could not list stacks: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
err = json.Unmarshal([]byte(stdout), &stacks)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal config value: %w", err)
2020-08-20 04:23:53 +00:00
}
return stacks, nil
}
2020-08-28 21:21:56 +00:00
// InstallPlugin acquires the plugin matching the specified name and version.
func (l *LocalWorkspace) InstallPlugin(ctx context.Context, name string, version string) error {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "plugin", "install", "resource", name, version)
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("failed to install plugin: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
2020-08-19 18:13:42 +00:00
return nil
}
// InstallPluginFromServer acquires the plugin matching the specified name and version from a third party server.
2022-10-11 18:03:03 +00:00
func (l *LocalWorkspace) InstallPluginFromServer(
ctx context.Context,
name string,
version string,
server string,
) error {
2022-10-11 17:53:00 +00:00
stdout, stderr, errCode, err := l.runPulumiCmdSync(
ctx, "plugin", "install", "resource", name, version, "--server", server)
if err != nil {
return newAutoError(fmt.Errorf("failed to install plugin: %w", err), stdout, stderr, errCode)
}
return nil
}
2020-08-28 21:21:56 +00:00
// RemovePlugin deletes the plugin matching the specified name and verision.
func (l *LocalWorkspace) RemovePlugin(ctx context.Context, name string, version string) error {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "plugin", "rm", "resource", name, version, "--yes")
2020-08-20 04:23:53 +00:00
if err != nil {
return newAutoError(fmt.Errorf("failed to remove plugin: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
2020-08-19 18:13:42 +00:00
return nil
}
2020-08-25 18:16:54 +00:00
// ListPlugins lists all installed plugins.
func (l *LocalWorkspace) ListPlugins(ctx context.Context) ([]workspace.PluginInfo, error) {
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "plugin", "ls", "--json")
2020-08-20 04:23:53 +00:00
if err != nil {
return nil, newAutoError(fmt.Errorf("could not list list: %w", err), stdout, stderr, errCode)
2020-08-20 04:23:53 +00:00
}
var plugins []workspace.PluginInfo
err = json.Unmarshal([]byte(stdout), &plugins)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal plugin response: %w", err)
2020-08-20 04:23:53 +00:00
}
return plugins, nil
2020-08-19 18:13:42 +00:00
}
2020-08-25 18:16:54 +00:00
// Program returns the program `pulumi.RunFunc` to be used for Preview/Update if any.
2020-08-28 21:21:56 +00:00
// If none is specified, the stack will refer to ProjectSettings for this information.
func (l *LocalWorkspace) Program() pulumi.RunFunc {
return l.program
}
2020-08-28 21:21:56 +00:00
// SetProgram sets the program associated with the Workspace to the specified `pulumi.RunFunc`.
func (l *LocalWorkspace) SetProgram(fn pulumi.RunFunc) {
l.program = fn
}
// ExportStack exports the deployment state of the stack matching the given name.
// This can be combined with ImportStack to edit a stack's state (such as recovery from failed deployments).
func (l *LocalWorkspace) ExportStack(ctx context.Context, stackName string) (apitype.UntypedDeployment, error) {
var state apitype.UntypedDeployment
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "export", "--show-secrets", "--stack", stackName)
if err != nil {
return state, newAutoError(fmt.Errorf("could not export stack: %w", err), stdout, stderr, errCode)
}
err = json.Unmarshal([]byte(stdout), &state)
if err != nil {
return state, newAutoError(
fmt.Errorf("failed to export stack, unable to unmarshall stack state: %w", err), stdout, stderr, errCode,
)
}
return state, nil
}
// ImportStack imports the specified deployment state into a pre-existing stack.
// This can be combined with ExportStack to edit a stack's state (such as recovery from failed deployments).
func (l *LocalWorkspace) ImportStack(ctx context.Context, stackName string, state apitype.UntypedDeployment) error {
f, err := os.CreateTemp(os.TempDir(), "")
if err != nil {
return fmt.Errorf("could not import stack. failed to allocate temp file: %w", err)
}
defer func() { contract.IgnoreError(os.Remove(f.Name())) }()
bytes, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("could not import stack, failed to marshal stack state: %w", err)
}
_, err = f.Write(bytes)
if err != nil {
return fmt.Errorf("could not import stack. failed to write out stack intermediate: %w", err)
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "import", "--file", f.Name(), "--stack", stackName)
if err != nil {
return newAutoError(fmt.Errorf("could not import stack: %w", err), stdout, stderr, errCode)
}
return nil
}
// StackOutputs gets the current set of Stack outputs from the last Stack.Up().
func (l *LocalWorkspace) StackOutputs(ctx context.Context, stackName string) (OutputMap, error) {
// standard outputs
outStdout, outStderr, code, err := l.runPulumiCmdSync(ctx, "stack", "output", "--json", "--stack", stackName)
if err != nil {
return nil, newAutoError(fmt.Errorf("could not get outputs: %w", err), outStdout, outStderr, code)
}
// secret outputs
secretStdout, secretStderr, code, err := l.runPulumiCmdSync(ctx,
"stack", "output", "--json", "--show-secrets", "--stack", stackName,
)
if err != nil {
return nil, newAutoError(fmt.Errorf("could not get secret outputs: %w", err), outStdout, outStderr, code)
}
var outputs map[string]interface{}
var secrets map[string]interface{}
if err = json.Unmarshal([]byte(outStdout), &outputs); err != nil {
return nil, fmt.Errorf("error unmarshalling outputs: %s: %w", secretStderr, err)
}
if err = json.Unmarshal([]byte(secretStdout), &secrets); err != nil {
return nil, fmt.Errorf("error unmarshalling secret outputs: %s: %w", secretStderr, err)
}
res := make(OutputMap)
for k, v := range secrets {
raw, err := json.Marshal(outputs[k])
if err != nil {
return nil, fmt.Errorf("error determining secretness: %s: %w", secretStderr, err)
}
rawString := string(raw)
isSecret := strings.Contains(rawString, secretSentinel)
res[k] = OutputValue{
Value: v,
Secret: isSecret,
}
}
return res, nil
}
func (l *LocalWorkspace) runPulumiInputCmdSync(
ctx context.Context,
stdin io.Reader,
args ...string,
) (string, string, int, error) {
2020-08-19 18:13:42 +00:00
var env []string
2020-08-29 02:10:39 +00:00
if l.PulumiHome() != "" {
homeEnv := fmt.Sprintf("%s=%s", pulumiHomeEnv, l.PulumiHome())
env = append(env, homeEnv)
2020-08-19 18:13:42 +00:00
}
if envvars := l.GetEnvVars(); envvars != nil {
for k, v := range envvars {
e := []string{k, v}
env = append(env, strings.Join(e, "="))
}
}
return l.PulumiCommand().Run(ctx,
l.WorkDir(),
stdin,
nil, /* additionalOutputs */
nil, /* additionalErrorOutputs */
env,
args...,
)
2020-08-19 18:13:42 +00:00
}
func (l *LocalWorkspace) runPulumiCmdSync(
ctx context.Context,
args ...string,
) (string, string, int, error) {
return l.runPulumiInputCmdSync(ctx, nil, args...)
}
// supportsPulumiCmdFlag runs a command with `--help` to see if the specified flag is found within the resulting
// output, in which case we assume the flag is supported.
func (l *LocalWorkspace) supportsPulumiCmdFlag(ctx context.Context, flag string, args ...string) (bool, error) {
env := []string{
"PULUMI_DEBUG_COMMANDS=true",
"PULUMI_EXPERIMENTAL=true",
}
// Run the command with `--help`, and then we'll look for the flag in the output.
stdout, _, _, err := l.PulumiCommand().Run(ctx, l.WorkDir(), nil, nil, nil, env, append(args, "--help")...)
if err != nil {
return false, err
}
// Does the help test in stdout mention the flag? If so, assume it's supported.
if strings.Contains(stdout, flag) {
return true, nil
}
return false, nil
}
2020-08-28 21:21:56 +00:00
// NewLocalWorkspace creates and configures a LocalWorkspace. LocalWorkspaceOptions can be used to
2020-08-25 18:16:54 +00:00
// configure things like the working directory, the program to execute, and to seed the directory with source code
// from a git repository.
func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Workspace, error) {
2020-08-19 18:13:42 +00:00
lwOpts := &localWorkspaceOptions{}
// for merging options, last specified value wins
for _, opt := range opts {
opt.applyLocalWorkspaceOption(lwOpts)
}
var workDir string
if lwOpts.WorkDir != "" {
workDir = lwOpts.WorkDir
} else {
dir, err := os.MkdirTemp("", "pulumi_auto")
2020-08-19 18:13:42 +00:00
if err != nil {
return nil, fmt.Errorf("unable to create tmp directory for workspace: %w", err)
2020-08-19 18:13:42 +00:00
}
workDir = dir
}
if lwOpts.Repo != nil && !lwOpts.Remote {
2020-08-19 18:13:42 +00:00
// now do the git clone
projDir, err := setupGitRepo(ctx, workDir, lwOpts.Repo)
2020-08-19 18:13:42 +00:00
if err != nil {
return nil, fmt.Errorf("failed to create workspace, unable to enlist in git repo: %w", err)
2020-08-19 18:13:42 +00:00
}
workDir = projDir
}
optOut := env.SkipVersionCheck.Value()
if val, ok := lwOpts.EnvVars[env.SkipVersionCheck.Var().Name()]; ok {
optOut = optOut || cmdutil.IsTruthy(val)
}
var pulumiCommand PulumiCommand
if lwOpts.PulumiCommand != nil {
pulumiCommand = lwOpts.PulumiCommand
} else {
p, err := NewPulumiCommand(&PulumiCommandOptions{SkipVersionCheck: optOut})
if err != nil {
return nil, err
}
pulumiCommand = p
}
2020-08-19 18:13:42 +00:00
var program pulumi.RunFunc
if lwOpts.Program != nil {
program = lwOpts.Program
}
l := &LocalWorkspace{
workDir: workDir,
preRunCommands: lwOpts.PreRunCommands,
program: program,
pulumiHome: lwOpts.PulumiHome,
remote: lwOpts.Remote,
remoteEnvVars: lwOpts.RemoteEnvVars,
remoteSkipInstallDependencies: lwOpts.RemoteSkipInstallDependencies,
[auto/go] Support for remote deployment executor image (#15697) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Adds Go Automation API support for specifying custom executor images for remote deployments. Part of: https://github.com/pulumi/pulumi/issues/15693 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Thomas Gummerer <t.gummerer@gmail.com>
2024-03-15 06:43:06 +00:00
remoteExecutorImage: lwOpts.RemoteExecutorImage,
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
remoteInheritSettings: lwOpts.RemoteInheritSettings,
repo: lwOpts.Repo,
pulumiCommand: pulumiCommand,
}
// If remote was specified, ensure the CLI supports it.
if !optOut && l.remote {
// See if `--remote` is present in `pulumi preview --help`'s output.
supportsRemote, err := l.supportsPulumiCmdFlag(ctx, "--remote", "preview")
if err != nil {
return nil, err
}
if !supportsRemote {
return nil, errors.New("Pulumi CLI does not support remote operations; please upgrade")
}
}
2020-08-19 18:13:42 +00:00
if lwOpts.Project != nil {
2020-08-29 02:10:39 +00:00
err := l.SaveProjectSettings(ctx, lwOpts.Project)
2020-08-19 18:13:42 +00:00
if err != nil {
return nil, fmt.Errorf("failed to create workspace, unable to save project settings: %w", err)
2020-08-19 18:13:42 +00:00
}
}
for stackName := range lwOpts.Stacks {
s := lwOpts.Stacks[stackName]
err := l.SaveStackSettings(ctx, stackName, &s)
2020-08-19 18:13:42 +00:00
if err != nil {
return nil, fmt.Errorf("failed to create workspace: %w", err)
2020-08-19 18:13:42 +00:00
}
}
// setup
if !lwOpts.Remote && lwOpts.Repo != nil && lwOpts.Repo.Setup != nil {
err := lwOpts.Repo.Setup(ctx, l)
if err != nil {
return nil, fmt.Errorf("error while running setup function: %w", err)
}
}
// Secrets providers
if lwOpts.SecretsProvider != "" {
l.secretsProvider = lwOpts.SecretsProvider
}
// Environment values
if lwOpts.EnvVars != nil {
if err := setEnvVars(l, lwOpts.EnvVars); err != nil {
return nil, fmt.Errorf("failed to set environment values: %w", err)
}
}
2020-08-19 18:13:42 +00:00
return l, nil
}
// EnvVarValue represents the value of an envvar. A value can be a secret, which is passed along
// to remote operations when used with remote workspaces, otherwise, it has no affect.
type EnvVarValue struct {
Value string
Secret bool
}
2020-08-19 18:13:42 +00:00
type localWorkspaceOptions struct {
// WorkDir is the directory to execute commands from and store state.
// Defaults to a tmp dir.
WorkDir string
// Program is the Pulumi Program to execute. If none is supplied,
2020-08-28 21:21:56 +00:00
// the program identified in $WORKDIR/Pulumi.yaml will be used instead.
2020-08-19 18:13:42 +00:00
Program pulumi.RunFunc
2020-08-29 02:10:39 +00:00
// PulumiHome overrides the metadata directory for pulumi commands.
// This customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed.
PulumiHome string
// PulumiCommand is the PulumiCommand instance to use. If none is
// supplied, the workspace will create an instance using the PulumiCommand
// CLI found in $PATH.
PulumiCommand PulumiCommand
2020-08-29 02:10:39 +00:00
// Project is the project settings for the workspace.
2020-08-19 18:13:42 +00:00
Project *workspace.Project
// Stacks is a map of [stackName -> stack settings objects] to seed the workspace.
2020-08-19 18:13:42 +00:00
Stacks map[string]workspace.ProjectStack
// Repo is a git repo with a Pulumi Project to clone into the WorkDir.
Repo *GitRepo
// Secrets Provider to use with the current Stack
SecretsProvider string
// EnvVars is a map of environment values scoped to the workspace.
// These values will be passed to all Workspace and Stack level commands.
EnvVars map[string]string
// Whether the workspace represents a remote workspace.
Remote bool
// Remote environment variables to be passed to the remote Pulumi operation.
RemoteEnvVars map[string]EnvVarValue
// PreRunCommands is an optional list of arbitrary commands to run before the remote Pulumi operation is invoked.
PreRunCommands []string
// RemoteSkipInstallDependencies sets whether to skip the default dependency installation step
RemoteSkipInstallDependencies bool
[auto/go] Support for remote deployment executor image (#15697) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Adds Go Automation API support for specifying custom executor images for remote deployments. Part of: https://github.com/pulumi/pulumi/issues/15693 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Thomas Gummerer <t.gummerer@gmail.com>
2024-03-15 06:43:06 +00:00
// RemoteExecutorImage is the image to use for the remote Pulumi operation.
RemoteExecutorImage *ExecutorImage
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
// RemoteInheritSettings sets whether to inherit settings from the remote workspace.
RemoteInheritSettings bool
2020-08-19 18:13:42 +00:00
}
2020-08-25 18:16:54 +00:00
// LocalWorkspaceOption is used to customize and configure a LocalWorkspace at initialization time.
// See Workdir, Program, PulumiHome, Project, Stacks, and Repo for concrete options.
2020-08-19 18:13:42 +00:00
type LocalWorkspaceOption interface {
applyLocalWorkspaceOption(*localWorkspaceOptions)
}
type localWorkspaceOption func(*localWorkspaceOptions)
func (o localWorkspaceOption) applyLocalWorkspaceOption(opts *localWorkspaceOptions) {
o(opts)
}
2020-08-25 18:16:54 +00:00
// GitRepo contains info to acquire and setup a Pulumi program from a git repository.
2020-08-19 18:13:42 +00:00
type GitRepo struct {
// URL to clone git repo
URL string
// Optional path relative to the repo root specifying location of the pulumi program.
// Specifying this option will update the Workspace's WorkDir accordingly.
2020-08-19 18:13:42 +00:00
ProjectPath string
2020-08-28 21:21:56 +00:00
// Optional branch to checkout.
2020-08-19 18:13:42 +00:00
Branch string
2020-08-28 21:21:56 +00:00
// Optional commit to checkout.
2020-08-19 18:13:42 +00:00
CommitHash string
// Optional function to execute after enlisting in the specified repo.
Setup SetupFn
// GitAuth is the different Authentication options for the Git repository
Auth *GitAuth
// Shallow disables fetching the repo's entire history.
Shallow bool
}
// GitAuth is the authentication details that can be specified for a private Git repo.
// There are 3 different authentication paths:
// * PersonalAccessToken
// * SSHPrivateKeyPath (and it's potential password)
// * Username and Password
// Only 1 authentication path is valid. If more than 1 is specified it will result in an error
type GitAuth struct {
// The absolute path to a private key for access to the git repo
// When using `SSHPrivateKeyPath`, the URL of the repository must be in the format
// git@github.com:org/repository.git - if the url is not in this format, then an error
// `unable to clone repo: invalid auth method` will be returned
SSHPrivateKeyPath string
// The (contents) private key for access to the git repo.
// When using `SSHPrivateKey`, the URL of the repository must be in the format
// git@github.com:org/repository.git - if the url is not in this format, then an error
// `unable to clone repo: invalid auth method` will be returned
SSHPrivateKey string
// The password that pairs with a username or as part of an SSH Private Key
Password string
// PersonalAccessToken is a Git personal access token in replacement of your password
PersonalAccessToken string
// Username is the username to use when authenticating to a git repository
Username string
2020-08-19 18:13:42 +00:00
}
// SetupFn is a function to execute after enlisting in a git repo.
// It is called with the workspace after all other options have been processed.
type SetupFn func(context.Context, Workspace) error
2020-08-19 18:13:42 +00:00
// WorkDir is the directory to execute commands from and store state.
func WorkDir(workDir string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.WorkDir = workDir
})
}
// Program is the Pulumi Program to execute. If none is supplied,
2020-08-28 21:21:56 +00:00
// the program identified in $WORKDIR/Pulumi.yaml will be used instead.
2020-08-19 18:13:42 +00:00
func Program(program pulumi.RunFunc) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.Program = program
})
}
2020-08-28 21:21:56 +00:00
// PulumiHome overrides the metadata directory for pulumi commands.
2020-08-19 18:13:42 +00:00
func PulumiHome(dir string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
2020-08-29 02:10:39 +00:00
lo.PulumiHome = dir
2020-08-19 18:13:42 +00:00
})
}
// PulumiCommand is the PulumiCommand instance to use. If none is
// supplied, the workspace will create an instance using the PulumiCommand
// CLI found in $PATH.
func Pulumi(pulumi PulumiCommand) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.PulumiCommand = pulumi
})
}
2020-08-28 21:21:56 +00:00
// Project sets project settings for the workspace.
2020-08-19 18:13:42 +00:00
func Project(settings workspace.Project) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.Project = &settings
})
}
2020-08-28 21:21:56 +00:00
// Stacks is a list of stack settings objects to seed the workspace.
2020-08-19 18:13:42 +00:00
func Stacks(settings map[string]workspace.ProjectStack) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.Stacks = settings
})
}
2020-08-21 16:49:46 +00:00
// Repo is a git repo with a Pulumi Project to clone into the WorkDir.
func Repo(gitRepo GitRepo) LocalWorkspaceOption {
2020-08-19 18:13:42 +00:00
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.Repo = &gitRepo
})
}
// SecretsProvider is the secrets provider to use with the current
// workspace when interacting with a stack
func SecretsProvider(secretsProvider string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.SecretsProvider = secretsProvider
})
}
// EnvVars is a map of environment values scoped to the workspace.
// These values will be passed to all Workspace and Stack level commands.
func EnvVars(envvars map[string]string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.EnvVars = envvars
})
}
// remoteEnvVars is a map of environment values scoped to the workspace.
// These values will be passed to the remote Pulumi operation.
func remoteEnvVars(envvars map[string]EnvVarValue) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.RemoteEnvVars = envvars
})
}
// remote is set on the local workspace to indicate it is actually a remote workspace.
func remote(remote bool) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.Remote = remote
})
}
// preRunCommands is an optional list of arbitrary commands to run before the remote Pulumi operation is invoked.
func preRunCommands(commands ...string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.PreRunCommands = commands
})
}
// remoteSkipInstallDependencies sets whether to skip the default dependency installation step.
func remoteSkipInstallDependencies(skipInstallDependencies bool) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.RemoteSkipInstallDependencies = skipInstallDependencies
})
}
[auto/go] Support for remote deployment executor image (#15697) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Adds Go Automation API support for specifying custom executor images for remote deployments. Part of: https://github.com/pulumi/pulumi/issues/15693 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. --> --------- Co-authored-by: Thomas Gummerer <t.gummerer@gmail.com>
2024-03-15 06:43:06 +00:00
func remoteExecutorImage(image *ExecutorImage) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.RemoteExecutorImage = image
})
}
Use new API for deployments (#15684) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Updates Remote Automation API to use the new stable URLs for the Deployments API. Adds support for `inheritSettings` to allow inheriting deployment settings pre-existing on the stack. I've tested this manually by editing the automation-api-examples for remote deployments, but not sure of a great way to add automated tests since automation api doesn't yet have support for setting deployment settings. EDIT: I considered just setting up a static stack for this but abandoned it because of concerns around parallel runs. We can add automated tests for this once we support creating deployment settings with automation api (coming soon). Fixes #12739 Fixes #15518 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-04-16 23:23:56 +00:00
func remoteInheritSettings(inheritSettings bool) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.RemoteInheritSettings = inheritSettings
})
}
2020-08-25 18:16:54 +00:00
// NewStackLocalSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// from the specified WorkDir. This Workspace will pick up
2020-08-28 21:21:56 +00:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
func NewStackLocalSource(ctx context.Context, stackName, workDir string, opts ...LocalWorkspaceOption) (Stack, error) {
2020-08-21 16:49:46 +00:00
opts = append(opts, WorkDir(workDir))
w, err := NewLocalWorkspace(ctx, opts...)
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
return stack, fmt.Errorf("failed to create stack: %w", err)
2020-08-21 16:49:46 +00:00
}
return NewStack(ctx, stackName, w)
2020-08-21 16:49:46 +00:00
}
// UpsertStackLocalSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// from the specified WorkDir. If the Stack already exists, it will not error
// and proceed to selecting the Stack.This Workspace will pick up any available
// Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
func UpsertStackLocalSource(
ctx context.Context,
stackName,
workDir string,
opts ...LocalWorkspaceOption,
) (Stack, error) {
opts = append(opts, WorkDir(workDir))
w, err := NewLocalWorkspace(ctx, opts...)
var stack Stack
if err != nil {
return stack, fmt.Errorf("failed to create stack: %w", err)
}
return UpsertStack(ctx, stackName, w)
}
2020-08-25 18:16:54 +00:00
// SelectStackLocalSource selects an existing Stack backed by a LocalWorkspace created on behalf of the user,
// from the specified WorkDir. This Workspace will pick up
2020-08-28 21:21:56 +00:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
func SelectStackLocalSource(
ctx context.Context,
stackName,
workDir string,
opts ...LocalWorkspaceOption,
) (Stack, error) {
2020-08-21 16:49:46 +00:00
opts = append(opts, WorkDir(workDir))
w, err := NewLocalWorkspace(ctx, opts...)
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
return stack, fmt.Errorf("failed to select stack: %w", err)
2020-08-21 16:49:46 +00:00
}
return SelectStack(ctx, stackName, w)
2020-08-21 16:49:46 +00:00
}
2020-08-25 18:16:54 +00:00
// NewStackRemoteSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// with source code cloned from the specified GitRepo. This Workspace will pick up
2020-08-28 21:21:56 +00:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml) that are cloned into the Workspace.
2020-08-25 18:16:54 +00:00
// Unless a WorkDir option is specified, the GitRepo will be clone into a new temporary directory provided by the OS.
func NewStackRemoteSource(
ctx context.Context,
stackName string,
repo GitRepo,
opts ...LocalWorkspaceOption,
) (Stack, error) {
2020-08-21 16:49:46 +00:00
opts = append(opts, Repo(repo))
w, err := NewLocalWorkspace(ctx, opts...)
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
return stack, fmt.Errorf("failed to create stack: %w", err)
2020-08-21 16:49:46 +00:00
}
return NewStack(ctx, stackName, w)
2020-08-21 16:49:46 +00:00
}
// UpsertStackRemoteSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// with source code cloned from the specified GitRepo. If the Stack already exists,
// it will not error and proceed to selecting the Stack. This Workspace will pick up
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml) that are cloned
// into the Workspace. Unless a WorkDir option is specified, the GitRepo will be clone
// into a new temporary directory provided by the OS.
func UpsertStackRemoteSource(
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
ctx context.Context, stackName string, repo GitRepo, opts ...LocalWorkspaceOption,
) (Stack, error) {
opts = append(opts, Repo(repo))
w, err := NewLocalWorkspace(ctx, opts...)
var stack Stack
if err != nil {
return stack, fmt.Errorf("failed to create stack: %w", err)
}
return UpsertStack(ctx, stackName, w)
}
2020-08-25 18:16:54 +00:00
// SelectStackRemoteSource selects an existing Stack backed by a LocalWorkspace created on behalf of the user,
// with source code cloned from the specified GitRepo. This Workspace will pick up
2020-08-28 21:21:56 +00:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml) that are cloned into the Workspace.
2020-08-25 18:16:54 +00:00
// Unless a WorkDir option is specified, the GitRepo will be clone into a new temporary directory provided by the OS.
2020-08-22 19:01:55 +00:00
func SelectStackRemoteSource(
ctx context.Context,
stackName string, repo GitRepo,
2020-08-22 19:01:55 +00:00
opts ...LocalWorkspaceOption,
) (Stack, error) {
2020-08-21 16:49:46 +00:00
opts = append(opts, Repo(repo))
w, err := NewLocalWorkspace(ctx, opts...)
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
return stack, fmt.Errorf("failed to select stack: %w", err)
2020-08-21 16:49:46 +00:00
}
return SelectStack(ctx, stackName, w)
2020-08-21 16:49:46 +00:00
}
2020-08-25 18:16:54 +00:00
// NewStackInlineSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// with the specified program. If no Project option is specified, default project settings will be created
// on behalf of the user. Similarly, unless a WorkDir option is specified, the working directory will default
// to a new temporary directory provided by the OS.
2020-08-21 16:49:46 +00:00
func NewStackInlineSource(
ctx context.Context,
stackName string,
projectName string,
2020-08-21 16:49:46 +00:00
program pulumi.RunFunc,
opts ...LocalWorkspaceOption,
) (Stack, error) {
var stack Stack
opts = append(opts, Program(program))
proj, err := getProjectSettings(ctx, projectName, opts)
2020-08-21 16:49:46 +00:00
if err != nil {
return stack, err
2020-08-21 16:49:46 +00:00
}
if proj != nil {
opts = append(opts, Project(*proj))
}
w, err := NewLocalWorkspace(ctx, opts...)
2020-08-21 16:49:46 +00:00
if err != nil {
return stack, fmt.Errorf("failed to create stack: %w", err)
2020-08-21 16:49:46 +00:00
}
return NewStack(ctx, stackName, w)
2020-08-21 16:49:46 +00:00
}
// UpsertStackInlineSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// with the specified program. If the Stack already exists, it will not error and
// proceed to selecting the Stack. If no Project option is specified, default project
// settings will be created on behalf of the user. Similarly, unless a WorkDir option
// is specified, the working directory will default to a new temporary directory provided by the OS.
func UpsertStackInlineSource(
ctx context.Context,
stackName string,
projectName string,
program pulumi.RunFunc,
opts ...LocalWorkspaceOption,
) (Stack, error) {
var stack Stack
opts = append(opts, Program(program))
proj, err := getProjectSettings(ctx, projectName, opts)
if err != nil {
return stack, err
}
if proj != nil {
opts = append(opts, Project(*proj))
}
w, err := NewLocalWorkspace(ctx, opts...)
if err != nil {
return stack, fmt.Errorf("failed to create stack: %w", err)
}
return UpsertStack(ctx, stackName, w)
}
2020-08-25 18:16:54 +00:00
// SelectStackInlineSource selects an existing Stack backed by a new LocalWorkspace created on behalf of the user,
// with the specified program. If no Project option is specified, default project settings will be created
// on behalf of the user. Similarly, unless a WorkDir option is specified, the working directory will default
// to a new temporary directory provided by the OS.
2020-08-21 16:49:46 +00:00
func SelectStackInlineSource(
ctx context.Context,
stackName string,
projectName string,
2020-08-21 16:49:46 +00:00
program pulumi.RunFunc,
opts ...LocalWorkspaceOption,
) (Stack, error) {
var stack Stack
opts = append(opts, Program(program))
proj, err := getProjectSettings(ctx, projectName, opts)
2020-08-21 16:49:46 +00:00
if err != nil {
return stack, err
2020-08-21 16:49:46 +00:00
}
if proj != nil {
opts = append(opts, Project(*proj))
}
w, err := NewLocalWorkspace(ctx, opts...)
2020-08-21 16:49:46 +00:00
if err != nil {
return stack, fmt.Errorf("failed to select stack: %w", err)
2020-08-21 16:49:46 +00:00
}
return SelectStack(ctx, stackName, w)
2020-08-21 16:49:46 +00:00
}
func defaultInlineProject(projectName string) (workspace.Project, error) {
2020-08-21 16:49:46 +00:00
var proj workspace.Project
cwd, err := os.Getwd()
if err != nil {
return proj, err
}
2020-08-21 16:49:46 +00:00
proj = workspace.Project{
Name: tokens.PackageName(projectName),
2020-08-21 16:49:46 +00:00
Runtime: workspace.NewProjectRuntimeInfo("go", nil),
Main: cwd,
2020-08-21 16:49:46 +00:00
}
return proj, nil
}
2020-08-25 18:16:54 +00:00
// stack names come in many forms:
// s, o/p/s, u/p/s o/s
// so just return the last chunk which is what will be used in pulumi.<stack>.yaml
func getStackSettingsName(stackName string) string {
parts := strings.Split(stackName, "/")
if len(parts) < 1 {
return stackName
2020-08-25 18:16:54 +00:00
}
return parts[len(parts)-1]
2020-08-25 18:16:54 +00:00
}
const pulumiHomeEnv = "PULUMI_HOME"
func readProjectSettingsFromDir(ctx context.Context, workDir string) (*workspace.Project, error) {
for _, ext := range settingsExtensions {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
projectPath := filepath.Join(workDir, "Pulumi"+ext)
if _, err := os.Stat(projectPath); err == nil {
proj, err := workspace.LoadProject(projectPath)
if err != nil {
return nil, fmt.Errorf("found project settings, but failed to load: %w", err)
}
return proj, nil
}
}
return nil, errors.New("unable to find project settings in workspace")
}
func getProjectSettings(
ctx context.Context,
projectName string,
opts []LocalWorkspaceOption,
) (*workspace.Project, error) {
var optsBag localWorkspaceOptions
for _, opt := range opts {
opt.applyLocalWorkspaceOption(&optsBag)
}
// If the Project is included in the opts, just use that.
if optsBag.Project != nil {
return optsBag.Project, nil
}
// If WorkDir is specified, try to read any existing project settings before resorting to
// creating a default project.
if optsBag.WorkDir != "" {
_, err := readProjectSettingsFromDir(ctx, optsBag.WorkDir)
if err == nil {
return nil, nil
}
if err.Error() == "unable to find project settings in workspace" {
proj, err := defaultInlineProject(projectName)
if err != nil {
return nil, fmt.Errorf("failed to create default project: %w", err)
}
return &proj, nil
}
return nil, fmt.Errorf("failed to load project settings: %w", err)
}
// If there was no workdir specified, create the default project.
proj, err := defaultInlineProject(projectName)
if err != nil {
return nil, fmt.Errorf("failed to create default project: %w", err)
}
return &proj, nil
}