[cli] Add the ability to control auto-refresh of stacks by Pulumi.yaml (#8071)

Co-authored-by: Komal Ali <komal@pulumi.com>
This commit is contained in:
Paul Stack 2021-09-29 10:43:48 +01:00 committed by GitHub
parent 4dc9c4bf39
commit 09a8cc7079
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 14 deletions

View file

@ -3,6 +3,10 @@
- [cli] - Differentiate in-progress actions by bolding output.
[#7918](https://github.com/pulumi/pulumi/pull/7918)
- [CLI] Adding the ability to set `refresh: always` in an options object at a Pulumi.yaml level
to allow a user to be able to always refresh their derivative stacks by default
[#8071](https://github.com/pulumi/pulumi/pull/8071)
### Bug Fixes
- [codegen/go] - Fix generation of cyclic struct types.

View file

@ -41,7 +41,7 @@ func newDestroyCmd() *cobra.Command {
var diffDisplay bool
var eventLogPath string
var parallel int
var refresh bool
var refresh string
var showConfig bool
var showReplacementSteps bool
var showSames bool
@ -143,10 +143,14 @@ func newDestroyCmd() *cobra.Command {
targetUrns = append(targetUrns, resource.URN(t))
}
refreshOption, err := getRefreshOption(proj, refresh)
if err != nil {
return result.FromError(err)
}
opts.Engine = engine.UpdateOptions{
Parallel: parallel,
Debug: debug,
Refresh: refresh,
Refresh: refreshOption,
DestroyTargets: targetUrns,
TargetDependents: targetDependents,
UseLegacyDiff: useLegacyDiff(),
@ -204,9 +208,10 @@ func newDestroyCmd() *cobra.Command {
cmd.PersistentFlags().IntVarP(
&parallel, "parallel", "p", defaultParallel,
"Allow P resource operations to run in parallel at once (1 for no parallelism). Defaults to unbounded.")
cmd.PersistentFlags().BoolVarP(
&refresh, "refresh", "r", false,
cmd.PersistentFlags().StringVarP(
&refresh, "refresh", "r", "",
"Refresh the state of the stack's resources before this update")
cmd.PersistentFlags().Lookup("refresh").NoOptDefVal = "true"
cmd.PersistentFlags().BoolVar(
&showConfig, "show-config", false,
"Show configuration keys and variables")

View file

@ -44,7 +44,7 @@ func newPreviewCmd() *cobra.Command {
var diffDisplay bool
var eventLogPath string
var parallel int
var refresh bool
var refresh string
var showConfig bool
var showReplacementSteps bool
var showSames bool
@ -160,12 +160,17 @@ func newPreviewCmd() *cobra.Command {
replaceURNs = append(replaceURNs, resource.URN(tr))
}
refreshOption, err := getRefreshOption(proj, refresh)
if err != nil {
return result.FromError(err)
}
opts := backend.UpdateOptions{
Engine: engine.UpdateOptions{
LocalPolicyPacks: engine.MakeLocalPolicyPacks(policyPackPaths, policyPackConfigPaths),
Parallel: parallel,
Debug: debug,
Refresh: refresh,
Refresh: refreshOption,
ReplaceTargets: replaceURNs,
UseLegacyDiff: useLegacyDiff(),
DisableProviderPreview: disableProviderPreview(),
@ -256,9 +261,10 @@ func newPreviewCmd() *cobra.Command {
cmd.PersistentFlags().IntVarP(
&parallel, "parallel", "p", defaultParallel,
"Allow P resource operations to run in parallel at once (1 for no parallelism). Defaults to unbounded.")
cmd.PersistentFlags().BoolVarP(
&refresh, "refresh", "r", false,
cmd.PersistentFlags().StringVarP(
&refresh, "refresh", "r", "",
"Refresh the state of the stack's resources before this update")
cmd.PersistentFlags().Lookup("refresh").NoOptDefVal = "true"
cmd.PersistentFlags().BoolVar(
&showConfig, "show-config", false,
"Show configuration keys and variables")

View file

@ -60,7 +60,7 @@ func newUpCmd() *cobra.Command {
var diffDisplay bool
var eventLogPath string
var parallel int
var refresh bool
var refresh string
var showConfig bool
var showReplacementSteps bool
var showSames bool
@ -122,11 +122,15 @@ func newUpCmd() *cobra.Command {
replaceURNs = append(replaceURNs, resource.URN(tr))
}
refreshOption, err := getRefreshOption(proj, refresh)
if err != nil {
return result.FromError(err)
}
opts.Engine = engine.UpdateOptions{
LocalPolicyPacks: engine.MakeLocalPolicyPacks(policyPackPaths, policyPackConfigPaths),
Parallel: parallel,
Debug: debug,
Refresh: refresh,
Refresh: refreshOption,
RefreshTargets: targetURNs,
ReplaceTargets: replaceURNs,
UseLegacyDiff: useLegacyDiff(),
@ -287,11 +291,16 @@ func newUpCmd() *cobra.Command {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
refreshOption, err := getRefreshOption(proj, refresh)
if err != nil {
return result.FromError(err)
}
opts.Engine = engine.UpdateOptions{
LocalPolicyPacks: engine.MakeLocalPolicyPacks(policyPackPaths, policyPackConfigPaths),
Parallel: parallel,
Debug: debug,
Refresh: refresh,
Refresh: refreshOption,
}
// TODO for the URL case:
@ -458,9 +467,10 @@ func newUpCmd() *cobra.Command {
cmd.PersistentFlags().IntVarP(
&parallel, "parallel", "p", defaultParallel,
"Allow P resource operations to run in parallel at once (1 for no parallelism). Defaults to unbounded.")
cmd.PersistentFlags().BoolVarP(
&refresh, "refresh", "r", false,
cmd.PersistentFlags().StringVarP(
&refresh, "refresh", "r", "",
"Refresh the state of the stack's resources before this update")
cmd.PersistentFlags().Lookup("refresh").NoOptDefVal = "true"
cmd.PersistentFlags().BoolVar(
&showConfig, "show-config", false,
"Show configuration keys and variables")

View file

@ -839,3 +839,26 @@ func checkDeploymentVersionError(err error, stackName string) error {
}
return errors.Wrap(err, "could not deserialize deployment")
}
func getRefreshOption(proj *workspace.Project, refresh string) (bool, error) {
// we want to check for an explicit --refresh or a --refresh=true or --refresh=false
// refresh is assigned the empty string by default to distinguish the difference between
// when the user actually interacted with the cli argument (`NoOptDefVal`)
// and the default functionality today
if refresh != "" {
refreshDetails, boolErr := strconv.ParseBool(refresh)
if boolErr != nil {
// the user has passed a --refresh but with a random value that we don't support
return false, errors.New("unable to determine value for --refresh")
}
return refreshDetails, nil
}
// the user has not specifically passed an argument on the cli to refresh but has set a Project option to refresh
if proj.Options != nil && proj.Options.Refresh == "always" {
return true, nil
}
// the default functionality right now is to always skip a refresh
return false, nil
}

View file

@ -17,10 +17,12 @@ import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/v3/backend"
pul_testing "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/gitutil"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
)
// assertEnvValue assert the update metadata's Environment map contains the given value.
@ -264,3 +266,66 @@ func Test_makeJSONString(t *testing.T) {
})
}
}
func TestGetRefreshOption(t *testing.T) {
tests := []struct {
name string
refresh string
project workspace.Project
expectedRefreshState bool
}{
{
"No options specified means no refresh",
"",
workspace.Project{},
false,
},
{
"Passing --refresh=true causes a refresh",
"true",
workspace.Project{},
true,
},
{
"Passing --refresh=false causes no refresh",
"false",
workspace.Project{},
false,
},
{
"Setting Refresh at a project level via Pulumi.yaml and no CLI args",
"",
workspace.Project{
Name: "auto-refresh",
Runtime: workspace.ProjectRuntimeInfo{},
Options: &workspace.ProjectOptions{
Refresh: "always",
},
},
true,
},
{
"Setting Refresh at a project level via Pulumi.yaml and --refresh=false",
"false",
workspace.Project{
Name: "auto-refresh",
Runtime: workspace.ProjectRuntimeInfo{},
Options: &workspace.ProjectOptions{
Refresh: "always",
},
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
shouldRefresh, err := getRefreshOption(&tt.project, tt.refresh)
if err != nil {
t.Errorf("getRefreshOption() error = %v", err)
}
if shouldRefresh != tt.expectedRefreshState {
t.Errorf("getRefreshOption got = %t, expected %t", shouldRefresh, tt.expectedRefreshState)
}
})
}
}

View file

@ -58,6 +58,11 @@ type ProjectBackend struct {
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
type ProjectOptions struct {
// Refresh is the ability to always run a refresh as part of a pulumi update / preview / destroy
Refresh string `json:"refresh,omitempty" yaml:"refresh,omitempty"`
}
// Project is a Pulumi project manifest.
//
// We explicitly add yaml tags (instead of using the default behavior from https://github.com/ghodss/yaml which works
@ -90,6 +95,9 @@ type Project struct {
// Backend is an optional backend configuration
Backend *ProjectBackend `json:"backend,omitempty" yaml:"backend,omitempty"`
// Options is an optional set of project options
Options *ProjectOptions `json:"options,omitempty" yaml:"options,omitempty"`
}
func (proj *Project) Validate() error {