2021-09-21 19:00:44 +02:00
// Copyright 2016-2021, Pulumi Corporation.
2020-08-27 19:43:23 +02: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 20:13:42 +02:00
package auto
import (
2020-08-22 07:20:32 +02:00
"context"
2020-08-20 06:23:53 +02:00
"encoding/json"
2020-08-19 20:13:42 +02:00
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
2021-03-23 07:04:14 +01:00
"github.com/blang/semver"
2020-08-19 20:13:42 +02:00
"github.com/pkg/errors"
2021-03-23 07:04:14 +01:00
2021-03-17 14:20:05 +01:00
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
2021-10-27 01:20:45 +02:00
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
2021-03-17 14:20:05 +01: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"
2021-08-02 21:54:46 +02:00
"github.com/pulumi/pulumi/sdk/v3/go/auto/optremove"
2020-08-19 20:13:42 +02:00
)
2020-08-28 23:21:56 +02:00
// LocalWorkspace is a default implementation of the Workspace interface.
2020-08-25 20:16:54 +02: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 23:21:56 +02:00
// LocalWorkspace relies on Pulumi.yaml and Pulumi.<stack>.yaml as the intermediate format
2020-08-25 20:16:54 +02:00
// for Project and Stack settings. Modifying ProjectSettings will
2020-08-28 23:21:56 +02:00
// alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.<stack>.yaml file.
2020-08-25 20:16:54 +02:00
// This is identical to the behavior of Pulumi CLI driven workspaces.
2020-08-19 20:13:42 +02:00
type LocalWorkspace struct {
2020-09-10 20:25:47 +02:00
workDir string
pulumiHome string
program pulumi . RunFunc
envvars map [ string ] string
secretsProvider string
2021-03-23 07:04:14 +01:00
pulumiVersion semver . Version
2020-08-19 20:13:42 +02:00
}
var settingsExtensions = [ ] string { ".yaml" , ".yml" , ".json" }
2021-04-28 05:54:27 +02:00
var skipVersionCheckVar = "PULUMI_AUTOMATION_API_SKIP_VERSION_CHECK"
2020-08-25 20:16:54 +02:00
// ProjectSettings returns the settings object for the current project if any
2020-08-28 23:21:56 +02:00
// LocalWorkspace reads settings from the Pulumi.yaml in the workspace.
2020-08-25 20:16:54 +02:00
// A workspace can contain only a single project at a time.
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) ProjectSettings ( ctx context . Context ) ( * workspace . Project , error ) {
2021-03-31 16:51:11 +02:00
return readProjectSettingsFromDir ( ctx , l . WorkDir ( ) )
2020-08-19 20:13:42 +02:00
}
2020-08-29 04:10:39 +02:00
// SaveProjectSettings overwrites the settings object in the current project.
2020-08-25 20:16:54 +02:00
// There can only be a single project per workspace. Fails is new project name does not match old.
2020-08-28 23:21:56 +02:00
// LocalWorkspace writes this value to a Pulumi.yaml file in Workspace.WorkDir().
2020-08-29 04:10:39 +02:00
func ( l * LocalWorkspace ) SaveProjectSettings ( ctx context . Context , settings * workspace . Project ) error {
2020-08-21 18:49:46 +02:00
pulumiYamlPath := filepath . Join ( l . WorkDir ( ) , "Pulumi.yaml" )
2020-08-19 20:13:42 +02:00
return settings . Save ( pulumiYamlPath )
}
2020-09-15 02:45:07 +02:00
// StackSettings returns the settings object for the stack matching the specified stack name if any.
2020-08-28 23:21:56 +02:00
// LocalWorkspace reads this from a Pulumi.<stack>.yaml file in Workspace.WorkDir().
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) StackSettings ( ctx context . Context , stackName string ) ( * workspace . ProjectStack , error ) {
name := getStackSettingsName ( stackName )
2020-08-19 20:13:42 +02:00
for _ , ext := range settingsExtensions {
2021-04-28 07:27:59 +02:00
stackPath := filepath . Join ( l . WorkDir ( ) , fmt . Sprintf ( "Pulumi.%s%s" , name , ext ) )
2021-08-31 05:56:08 +02:00
if _ , err := os . Stat ( stackPath ) ; err == nil {
2020-08-19 20:13:42 +02:00
proj , err := workspace . LoadProjectStack ( stackPath )
if err != nil {
return nil , errors . Wrap ( err , "found stack settings, but failed to load" )
}
return proj , nil
}
}
2020-09-15 02:45:07 +02:00
return nil , errors . Errorf ( "unable to find stack settings in workspace for %s" , stackName )
2020-08-19 20:13:42 +02:00
}
2020-09-15 02:45:07 +02:00
// SaveStackSettings overwrites the settings object for the stack matching the specified stack name.
2020-08-28 23:21:56 +02:00
// LocalWorkspace writes this value to a Pulumi.<stack>.yaml file in Workspace.WorkDir()
2020-08-29 04:10:39 +02:00
func ( l * LocalWorkspace ) SaveStackSettings (
2020-08-22 07:20:32 +02:00
ctx context . Context ,
2020-09-15 02:45:07 +02:00
stackName string ,
2020-08-22 07:20:32 +02:00
settings * workspace . ProjectStack ,
) error {
2020-09-15 02:45:07 +02:00
name := getStackSettingsName ( stackName )
2021-04-28 07:27:59 +02:00
stackYamlPath := filepath . Join ( l . WorkDir ( ) , fmt . Sprintf ( "Pulumi.%s.yaml" , name ) )
2020-09-15 02:45:07 +02:00
err := settings . Save ( stackYamlPath )
2020-08-19 20:13:42 +02:00
if err != nil {
2020-09-15 02:45:07 +02:00
return errors . Wrapf ( err , "failed to save stack setttings for %s" , stackName )
2020-08-19 20:13:42 +02:00
}
return nil
}
2020-08-25 20:16:54 +02:00
// SerializeArgsForOp is hook to provide additional args to every CLI commands before they are executed.
2020-09-15 02:45:07 +02:00
// Provided with stack name,
2020-08-25 20:16:54 +02:00
// returns a list of args to append to an invoked command ["--config=...", ]
// LocalWorkspace does not utilize this extensibility point.
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) SerializeArgsForOp ( ctx context . Context , stackName string ) ( [ ] string , error ) {
2020-08-19 20:13:42 +02:00
// not utilized for LocalWorkspace
return nil , nil
}
2020-09-15 02:45:07 +02:00
// PostCommandCallback is a hook executed after every command. Called with the stack name.
2020-08-29 04:10:39 +02:00
// An extensibility point to perform workspace cleanup (CLI operations may create/modify a Pulumi.stack.yaml)
2020-08-25 20:16:54 +02:00
// LocalWorkspace does not utilize this extensibility point.
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) PostCommandCallback ( ctx context . Context , stackName string ) error {
2020-08-19 20:13:42 +02:00
// not utilized for LocalWorkspace
return nil
}
2020-09-15 02:45:07 +02:00
// GetConfig returns the value associated with the specified stack name and key,
2020-08-29 04:10:39 +02:00
// scoped to the current workspace. LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) GetConfig ( ctx context . Context , stackName string , key string ) ( ConfigValue , error ) {
2020-08-20 06:23:53 +02:00
var val ConfigValue
2021-04-22 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "config" , "get" , key , "--json" , "--stack" , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return val , newAutoError ( errors . Wrap ( err , "unable to read config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
err = json . Unmarshal ( [ ] byte ( stdout ) , & val )
if err != nil {
return val , errors . Wrap ( err , "unable to unmarshal config value" )
}
2020-08-19 20:13:42 +02:00
return val , nil
}
2020-09-15 02:45:07 +02:00
// GetAllConfig returns the config map for the specified stack name, scoped to the current workspace.
2020-08-29 04:10:39 +02:00
// LocalWorkspace reads this config from the matching Pulumi.stack.yaml file.
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) GetAllConfig ( ctx context . Context , stackName string ) ( ConfigMap , error ) {
2020-08-20 06:23:53 +02:00
var val ConfigMap
2021-04-22 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "config" , "--show-secrets" , "--json" , "--stack" , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
2021-04-22 15:10:39 +02:00
return val , newAutoError ( errors . Wrap ( err , "unable to read config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
err = json . Unmarshal ( [ ] byte ( stdout ) , & val )
if err != nil {
return val , errors . Wrap ( err , "unable to unmarshal config value" )
}
return val , nil
}
2020-09-15 02:45:07 +02:00
// SetConfig sets the specified key-value pair on the provided stack name.
2020-08-28 23:21:56 +02:00
// LocalWorkspace writes this value to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) SetConfig ( ctx context . Context , stackName string , key string , val ConfigValue ) error {
2020-08-20 06:23:53 +02:00
secretArg := "--plaintext"
if val . Secret {
secretArg = "--secret"
}
2021-04-22 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx ,
"config" , "set" , key , val . Value , secretArg , "--stack" , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
2021-04-22 15:10:39 +02:00
return newAutoError ( errors . Wrap ( err , "unable to set config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
return nil
2020-08-19 20:13:42 +02:00
}
2020-09-15 02:45:07 +02:00
// SetAllConfig sets all values in the provided config map for the specified stack name.
2020-08-28 23:21:56 +02:00
// LocalWorkspace writes the config to the matching Pulumi.<stack>.yaml file in Workspace.WorkDir().
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) SetAllConfig ( ctx context . Context , stackName string , config ConfigMap ) error {
2021-02-20 07:59:18 +01:00
args := [ ] string { "config" , "set-all" , "--stack" , stackName }
2020-08-20 06:23:53 +02:00
for k , v := range config {
2021-02-20 07:59:18 +01:00
secretArg := "--plaintext"
if v . Secret {
secretArg = "--secret"
2020-08-20 06:23:53 +02:00
}
2021-02-20 07:59:18 +01: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 ( errors . Wrap ( err , "unable to set config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-08-19 20:13:42 +02:00
return nil
}
2020-09-15 02:45:07 +02:00
// RemoveConfig removes the specified key-value pair on the provided stack name.
2020-08-28 23:21:56 +02:00
// It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) RemoveConfig ( ctx context . Context , stackName string , key string ) error {
2021-04-22 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "config" , "rm" , key , "--stack" , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return newAutoError ( errors . Wrap ( err , "could not remove config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-08-19 20:13:42 +02:00
return nil
}
2020-09-15 02:45:07 +02:00
// RemoveAllConfig removes all values in the provided key list for the specified stack name
2020-08-28 23:21:56 +02:00
// It will remove any matching values in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) RemoveAllConfig ( ctx context . Context , stackName string , keys [ ] string ) error {
2021-02-20 07:59:18 +01:00
args := [ ] string { "config" , "rm-all" , "--stack" , stackName }
args = append ( args , keys ... )
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , args ... )
if err != nil {
return newAutoError ( errors . Wrap ( err , "unable to set config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
return nil
}
2020-09-15 02:45:07 +02:00
// RefreshConfig gets and sets the config map used with the last Update for Stack matching stack name.
2020-08-28 23:21:56 +02:00
// It will overwrite all configuration in the Pulumi.<stack>.yaml file in Workspace.WorkDir().
2020-09-15 02:45:07 +02:00
func ( l * LocalWorkspace ) RefreshConfig ( ctx context . Context , stackName string ) ( ConfigMap , error ) {
2021-04-22 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "config" , "refresh" , "--force" , "--stack" , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return nil , newAutoError ( errors . Wrap ( err , "could not refresh config" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-09-15 02:45:07 +02:00
cfg , err := l . GetAllConfig ( ctx , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
return nil , errors . Wrap ( err , "could not fetch config after refresh" )
}
return cfg , nil
2020-08-19 20:13:42 +02:00
}
2020-09-02 01:34:27 +02: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-02 01:58:09 +02:00
return setEnvVars ( l , envvars )
}
func setEnvVars ( l * LocalWorkspace , envvars map [ string ] string ) error {
2020-09-02 01:34:27 +02: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 20:16:54 +02:00
// WorkDir returns the working directory to run Pulumi CLI commands.
// LocalWorkspace expects that this directory contains a Pulumi.yaml file.
2020-08-28 23:21:56 +02:00
// For "Inline" Pulumi programs created from NewStackInlineSource, a Pulumi.yaml
2020-08-25 20:16:54 +02:00
// is created on behalf of the user if none is specified.
2020-08-19 20:13:42 +02:00
func ( l * LocalWorkspace ) WorkDir ( ) string {
return l . workDir
}
2020-08-25 20:16:54 +02:00
// PulumiHome returns the directory override for CLI metadata if set.
2020-08-29 04:10:39 +02:00
// This customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed.
func ( l * LocalWorkspace ) PulumiHome ( ) string {
2020-08-19 20:13:42 +02:00
return l . pulumiHome
}
2021-03-23 07:04:14 +01:00
// PulumiVersion returns the version of the underlying Pulumi CLI/Engine.
2021-03-23 23:09:50 +01:00
func ( l * LocalWorkspace ) PulumiVersion ( ) string {
return l . pulumiVersion . String ( )
2021-03-23 07:04:14 +01:00
}
2020-08-25 20:16:54 +02:00
// WhoAmI returns the currently authenticated user
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) WhoAmI ( ctx context . Context ) ( string , error ) {
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "whoami" )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return "" , newAutoError ( errors . Wrap ( err , "could not determine authenticated user" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
return strings . TrimSpace ( stdout ) , nil
2020-08-19 20:13:42 +02:00
}
2020-08-25 20:16:54 +02:00
// Stack returns a summary of the currently selected stack, if any.
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) Stack ( ctx context . Context ) ( * StackSummary , error ) {
stacks , err := l . ListStacks ( ctx )
2020-08-20 06:23:53 +02:00
if err != nil {
return nil , errors . Wrap ( err , "could not determine selected stack" )
}
for _ , s := range stacks {
if s . Current {
return & s , nil
}
}
2020-08-19 20:13:42 +02:00
return nil , nil
}
2020-09-15 02:45:07 +02: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 20:25:47 +02:00
if l . secretsProvider != "" {
2020-09-17 20:57:27 +02:00
args = append ( args , "--secrets-provider" , l . secretsProvider )
2020-09-10 20:25:47 +02:00
}
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , args ... )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return newAutoError ( errors . Wrap ( err , "failed to create stack" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-08-21 04:37:39 +02:00
return nil
2020-08-19 20:13:42 +02:00
}
2020-09-15 02:45:07 +02: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 {
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "select" , stackName )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return newAutoError ( errors . Wrap ( err , "failed to select stack" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-08-21 04:37:39 +02:00
return nil
2020-08-19 20:13:42 +02:00
}
2020-08-25 20:16:54 +02:00
// RemoveStack deletes the stack and all associated configuration and history.
2021-08-02 21:54:46 +02: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 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return newAutoError ( errors . Wrap ( err , "failed to remove stack" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
return nil
}
2020-08-25 20:16:54 +02:00
// ListStacks returns all Stacks created under the current Project.
2020-08-28 23:21:56 +02:00
// This queries underlying backend and may return stacks not present in the Workspace (as Pulumi.<stack>.yaml files).
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) ListStacks ( ctx context . Context ) ( [ ] StackSummary , error ) {
2020-08-20 06:23:53 +02:00
var stacks [ ] StackSummary
2020-08-22 07:20:32 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "ls" , "--json" )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return stacks , newAutoError ( errors . Wrap ( err , "could not list stacks" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
err = json . Unmarshal ( [ ] byte ( stdout ) , & stacks )
if err != nil {
return nil , errors . Wrap ( err , "unable to unmarshal config value" )
}
return stacks , nil
}
2020-08-28 23:21:56 +02:00
// InstallPlugin acquires the plugin matching the specified name and version.
2020-08-22 07:20:32 +02: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 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return newAutoError ( errors . Wrap ( err , "failed to install plugin" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-08-19 20:13:42 +02:00
return nil
}
2020-08-28 23:21:56 +02:00
// RemovePlugin deletes the plugin matching the specified name and verision.
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) RemovePlugin ( ctx context . Context , name string , version string ) error {
2020-10-09 18:03:03 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "plugin" , "rm" , "resource" , name , version , "--yes" )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return newAutoError ( errors . Wrap ( err , "failed to remove plugin" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
2020-08-19 20:13:42 +02:00
return nil
}
2020-08-25 20:16:54 +02:00
// ListPlugins lists all installed plugins.
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) ListPlugins ( ctx context . Context ) ( [ ] workspace . PluginInfo , error ) {
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "plugin" , "ls" , "--json" )
2020-08-20 06:23:53 +02:00
if err != nil {
2020-09-09 22:51:52 +02:00
return nil , newAutoError ( errors . Wrap ( err , "could not list list" ) , stdout , stderr , errCode )
2020-08-20 06:23:53 +02:00
}
var plugins [ ] workspace . PluginInfo
err = json . Unmarshal ( [ ] byte ( stdout ) , & plugins )
if err != nil {
return nil , errors . Wrap ( err , "unable to unmarshal plugin response" )
}
return plugins , nil
2020-08-19 20:13:42 +02:00
}
2020-08-25 20:16:54 +02:00
// Program returns the program `pulumi.RunFunc` to be used for Preview/Update if any.
2020-08-28 23:21:56 +02:00
// If none is specified, the stack will refer to ProjectSettings for this information.
2020-08-21 04:37:39 +02:00
func ( l * LocalWorkspace ) Program ( ) pulumi . RunFunc {
return l . program
}
2020-08-28 23:21:56 +02:00
// SetProgram sets the program associated with the Workspace to the specified `pulumi.RunFunc`.
2020-08-21 04:37:39 +02:00
func ( l * LocalWorkspace ) SetProgram ( fn pulumi . RunFunc ) {
l . program = fn
}
2020-09-15 23:20:58 +02: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 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "export" , "--show-secrets" , "--stack" , stackName )
2020-09-15 23:20:58 +02:00
if err != nil {
return state , newAutoError ( errors . Wrap ( err , "could not export stack." ) , stdout , stderr , errCode )
}
err = json . Unmarshal ( [ ] byte ( stdout ) , & state )
if err != nil {
return state , newAutoError (
errors . Wrap ( err , "failed to export stack, unable to unmarshall stack state." ) , 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 := ioutil . TempFile ( os . TempDir ( ) , "" )
if err != nil {
2021-01-13 04:27:21 +01:00
return errors . Wrap ( err , "could not import stack. failed to allocate temp file." )
2020-09-15 23:20:58 +02:00
}
defer func ( ) { contract . IgnoreError ( os . Remove ( f . Name ( ) ) ) } ( )
bytes , err := json . Marshal ( state )
if err != nil {
return errors . Wrap ( err , "could not import stack, failed to marshal stack state." )
}
_ , err = f . Write ( bytes )
if err != nil {
return errors . Wrap ( err , "could not import stack. failed to write out stack intermediate." )
}
2021-04-22 15:10:39 +02:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "stack" , "import" , "--file" , f . Name ( ) , "--stack" , stackName )
2020-09-15 23:20:58 +02:00
if err != nil {
return newAutoError ( errors . Wrap ( err , "could not import stack." ) , stdout , stderr , errCode )
}
return nil
}
2021-04-27 01:32:30 +02:00
// Outputs get 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 ( errors . Wrap ( err , "could not get outputs" ) , 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 ( errors . Wrap ( err , "could not get secret outputs" ) , outStdout , outStderr , code )
}
var outputs map [ string ] interface { }
var secrets map [ string ] interface { }
if err = json . Unmarshal ( [ ] byte ( outStdout ) , & outputs ) ; err != nil {
return nil , errors . Wrapf ( err , "error unmarshalling outputs: %s" , secretStderr )
}
if err = json . Unmarshal ( [ ] byte ( secretStdout ) , & secrets ) ; err != nil {
return nil , errors . Wrapf ( err , "error unmarshalling secret outputs: %s" , secretStderr )
}
res := make ( OutputMap )
for k , v := range secrets {
2021-10-08 23:43:10 +02:00
raw , err := json . Marshal ( outputs [ k ] )
if err != nil {
return nil , errors . Wrapf ( err , "error determining secretness: %s" , secretStderr )
}
rawString := string ( raw )
isSecret := strings . Contains ( rawString , secretSentinel )
2021-04-27 01:32:30 +02:00
res [ k ] = OutputValue {
Value : v ,
Secret : isSecret ,
}
}
return res , nil
}
2021-10-28 05:54:23 +02:00
func ( l * LocalWorkspace ) getPulumiVersion ( ctx context . Context ) ( string , error ) {
2021-03-23 07:04:14 +01:00
stdout , stderr , errCode , err := l . runPulumiCmdSync ( ctx , "version" )
if err != nil {
2021-10-28 05:54:23 +02:00
return "" , newAutoError ( errors . Wrap ( err , "could not determine pulumi version" ) , stdout , stderr , errCode )
2021-03-23 07:04:14 +01:00
}
2021-10-28 05:54:23 +02:00
return stdout , nil
2021-03-23 07:04:14 +01:00
}
//nolint:lll
2021-10-28 05:54:23 +02:00
func parseAndValidatePulumiVersion ( minVersion semver . Version , currentVersion string , optOut bool ) ( semver . Version , error ) {
version , err := semver . ParseTolerant ( currentVersion )
if err != nil && ! optOut {
return semver . Version { } , errors . Wrapf ( err , "Unable to parse Pulumi CLI version (skip with %s=true)" , skipVersionCheckVar )
}
2021-04-28 05:54:27 +02:00
if optOut {
2021-10-28 05:54:23 +02:00
return version , nil
2021-04-28 05:54:27 +02:00
}
2021-10-28 05:54:23 +02:00
if minVersion . Major < version . Major {
return semver . Version { } , errors . Errorf ( "Major version mismatch. You are using Pulumi CLI version %s with Automation SDK v%v. Please update the SDK." , currentVersion , minVersion . Major )
2021-03-23 07:04:14 +01:00
}
2021-10-28 05:54:23 +02:00
if minVersion . GT ( version ) {
return semver . Version { } , errors . 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 )
2021-03-23 07:04:14 +01:00
}
2021-10-28 05:54:23 +02:00
return version , nil
2021-03-23 07:04:14 +01:00
}
2020-08-22 07:20:32 +02:00
func ( l * LocalWorkspace ) runPulumiCmdSync (
ctx context . Context ,
args ... string ,
) ( string , string , int , error ) {
2020-08-19 20:13:42 +02:00
var env [ ] string
2020-08-29 04:10:39 +02:00
if l . PulumiHome ( ) != "" {
homeEnv := fmt . Sprintf ( "%s=%s" , pulumiHomeEnv , l . PulumiHome ( ) )
2020-08-22 01:22:45 +02:00
env = append ( env , homeEnv )
2020-08-19 20:13:42 +02:00
}
2020-09-02 01:34:27 +02:00
if envvars := l . GetEnvVars ( ) ; envvars != nil {
for k , v := range envvars {
e := [ ] string { k , v }
env = append ( env , strings . Join ( e , "=" ) )
}
}
2020-09-15 03:56:04 +02:00
return runPulumiCommandSync ( ctx , l . WorkDir ( ) , nil /* additionalOutputs */ , env , args ... )
2020-08-19 20:13:42 +02:00
}
2020-08-28 23:21:56 +02:00
// NewLocalWorkspace creates and configures a LocalWorkspace. LocalWorkspaceOptions can be used to
2020-08-25 20:16:54 +02: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 07:20:32 +02:00
func NewLocalWorkspace ( ctx context . Context , opts ... LocalWorkspaceOption ) ( Workspace , error ) {
2020-08-19 20:13:42 +02: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 := ioutil . TempDir ( "" , "pulumi_auto" )
if err != nil {
return nil , errors . Wrap ( err , "unable to create tmp directory for workspace" )
}
workDir = dir
}
if lwOpts . Repo != nil {
// now do the git clone
2020-08-22 07:20:32 +02:00
projDir , err := setupGitRepo ( ctx , workDir , lwOpts . Repo )
2020-08-19 20:13:42 +02:00
if err != nil {
return nil , errors . Wrap ( err , "failed to create workspace, unable to enlist in git repo" )
}
workDir = projDir
}
var program pulumi . RunFunc
if lwOpts . Program != nil {
program = lwOpts . Program
}
l := & LocalWorkspace {
workDir : workDir ,
program : program ,
2020-08-29 04:10:39 +02:00
pulumiHome : lwOpts . PulumiHome ,
2020-08-19 20:13:42 +02:00
}
2021-10-27 01:20:45 +02:00
// optOut indicates we should skip the version check.
optOut := cmdutil . IsTruthy ( os . Getenv ( skipVersionCheckVar ) )
2021-04-30 18:41:31 +02:00
if val , ok := lwOpts . EnvVars [ skipVersionCheckVar ] ; ok {
2021-10-27 01:20:45 +02:00
optOut = optOut || cmdutil . IsTruthy ( val )
}
2021-10-28 05:54:23 +02:00
currentVersion , err := l . getPulumiVersion ( ctx )
if err != nil {
return nil , err
2021-04-28 05:54:27 +02:00
}
2021-10-28 05:54:23 +02:00
if l . pulumiVersion , err = parseAndValidatePulumiVersion ( minimumVersion , currentVersion , optOut ) ; err != nil {
return nil , err
2021-03-23 07:04:14 +01:00
}
2020-08-19 20:13:42 +02:00
if lwOpts . Project != nil {
2020-08-29 04:10:39 +02:00
err := l . SaveProjectSettings ( ctx , lwOpts . Project )
2020-08-19 20:13:42 +02:00
if err != nil {
return nil , errors . Wrap ( err , "failed to create workspace, unable to save project settings" )
}
}
2020-09-15 02:45:07 +02:00
for stackName := range lwOpts . Stacks {
s := lwOpts . Stacks [ stackName ]
err := l . SaveStackSettings ( ctx , stackName , & s )
2020-08-19 20:13:42 +02:00
if err != nil {
return nil , errors . Wrap ( err , "failed to create workspace" )
}
}
2020-09-02 21:40:15 +02:00
// setup
if lwOpts . Repo != nil && lwOpts . Repo . Setup != nil {
err := lwOpts . Repo . Setup ( ctx , l )
if err != nil {
return nil , errors . Wrap ( err , "error while running setup function" )
}
}
2020-09-10 20:25:47 +02:00
// Secrets providers
if lwOpts . SecretsProvider != "" {
l . secretsProvider = lwOpts . SecretsProvider
}
2020-10-02 01:58:09 +02:00
// Environment values
if lwOpts . EnvVars != nil {
if err := setEnvVars ( l , lwOpts . EnvVars ) ; err != nil {
return nil , errors . Wrap ( err , "failed to set environment values" )
}
}
2020-08-19 20:13:42 +02:00
return l , nil
}
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 23:21:56 +02:00
// the program identified in $WORKDIR/Pulumi.yaml will be used instead.
2020-08-19 20:13:42 +02:00
Program pulumi . RunFunc
2020-08-29 04:10:39 +02: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 20:13:42 +02:00
Project * workspace . Project
2020-09-15 02:45:07 +02:00
// Stacks is a map of [stackName -> stack settings objects] to seed the workspace.
2020-08-19 20:13:42 +02: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 20:25:47 +02:00
// Secrets Provider to use with the current Stack
SecretsProvider string
2020-10-02 01:58:09 +02: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
2020-08-19 20:13:42 +02:00
}
2020-08-25 20:16:54 +02: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 20:13:42 +02:00
type LocalWorkspaceOption interface {
applyLocalWorkspaceOption ( * localWorkspaceOptions )
}
type localWorkspaceOption func ( * localWorkspaceOptions )
func ( o localWorkspaceOption ) applyLocalWorkspaceOption ( opts * localWorkspaceOptions ) {
o ( opts )
}
2020-08-25 20:16:54 +02:00
// GitRepo contains info to acquire and setup a Pulumi program from a git repository.
2020-08-19 20:13:42 +02: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 07:04:14 +01:00
// Specifying this option will update the Workspace's WorkDir accordingly.
2020-08-19 20:13:42 +02:00
ProjectPath string
2020-08-28 23:21:56 +02:00
// Optional branch to checkout.
2020-08-19 20:13:42 +02:00
Branch string
2020-08-28 23:21:56 +02:00
// Optional commit to checkout.
2020-08-19 20:13:42 +02:00
CommitHash string
// Optional function to execute after enlisting in the specified repo.
Setup SetupFn
2020-09-14 21:24:57 +02:00
// GitAuth is the different Authentication options for the Git repository
Auth * GitAuth
}
// 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 16:48:44 +02: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 21:24:57 +02:00
SSHPrivateKeyPath string
2020-10-12 20:51:26 +02: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 21:24:57 +02: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 20:13:42 +02:00
}
// SetupFn is a function to execute after enlisting in a git repo.
2020-09-02 21:40:15 +02:00
// It is called with the workspace after all other options have been processed.
type SetupFn func ( context . Context , Workspace ) error
2020-08-19 20:13:42 +02: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 04:37:39 +02:00
// Program is the Pulumi Program to execute. If none is supplied,
2020-08-28 23:21:56 +02:00
// the program identified in $WORKDIR/Pulumi.yaml will be used instead.
2020-08-19 20:13:42 +02:00
func Program ( program pulumi . RunFunc ) LocalWorkspaceOption {
return localWorkspaceOption ( func ( lo * localWorkspaceOptions ) {
lo . Program = program
} )
}
2020-08-28 23:21:56 +02:00
// PulumiHome overrides the metadata directory for pulumi commands.
2020-08-19 20:13:42 +02:00
func PulumiHome ( dir string ) LocalWorkspaceOption {
return localWorkspaceOption ( func ( lo * localWorkspaceOptions ) {
2020-08-29 04:10:39 +02:00
lo . PulumiHome = dir
2020-08-19 20:13:42 +02:00
} )
}
2020-08-28 23:21:56 +02:00
// Project sets project settings for the workspace.
2020-08-19 20:13:42 +02:00
func Project ( settings workspace . Project ) LocalWorkspaceOption {
return localWorkspaceOption ( func ( lo * localWorkspaceOptions ) {
lo . Project = & settings
} )
}
2020-08-28 23:21:56 +02:00
// Stacks is a list of stack settings objects to seed the workspace.
2020-08-19 20:13:42 +02:00
func Stacks ( settings map [ string ] workspace . ProjectStack ) LocalWorkspaceOption {
return localWorkspaceOption ( func ( lo * localWorkspaceOptions ) {
lo . Stacks = settings
} )
}
2020-08-21 18:49:46 +02:00
// Repo is a git repo with a Pulumi Project to clone into the WorkDir.
func Repo ( gitRepo GitRepo ) LocalWorkspaceOption {
2020-08-19 20:13:42 +02:00
return localWorkspaceOption ( func ( lo * localWorkspaceOptions ) {
lo . Repo = & gitRepo
} )
}
2020-09-10 20:25:47 +02: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-02 01:58:09 +02: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
} )
}
2020-08-25 20:16:54 +02: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 23:21:56 +02:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
2020-09-15 02:45:07 +02:00
func NewStackLocalSource ( ctx context . Context , stackName , workDir string , opts ... LocalWorkspaceOption ) ( Stack , error ) {
2020-08-21 18:49:46 +02:00
opts = append ( opts , WorkDir ( workDir ) )
2020-08-22 07:20:32 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 18:49:46 +02:00
var stack Stack
if err != nil {
return stack , errors . Wrap ( err , "failed to create stack" )
}
2020-09-15 02:45:07 +02:00
return NewStack ( ctx , stackName , w )
2020-08-21 18:49:46 +02:00
}
2020-09-10 00:20:41 +02: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 02:45:07 +02:00
func UpsertStackLocalSource (
ctx context . Context ,
stackName ,
workDir string ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
2020-09-10 00:20:41 +02:00
opts = append ( opts , WorkDir ( workDir ) )
w , err := NewLocalWorkspace ( ctx , opts ... )
var stack Stack
if err != nil {
return stack , errors . Wrap ( err , "failed to create stack" )
}
2020-09-15 02:45:07 +02:00
return UpsertStack ( ctx , stackName , w )
2020-09-10 00:20:41 +02:00
}
2020-08-25 20:16:54 +02: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 23:21:56 +02:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
2020-09-15 02:45:07 +02:00
func SelectStackLocalSource (
ctx context . Context ,
stackName ,
workDir string ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
2020-08-21 18:49:46 +02:00
opts = append ( opts , WorkDir ( workDir ) )
2020-08-22 07:20:32 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 18:49:46 +02:00
var stack Stack
if err != nil {
return stack , errors . Wrap ( err , "failed to select stack" )
}
2020-09-15 02:45:07 +02:00
return SelectStack ( ctx , stackName , w )
2020-08-21 18:49:46 +02:00
}
2020-08-25 20:16:54 +02: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 23:21:56 +02:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml) that are cloned into the Workspace.
2020-08-25 20:16:54 +02:00
// Unless a WorkDir option is specified, the GitRepo will be clone into a new temporary directory provided by the OS.
2020-09-15 02:45:07 +02:00
func NewStackRemoteSource (
ctx context . Context ,
stackName string ,
repo GitRepo ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
2020-08-21 18:49:46 +02:00
opts = append ( opts , Repo ( repo ) )
2020-08-22 07:20:32 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 18:49:46 +02:00
var stack Stack
if err != nil {
return stack , errors . Wrap ( err , "failed to create stack" )
}
2020-09-15 02:45:07 +02:00
return NewStack ( ctx , stackName , w )
2020-08-21 18:49:46 +02:00
}
2020-09-10 00:20:41 +02: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 (
2020-09-15 02:45:07 +02:00
ctx context . Context , stackName string , repo GitRepo , opts ... LocalWorkspaceOption ) ( Stack , error ) {
2020-09-10 00:20:41 +02:00
opts = append ( opts , Repo ( repo ) )
w , err := NewLocalWorkspace ( ctx , opts ... )
var stack Stack
if err != nil {
return stack , errors . Wrap ( err , "failed to create stack" )
}
2020-09-15 02:45:07 +02:00
return UpsertStack ( ctx , stackName , w )
2020-09-10 00:20:41 +02:00
}
2020-08-25 20:16:54 +02: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 23:21:56 +02:00
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml) that are cloned into the Workspace.
2020-08-25 20:16:54 +02:00
// Unless a WorkDir option is specified, the GitRepo will be clone into a new temporary directory provided by the OS.
2020-08-22 21:01:55 +02:00
func SelectStackRemoteSource (
ctx context . Context ,
2020-09-15 02:45:07 +02:00
stackName string , repo GitRepo ,
2020-08-22 21:01:55 +02:00
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
2020-08-21 18:49:46 +02:00
opts = append ( opts , Repo ( repo ) )
2020-08-22 07:20:32 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 18:49:46 +02:00
var stack Stack
if err != nil {
return stack , errors . Wrap ( err , "failed to select stack" )
}
2020-09-15 02:45:07 +02:00
return SelectStack ( ctx , stackName , w )
2020-08-21 18:49:46 +02:00
}
2020-08-25 20:16:54 +02: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 18:49:46 +02:00
func NewStackInlineSource (
2020-08-22 07:20:32 +02:00
ctx context . Context ,
2020-09-15 02:45:07 +02:00
stackName string ,
projectName string ,
2020-08-21 18:49:46 +02:00
program pulumi . RunFunc ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
var stack Stack
opts = append ( opts , Program ( program ) )
2021-03-31 16:51:11 +02:00
proj , err := getProjectSettings ( ctx , projectName , opts )
2020-08-21 18:49:46 +02:00
if err != nil {
2021-03-31 16:51:11 +02:00
return stack , err
2020-08-21 18:49:46 +02:00
}
2021-03-31 20:00:11 +02:00
if proj != nil {
opts = append ( opts , Project ( * proj ) )
}
2021-03-31 16:51:11 +02:00
2020-08-22 07:20:32 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 18:49:46 +02:00
if err != nil {
return stack , errors . Wrap ( err , "failed to create stack" )
}
2020-09-15 02:45:07 +02:00
return NewStack ( ctx , stackName , w )
2020-08-21 18:49:46 +02:00
}
2020-09-10 00:20:41 +02: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 02:45:07 +02:00
stackName string ,
projectName string ,
2020-09-10 00:20:41 +02:00
program pulumi . RunFunc ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
var stack Stack
opts = append ( opts , Program ( program ) )
2021-03-31 16:51:11 +02:00
proj , err := getProjectSettings ( ctx , projectName , opts )
2020-09-10 00:20:41 +02:00
if err != nil {
2021-03-31 16:51:11 +02:00
return stack , err
2020-09-10 00:20:41 +02:00
}
2021-03-31 20:00:11 +02:00
if proj != nil {
opts = append ( opts , Project ( * proj ) )
}
2021-03-31 16:51:11 +02:00
2020-09-10 00:20:41 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
if err != nil {
return stack , errors . Wrap ( err , "failed to create stack" )
}
2020-09-15 02:45:07 +02:00
return UpsertStack ( ctx , stackName , w )
2020-09-10 00:20:41 +02:00
}
2020-08-25 20:16:54 +02: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 18:49:46 +02:00
func SelectStackInlineSource (
2020-08-22 07:20:32 +02:00
ctx context . Context ,
2020-09-15 02:45:07 +02:00
stackName string ,
projectName string ,
2020-08-21 18:49:46 +02:00
program pulumi . RunFunc ,
opts ... LocalWorkspaceOption ,
) ( Stack , error ) {
var stack Stack
opts = append ( opts , Program ( program ) )
2021-03-31 16:51:11 +02:00
proj , err := getProjectSettings ( ctx , projectName , opts )
2020-08-21 18:49:46 +02:00
if err != nil {
2021-03-31 16:51:11 +02:00
return stack , err
2020-08-21 18:49:46 +02:00
}
2021-03-31 20:00:11 +02:00
if proj != nil {
opts = append ( opts , Project ( * proj ) )
}
2021-03-31 16:51:11 +02:00
2020-08-22 07:20:32 +02:00
w , err := NewLocalWorkspace ( ctx , opts ... )
2020-08-21 18:49:46 +02:00
if err != nil {
return stack , errors . Wrap ( err , "failed to select stack" )
}
2020-09-15 02:45:07 +02:00
return SelectStack ( ctx , stackName , w )
2020-08-21 18:49:46 +02:00
}
2020-09-15 02:45:07 +02:00
func defaultInlineProject ( projectName string ) ( workspace . Project , error ) {
2020-08-21 18:49:46 +02:00
var proj workspace . Project
2021-04-10 05:30:11 +02:00
cwd , err := os . Getwd ( )
if err != nil {
return proj , err
}
2020-08-21 18:49:46 +02:00
proj = workspace . Project {
2020-09-15 02:45:07 +02:00
Name : tokens . PackageName ( projectName ) ,
2020-08-21 18:49:46 +02:00
Runtime : workspace . NewProjectRuntimeInfo ( "go" , nil ) ,
2021-04-10 05:30:11 +02:00
Main : cwd ,
2020-08-21 18:49:46 +02:00
}
return proj , nil
}
2020-08-25 20:16:54 +02:00
2020-09-15 02:45:07 +02: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 20:16:54 +02:00
}
2020-09-15 02:45:07 +02:00
return parts [ len ( parts ) - 1 ]
2020-08-25 20:16:54 +02:00
}
const pulumiHomeEnv = "PULUMI_HOME"
2021-03-31 16:51:11 +02: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 {
return nil , errors . Wrap ( err , "found project settings, but failed to load" )
}
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 20:00:11 +02:00
_ , err := readProjectSettingsFromDir ( ctx , optsBag . WorkDir )
2021-03-31 16:51:11 +02:00
if err == nil {
2021-03-31 20:00:11 +02:00
return nil , nil
2021-03-31 16:51:11 +02:00
}
if err . Error ( ) == "unable to find project settings in workspace" {
proj , err := defaultInlineProject ( projectName )
if err != nil {
return nil , errors . Wrap ( err , "failed to create default project" )
}
return & proj , nil
}
return nil , errors . Wrap ( err , "failed to load project settings" )
}
// If there was no workdir specified, create the default project.
proj , err := defaultInlineProject ( projectName )
if err != nil {
return nil , errors . Wrap ( err , "failed to create default project" )
}
return & proj , nil
}