2023-03-22 08:47:24 +00:00
// 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 (
2020-08-22 05:20:32 +00:00
"context"
2020-08-20 04:23:53 +00:00
"encoding/json"
2023-01-06 22:09:19 +00:00
"errors"
2020-08-19 18:13:42 +00:00
"fmt"
"os"
"path/filepath"
"strings"
2021-03-23 06:04:14 +00:00
"github.com/blang/semver"
2022-03-28 17:24:51 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/auto/optremove"
2021-03-17 13:20:05 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
2021-10-26 23:20:45 +00:00
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
2021-03-17 13:20:05 +00:00
"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 {
2022-12-18 03:31:44 +00:00
workDir string
pulumiHome string
program pulumi . RunFunc
envvars map [ string ] string
secretsProvider string
pulumiVersion semver . Version
repo * GitRepo
remote bool
remoteEnvVars map [ string ] EnvVarValue
preRunCommands [ ] string
remoteSkipInstallDependencies bool
2020-08-19 18:13:42 +00:00
}
var settingsExtensions = [ ] string { ".yaml" , ".yml" , ".json" }
2021-04-28 03:54:27 +00:00
var skipVersionCheckVar = "PULUMI_AUTOMATION_API_SKIP_VERSION_CHECK"
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.
2020-08-22 05:20:32 +00:00
func ( l * LocalWorkspace ) ProjectSettings ( ctx context . Context ) ( * workspace . Project , error ) {
2021-03-31 14:51:11 +00:00
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 )
}
2020-09-15 00:45:07 +00:00
// 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().
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) StackSettings ( ctx context . Context , stackName string ) ( * workspace . ProjectStack , error ) {
2022-09-21 13:52:14 +00:00
project , err := l . ProjectSettings ( ctx )
if err != nil {
return nil , err
}
2020-09-15 00:45:07 +00:00
name := getStackSettingsName ( stackName )
2020-08-19 18:13:42 +00:00
for _ , ext := range settingsExtensions {
2021-04-28 05:27:59 +00:00
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 {
2022-09-21 13:52:14 +00:00
proj , err := workspace . LoadProjectStack ( project , stackPath )
2020-08-19 18:13:42 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "found stack settings, but failed to load: %w" , err )
2020-08-19 18:13:42 +00:00
}
return proj , nil
}
}
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "unable to find stack settings in workspace for %s" , stackName )
2020-08-19 18:13:42 +00:00
}
2020-09-15 00:45:07 +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 (
2020-08-22 05:20:32 +00:00
ctx context . Context ,
2020-09-15 00:45:07 +00:00
stackName string ,
2020-08-22 05:20:32 +00:00
settings * workspace . ProjectStack ,
) error {
2020-09-15 00:45:07 +00:00
name := getStackSettingsName ( stackName )
2021-04-28 05:27:59 +00:00
stackYamlPath := filepath . Join ( l . WorkDir ( ) , fmt . Sprintf ( "Pulumi.%s.yaml" , name ) )
2020-09-15 00:45:07 +00:00
err := settings . Save ( stackYamlPath )
2020-08-19 18:13:42 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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.
2020-09-15 00:45:07 +00:00
// 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.
2020-09-15 00:45:07 +00:00
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
}
2020-09-15 00:45:07 +00:00
// 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.
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) PostCommandCallback ( ctx context . Context , stackName string ) error {
2020-08-19 18:13:42 +00:00
// not utilized for LocalWorkspace
return nil
}
2020-09-15 00:45:07 +00:00
// 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.
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) GetConfig ( ctx context . Context , stackName string , key string ) ( ConfigValue , error ) {
2023-03-28 18:21:07 +00:00
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
2023-03-28 18:21:07 +00:00
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 {
2023-01-06 22:09:19 +00:00
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 {
2023-01-06 22:09:19 +00:00
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
}
2020-09-15 00:45:07 +00:00
// 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.
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) GetAllConfig ( ctx context . Context , stackName string ) ( ConfigMap , error ) {
2020-08-20 04:23:53 +00:00
var val ConfigMap
2021-04-22 13:10:39 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "config" , "--show-secrets" , "--json" , "--stack" , stackName )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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 {
2023-01-06 22:09:19 +00:00
return val , fmt . Errorf ( "unable to unmarshal config value: %w" , err )
2020-08-20 04:23:53 +00:00
}
return val , nil
}
2020-09-15 00:45:07 +00:00
// 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().
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) SetConfig ( ctx context . Context , stackName string , key string , val ConfigValue ) error {
2023-03-28 18:21:07 +00:00
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"
}
2023-03-28 18:21:07 +00:00
args = append ( args , key , secretArg , "--non-interactive" , "--" , val . Value )
2020-08-20 04:23:53 +00:00
2023-03-28 18:21:07 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , args ... )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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
}
2020-09-15 00:45:07 +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().
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) SetAllConfig ( ctx context . Context , stackName string , config ConfigMap ) error {
2023-03-28 18:21:07 +00:00
return l . SetAllConfigWithOptions ( ctx , stackName , config , nil )
}
2021-02-20 06:59:18 +00:00
2023-03-28 18:21:07 +00:00
// 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 {
2021-02-20 06:59:18 +00:00
secretArg := "--plaintext"
if v . Secret {
secretArg = "--secret"
2020-08-20 04:23:53 +00:00
}
2021-02-20 06:59:18 +00:00
args = append ( args , secretArg , fmt . Sprintf ( "%s=%s" , k , v . Value ) )
}
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , args ... )
if err != nil {
2023-01-06 22:09:19 +00:00
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
}
2020-09-15 00:45:07 +00:00
// 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().
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) RemoveConfig ( ctx context . Context , stackName string , key string ) error {
2023-03-28 18:21:07 +00:00
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 {
2023-01-06 22:09:19 +00:00
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
}
2020-09-15 00:45:07 +00:00
// 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().
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) RemoveAllConfig ( ctx context . Context , stackName string , keys [ ] string ) error {
2023-03-28 18:21:07 +00:00
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 {
2021-02-20 06:59:18 +00:00
args := [ ] string { "config" , "rm-all" , "--stack" , stackName }
2023-03-28 18:21:07 +00:00
if opts != nil {
if opts . Path {
args = append ( args , "--path" )
}
}
2021-02-20 06:59:18 +00:00
args = append ( args , keys ... )
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , args ... )
if err != nil {
2023-01-06 22:09:19 +00:00
return newAutoError ( fmt . Errorf ( "unable to set config: %w" , err ) , stdout , stderr , errCode )
2020-08-20 04:23:53 +00:00
}
return nil
}
2020-09-15 00:45:07 +00:00
// 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().
2020-09-15 00:45:07 +00:00
func ( l * LocalWorkspace ) RefreshConfig ( ctx context . Context , stackName string ) ( ConfigMap , error ) {
2021-04-22 13:10:39 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "config" , "refresh" , "--force" , "--stack" , stackName )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , newAutoError ( fmt . Errorf ( "could not refresh config: %w" , err ) , stdout , stderr , errCode )
2020-08-20 04:23:53 +00:00
}
2020-09-15 00:45:07 +00:00
cfg , err := l . GetAllConfig ( ctx , stackName )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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
}
2023-02-27 17:02: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
}
2020-09-01 23:34:27 +00:00
// 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 {
2020-10-01 23:58:09 +00:00
return setEnvVars ( l , envvars )
}
func setEnvVars ( l * LocalWorkspace , envvars map [ string ] string ) error {
2020-09-01 23:34:27 +00:00
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
}
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
}
2021-03-23 06:04:14 +00:00
// PulumiVersion returns the version of the underlying Pulumi CLI/Engine.
2021-03-23 22:09:50 +00:00
func ( l * LocalWorkspace ) PulumiVersion ( ) string {
return l . pulumiVersion . String ( )
2021-03-23 06:04:14 +00:00
}
2020-08-25 18:16:54 +00:00
// WhoAmI returns the currently authenticated user
2020-08-22 05:20:32 +00:00
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 {
2023-01-06 22:09:19 +00:00
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
}
2023-03-08 21:59:43 +00:00
// WhoAmIDetails returns detailed information about the currently
// logged-in Pulumi identity.
2023-03-08 22:01:56 +00:00
func ( l * LocalWorkspace ) WhoAmIDetails ( ctx context . Context ) ( WhoAmIResult , error ) {
2023-03-22 08:47:24 +00:00
// 3.58 added the --json flag (https://github.com/pulumi/pulumi/releases/tag/v3.58.0)
if l . pulumiVersion . 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
2023-03-06 14:07:22 +00:00
}
2023-03-22 08:47:24 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "whoami" )
2023-03-06 14:07:22 +00:00
if err != nil {
2023-03-22 08:47:24 +00:00
return WhoAmIResult { } , newAutoError (
fmt . Errorf ( "could not determine authenticated user: %w" , err ) , stdout , stderr , errCode )
2023-03-06 14:07:22 +00:00
}
2023-03-22 08:47:24 +00:00
return WhoAmIResult { User : strings . TrimSpace ( stdout ) } , nil
2023-03-06 14:07:22 +00:00
}
2020-08-25 18:16:54 +00:00
// Stack returns a summary of the currently selected stack, if any.
2020-08-22 05:20:32 +00:00
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 {
2023-01-06 22:09:19 +00:00
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
}
2020-09-15 00:45:07 +00:00
// 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 }
2020-09-10 18:25:47 +00:00
if l . secretsProvider != "" {
2020-09-17 18:57:27 +00:00
args = append ( args , "--secrets-provider" , l . secretsProvider )
2020-09-10 18:25:47 +00:00
}
2022-10-25 21:45:02 +00:00
if l . remote {
args = append ( args , "--no-select" )
}
2020-09-10 18:25:47 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , args ... )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return newAutoError ( fmt . Errorf ( "failed to create stack: %w" , err ) , stdout , stderr , errCode )
2020-08-20 04:23:53 +00:00
}
2020-08-21 02:37:39 +00:00
return nil
2020-08-19 18:13:42 +00:00
}
2020-09-15 00:45:07 +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 {
2022-10-25 21:45:02 +00:00
// 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 {
2023-01-06 22:09:19 +00:00
return newAutoError ( fmt . Errorf ( "failed to select stack: %w" , err ) , stdout , stderr , errCode )
2020-08-20 04:23:53 +00:00
}
2020-08-21 02:37:39 +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 {
2023-01-06 22:09:19 +00:00
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).
2020-08-22 05:20:32 +00:00
func ( l * LocalWorkspace ) ListStacks ( ctx context . Context ) ( [ ] StackSummary , error ) {
2020-08-20 04:23:53 +00:00
var stacks [ ] StackSummary
2020-08-22 05:20:32 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "ls" , "--json" )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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 {
2023-01-06 22:09:19 +00:00
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.
2020-08-22 05:20:32 +00:00
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 {
2023-01-06 22:09:19 +00:00
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
}
2022-10-07 01:40:02 +00:00
// 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 )
2022-10-07 01:40:02 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return newAutoError ( fmt . Errorf ( "failed to install plugin: %w" , err ) , stdout , stderr , errCode )
2022-10-07 01:40:02 +00:00
}
return nil
}
2020-08-28 21:21:56 +00:00
// RemovePlugin deletes the plugin matching the specified name and verision.
2020-08-22 05:20:32 +00:00
func ( l * LocalWorkspace ) RemovePlugin ( ctx context . Context , name string , version string ) error {
2020-10-09 16:03:03 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "plugin" , "rm" , "resource" , name , version , "--yes" )
2020-08-20 04:23:53 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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.
2020-08-22 05:20:32 +00:00
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 {
2023-01-06 22:09:19 +00:00
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 {
2023-01-06 22:09:19 +00:00
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.
2020-08-21 02:37:39 +00:00
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`.
2020-08-21 02:37:39 +00:00
func ( l * LocalWorkspace ) SetProgram ( fn pulumi . RunFunc ) {
l . program = fn
}
2020-09-15 21:20:58 +00:00
// 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
2021-04-22 13:10:39 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "export" , "--show-secrets" , "--stack" , stackName )
2020-09-15 21:20:58 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return state , newAutoError ( fmt . Errorf ( "could not export stack: %w" , err ) , stdout , stderr , errCode )
2020-09-15 21:20:58 +00:00
}
err = json . Unmarshal ( [ ] byte ( stdout ) , & state )
if err != nil {
return state , newAutoError (
2023-01-06 22:09:19 +00:00
fmt . Errorf ( "failed to export stack, unable to unmarshall stack state: %w" , err ) , stdout , stderr , errCode ,
2020-09-15 21:20:58 +00:00
)
}
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 {
2023-01-06 22:39:16 +00:00
f , err := os . CreateTemp ( os . TempDir ( ) , "" )
2020-09-15 21:20:58 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return fmt . Errorf ( "could not import stack. failed to allocate temp file: %w" , err )
2020-09-15 21:20:58 +00:00
}
defer func ( ) { contract . IgnoreError ( os . Remove ( f . Name ( ) ) ) } ( )
bytes , err := json . Marshal ( state )
if err != nil {
2023-01-06 22:09:19 +00:00
return fmt . Errorf ( "could not import stack, failed to marshal stack state: %w" , err )
2020-09-15 21:20:58 +00:00
}
_ , err = f . Write ( bytes )
if err != nil {
2023-01-06 22:09:19 +00:00
return fmt . Errorf ( "could not import stack. failed to write out stack intermediate: %w" , err )
2020-09-15 21:20:58 +00:00
}
2021-04-22 13:10:39 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "import" , "--file" , f . Name ( ) , "--stack" , stackName )
2020-09-15 21:20:58 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return newAutoError ( fmt . Errorf ( "could not import stack: %w" , err ) , stdout , stderr , errCode )
2020-09-15 21:20:58 +00:00
}
return nil
}
2022-10-09 14:58:33 +00:00
// StackOutputs gets the current set of Stack outputs from the last Stack.Up().
2021-04-26 23:32:30 +00:00
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 {
2023-01-06 22:09:19 +00:00
return nil , newAutoError ( fmt . Errorf ( "could not get outputs: %w" , err ) , outStdout , outStderr , code )
2021-04-26 23:32:30 +00:00
}
// secret outputs
secretStdout , secretStderr , code , err := l . runPulumiCmdSync ( ctx ,
"stack" , "output" , "--json" , "--show-secrets" , "--stack" , stackName ,
)
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , newAutoError ( fmt . Errorf ( "could not get secret outputs: %w" , err ) , outStdout , outStderr , code )
2021-04-26 23:32:30 +00:00
}
var outputs map [ string ] interface { }
var secrets map [ string ] interface { }
if err = json . Unmarshal ( [ ] byte ( outStdout ) , & outputs ) ; err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "error unmarshalling outputs: %s: %w" , secretStderr , err )
2021-04-26 23:32:30 +00:00
}
if err = json . Unmarshal ( [ ] byte ( secretStdout ) , & secrets ) ; err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "error unmarshalling secret outputs: %s: %w" , secretStderr , err )
2021-04-26 23:32:30 +00:00
}
res := make ( OutputMap )
for k , v := range secrets {
2021-10-08 21:43:10 +00:00
raw , err := json . Marshal ( outputs [ k ] )
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "error determining secretness: %s: %w" , secretStderr , err )
2021-10-08 21:43:10 +00:00
}
rawString := string ( raw )
isSecret := strings . Contains ( rawString , secretSentinel )
2021-04-26 23:32:30 +00:00
res [ k ] = OutputValue {
Value : v ,
Secret : isSecret ,
}
}
return res , nil
}
2021-10-28 03:54:23 +00:00
func ( l * LocalWorkspace ) getPulumiVersion ( ctx context . Context ) ( string , error ) {
2021-03-23 06:04:14 +00:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "version" )
if err != nil {
2023-01-06 22:09:19 +00:00
return "" , newAutoError ( fmt . Errorf ( "could not determine pulumi version: %w" , err ) , stdout , stderr , errCode )
2021-03-23 06:04:14 +00:00
}
2021-10-28 03:54:23 +00:00
return stdout , nil
2021-03-23 06:04:14 +00:00
}
//nolint:lll
2021-10-28 03:54:23 +00:00
func parseAndValidatePulumiVersion ( minVersion semver . Version , currentVersion string , optOut bool ) ( semver . Version , error ) {
version , err := semver . ParseTolerant ( currentVersion )
if err != nil && ! optOut {
2023-01-06 22:09:19 +00:00
return semver . Version { } , fmt . Errorf ( "Unable to parse Pulumi CLI version (skip with %s=true): %w" , skipVersionCheckVar , err )
2021-10-28 03:54:23 +00:00
}
2021-04-28 03:54:27 +00:00
if optOut {
2021-10-28 03:54:23 +00:00
return version , nil
2021-04-28 03:54:27 +00:00
}
2021-10-28 03:54:23 +00:00
if minVersion . Major < version . Major {
2023-01-06 22:09:19 +00:00
return semver . Version { } , fmt . Errorf ( "Major version mismatch. You are using Pulumi CLI version %s with Automation SDK v%v. Please update the SDK." , currentVersion , minVersion . Major ) //nolint
2021-03-23 06:04:14 +00:00
}
2021-10-28 03:54:23 +00:00
if minVersion . GT ( version ) {
2023-01-06 22:09:19 +00:00
return semver . Version { } , fmt . Errorf ( "Minimum version requirement failed. The minimum CLI version requirement is %s, your current CLI version is %s. Please update the Pulumi CLI." , minimumVersion , currentVersion ) //nolint
2021-03-23 06:04:14 +00:00
}
2021-10-28 03:54:23 +00:00
return version , nil
2021-03-23 06:04:14 +00:00
}
2020-08-22 05:20:32 +00:00
func ( l * LocalWorkspace ) runPulumiCmdSync (
ctx context . Context ,
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 ( ) )
2020-08-21 23:22:45 +00:00
env = append ( env , homeEnv )
2020-08-19 18:13:42 +00:00
}
2020-09-01 23:34:27 +00:00
if envvars := l . GetEnvVars ( ) ; envvars != nil {
for k , v := range envvars {
e := [ ] string { k , v }
env = append ( env , strings . Join ( e , "=" ) )
}
}
2022-07-19 17:10:10 +00:00
return runPulumiCommandSync ( ctx ,
l . WorkDir ( ) ,
nil , /* additionalOutputs */
nil , /* additionalErrorOutputs */
env ,
args ... ,
)
2020-08-19 18:13:42 +00:00
}
2022-10-25 21:45:02 +00:00
// 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 := runPulumiCommandSync ( ctx , l . WorkDir ( ) , 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.
2020-08-22 05:20:32 +00:00
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 {
2023-01-06 22:39:16 +00:00
dir , err := os . MkdirTemp ( "" , "pulumi_auto" )
2020-08-19 18:13:42 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "unable to create tmp directory for workspace: %w" , err )
2020-08-19 18:13:42 +00:00
}
workDir = dir
}
2022-10-25 21:45:02 +00:00
if lwOpts . Repo != nil && ! lwOpts . Remote {
2020-08-19 18:13:42 +00:00
// now do the git clone
2020-08-22 05:20:32 +00:00
projDir , err := setupGitRepo ( ctx , workDir , lwOpts . Repo )
2020-08-19 18:13:42 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
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
}
var program pulumi . RunFunc
if lwOpts . Program != nil {
program = lwOpts . Program
}
l := & LocalWorkspace {
2022-12-18 03:31:44 +00:00
workDir : workDir ,
preRunCommands : lwOpts . PreRunCommands ,
program : program ,
pulumiHome : lwOpts . PulumiHome ,
remote : lwOpts . Remote ,
remoteEnvVars : lwOpts . RemoteEnvVars ,
remoteSkipInstallDependencies : lwOpts . RemoteSkipInstallDependencies ,
repo : lwOpts . Repo ,
2020-08-19 18:13:42 +00:00
}
2021-10-26 23:20:45 +00:00
// optOut indicates we should skip the version check.
optOut := cmdutil . IsTruthy ( os . Getenv ( skipVersionCheckVar ) )
2021-04-30 16:41:31 +00:00
if val , ok := lwOpts . EnvVars [ skipVersionCheckVar ] ; ok {
2021-10-26 23:20:45 +00:00
optOut = optOut || cmdutil . IsTruthy ( val )
}
2021-10-28 03:54:23 +00:00
currentVersion , err := l . getPulumiVersion ( ctx )
if err != nil {
return nil , err
2021-04-28 03:54:27 +00:00
}
2021-10-28 03:54:23 +00:00
if l . pulumiVersion , err = parseAndValidatePulumiVersion ( minimumVersion , currentVersion , optOut ) ; err != nil {
return nil , err
2021-03-23 06:04:14 +00:00
}
2022-10-25 21:45:02 +00:00
// 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 {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "failed to create workspace, unable to save project settings: %w" , err )
2020-08-19 18:13:42 +00:00
}
}
2020-09-15 00:45:07 +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 {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "failed to create workspace: %w" , err )
2020-08-19 18:13:42 +00:00
}
}
2020-09-02 19:40:15 +00:00
// setup
2022-10-25 21:45:02 +00:00
if ! lwOpts . Remote && lwOpts . Repo != nil && lwOpts . Repo . Setup != nil {
2020-09-02 19:40:15 +00:00
err := lwOpts . Repo . Setup ( ctx , l )
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "error while running setup function: %w" , err )
2020-09-02 19:40:15 +00:00
}
}
2020-09-10 18:25:47 +00:00
// Secrets providers
if lwOpts . SecretsProvider != "" {
l . secretsProvider = lwOpts . SecretsProvider
}
2020-10-01 23:58:09 +00:00
// Environment values
if lwOpts . EnvVars != nil {
if err := setEnvVars ( l , lwOpts . EnvVars ) ; err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "failed to set environment values: %w" , err )
2020-10-01 23:58:09 +00:00
}
}
2020-08-19 18:13:42 +00:00
return l , nil
}
2022-10-25 21:45:02 +00:00
// 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
// Project is the project settings for the workspace.
2020-08-19 18:13:42 +00:00
Project * workspace . Project
2020-09-15 00:45:07 +00:00
// 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
2020-09-10 18:25:47 +00:00
// Secrets Provider to use with the current Stack
SecretsProvider string
2020-10-01 23:58:09 +00:00
// 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
2022-10-25 21:45:02 +00:00
// 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
2022-12-18 03:31:44 +00:00
// RemoteSkipInstallDependencies sets whether to skip the default dependency installation step
RemoteSkipInstallDependencies 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.
2021-03-23 06:04:14 +00:00
// 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
2020-09-14 19:24:57 +00:00
// GitAuth is the different Authentication options for the Git repository
Auth * GitAuth
2023-11-01 17:21:52 +00:00
// Shallow disables fetching the repo's entire history.
Shallow bool
2020-09-14 19:24:57 +00:00
}
// 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
2020-09-17 14:48:44 +00:00
// 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
2020-09-14 19:24:57 +00:00
SSHPrivateKeyPath string
2020-10-12 18:51:26 +00:00
// 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
2020-09-14 19:24:57 +00:00
// 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.
2020-09-02 19:40:15 +00:00
// 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
} )
}
2020-08-21 02:37:39 +00:00
// 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
} )
}
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
} )
}
2020-09-10 18:25:47 +00:00
// 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
} )
}
2020-10-01 23:58:09 +00:00
// 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
} )
}
2022-10-25 21:45:02 +00:00
// 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
} )
}
2022-12-18 03:31:44 +00:00
// remoteSkipInstallDependencies sets whether to skip the default dependency installation step.
func remoteSkipInstallDependencies ( skipInstallDependencies bool ) LocalWorkspaceOption {
return localWorkspaceOption ( func ( lo * localWorkspaceOptions ) {
lo . RemoteSkipInstallDependencies = skipInstallDependencies
} )
}
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).
2020-09-15 00:45:07 +00:00
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 ) )
2020-08-22 05:20:32 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to create stack: %w" , err )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +00:00
return NewStack ( ctx , stackName , w )
2020-08-21 16:49:46 +00:00
}
2020-09-09 22:20:41 +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).
2020-09-15 00:45:07 +00:00
func UpsertStackLocalSource (
ctx context . Context ,
stackName ,
workDir string ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
2020-09-09 22:20:41 +00:00
opts = append ( opts , WorkDir ( workDir ) )
w , err := NewLocalWorkspace ( ctx , opts ... )
var stack Stack
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to create stack: %w" , err )
2020-09-09 22:20:41 +00:00
}
2020-09-15 00:45:07 +00:00
return UpsertStack ( ctx , stackName , w )
2020-09-09 22:20:41 +00:00
}
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).
2020-09-15 00:45:07 +00:00
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 ) )
2020-08-22 05:20:32 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to select stack: %w" , err )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +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.
2020-09-15 00:45:07 +00:00
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 ) )
2020-08-22 05:20:32 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to create stack: %w" , err )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +00:00
return NewStack ( ctx , stackName , w )
2020-08-21 16:49:46 +00:00
}
2020-09-09 22:20:41 +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 (
2023-03-03 16:36:39 +00:00
ctx context . Context , stackName string , repo GitRepo , opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
2020-09-09 22:20:41 +00:00
opts = append ( opts , Repo ( repo ) )
w , err := NewLocalWorkspace ( ctx , opts ... )
var stack Stack
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to create stack: %w" , err )
2020-09-09 22:20:41 +00:00
}
2020-09-15 00:45:07 +00:00
return UpsertStack ( ctx , stackName , w )
2020-09-09 22:20:41 +00:00
}
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 ,
2020-09-15 00:45:07 +00:00
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 ) )
2020-08-22 05:20:32 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 16:49:46 +00:00
var stack Stack
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to select stack: %w" , err )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +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 (
2020-08-22 05:20:32 +00:00
ctx context . Context ,
2020-09-15 00:45:07 +00:00
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 ) )
2021-03-31 14:51:11 +00:00
proj , err := getProjectSettings ( ctx , projectName , opts )
2020-08-21 16:49:46 +00:00
if err != nil {
2021-03-31 14:51:11 +00:00
return stack , err
2020-08-21 16:49:46 +00:00
}
2021-03-31 18:00:11 +00:00
if proj != nil {
opts = append ( opts , Project ( * proj ) )
}
2021-03-31 14:51:11 +00:00
2020-08-22 05:20:32 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 16:49:46 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to create stack: %w" , err )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +00:00
return NewStack ( ctx , stackName , w )
2020-08-21 16:49:46 +00:00
}
2020-09-09 22:20:41 +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 ,
2020-09-15 00:45:07 +00:00
stackName string ,
projectName string ,
2020-09-09 22:20:41 +00:00
program pulumi . RunFunc ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
var stack Stack
opts = append ( opts , Program ( program ) )
2021-03-31 14:51:11 +00:00
proj , err := getProjectSettings ( ctx , projectName , opts )
2020-09-09 22:20:41 +00:00
if err != nil {
2021-03-31 14:51:11 +00:00
return stack , err
2020-09-09 22:20:41 +00:00
}
2021-03-31 18:00:11 +00:00
if proj != nil {
opts = append ( opts , Project ( * proj ) )
}
2021-03-31 14:51:11 +00:00
2020-09-09 22:20:41 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to create stack: %w" , err )
2020-09-09 22:20:41 +00:00
}
2020-09-15 00:45:07 +00:00
return UpsertStack ( ctx , stackName , w )
2020-09-09 22:20:41 +00:00
}
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 (
2020-08-22 05:20:32 +00:00
ctx context . Context ,
2020-09-15 00:45:07 +00:00
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 ) )
2021-03-31 14:51:11 +00:00
proj , err := getProjectSettings ( ctx , projectName , opts )
2020-08-21 16:49:46 +00:00
if err != nil {
2021-03-31 14:51:11 +00:00
return stack , err
2020-08-21 16:49:46 +00:00
}
2021-03-31 18:00:11 +00:00
if proj != nil {
opts = append ( opts , Project ( * proj ) )
}
2021-03-31 14:51:11 +00:00
2020-08-22 05:20:32 +00:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 16:49:46 +00:00
if err != nil {
2023-01-06 22:09:19 +00:00
return stack , fmt . Errorf ( "failed to select stack: %w" , err )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +00:00
return SelectStack ( ctx , stackName , w )
2020-08-21 16:49:46 +00:00
}
2020-09-15 00:45:07 +00:00
func defaultInlineProject ( projectName string ) ( workspace . Project , error ) {
2020-08-21 16:49:46 +00:00
var proj workspace . Project
2021-04-10 03:30:11 +00:00
cwd , err := os . Getwd ( )
if err != nil {
return proj , err
}
2020-08-21 16:49:46 +00:00
proj = workspace . Project {
2020-09-15 00:45:07 +00:00
Name : tokens . PackageName ( projectName ) ,
2020-08-21 16:49:46 +00:00
Runtime : workspace . NewProjectRuntimeInfo ( "go" , nil ) ,
2021-04-10 03:30:11 +00:00
Main : cwd ,
2020-08-21 16:49:46 +00:00
}
return proj , nil
}
2020-08-25 18:16:54 +00:00
2020-09-15 00:45:07 +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
}
2020-09-15 00:45:07 +00:00
return parts [ len ( parts ) - 1 ]
2020-08-25 18:16:54 +00:00
}
const pulumiHomeEnv = "PULUMI_HOME"
2021-03-31 14:51:11 +00:00
func readProjectSettingsFromDir ( ctx context . Context , workDir string ) ( * workspace . Project , error ) {
for _ , ext := range settingsExtensions {
projectPath := filepath . Join ( workDir , fmt . Sprintf ( "Pulumi%s" , ext ) )
if _ , err := os . Stat ( projectPath ) ; err == nil {
proj , err := workspace . LoadProject ( projectPath )
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "found project settings, but failed to load: %w" , err )
2021-03-31 14:51:11 +00:00
}
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 != "" {
2021-03-31 18:00:11 +00:00
_ , err := readProjectSettingsFromDir ( ctx , optsBag . WorkDir )
2021-03-31 14:51:11 +00:00
if err == nil {
2021-03-31 18:00:11 +00:00
return nil , nil
2021-03-31 14:51:11 +00:00
}
if err . Error ( ) == "unable to find project settings in workspace" {
proj , err := defaultInlineProject ( projectName )
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "failed to create default project: %w" , err )
2021-03-31 14:51:11 +00:00
}
return & proj , nil
}
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "failed to load project settings: %w" , err )
2021-03-31 14:51:11 +00:00
}
// If there was no workdir specified, create the default project.
proj , err := defaultInlineProject ( projectName )
if err != nil {
2023-01-06 22:09:19 +00:00
return nil , fmt . Errorf ( "failed to create default project: %w" , err )
2021-03-31 14:51:11 +00:00
}
return & proj , nil
}