Bring back preview, swizzle some flags
This changes the CLI interface in a few ways: * `pulumi preview` is back! The alternative of saying `pulumi update --preview` just felt awkward, and it's a common operation to want to perform. Let's just make it work. * There are two flags consistent across all update commands, `update`, `refresh`, and `destroy`: - `--skip-preview` will skip the preview step. Note that this does *not* skip the prompt to confirm that you'd like to proceed. Indeed, it will still prompt, with a little warning text about the fact that the preview has been skipped. * `--yes` will auto-approve the updates. This lands us in a simpler and more intuitive spot for common scenarios.
This commit is contained in:
parent
6ad785d5c4
commit
7c7f6d3ed7
|
@ -4,7 +4,6 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -25,11 +24,12 @@ func newDestroyCmd() *cobra.Command {
|
|||
var color colorFlag
|
||||
var diffDisplay bool
|
||||
var parallel int
|
||||
var preview string
|
||||
var showConfig bool
|
||||
var showReplacementSteps bool
|
||||
var showSames bool
|
||||
var nonInteractive bool
|
||||
var skipPreview bool
|
||||
var yes bool
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "destroy",
|
||||
|
@ -45,8 +45,12 @@ func newDestroyCmd() *cobra.Command {
|
|||
"is generally irreversible and should be used with great care.",
|
||||
Args: cmdutil.NoArgs,
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
interactive := isInteractive(cmd)
|
||||
behavior, err := previewFlagsToBehavior(interactive, preview)
|
||||
interactive := isInteractive(nonInteractive)
|
||||
if !interactive {
|
||||
yes = true // auto-approve changes, since we cannot prompt.
|
||||
}
|
||||
|
||||
opts, err := updateFlagsToOptions(interactive, skipPreview, yes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -65,32 +69,22 @@ func newDestroyCmd() *cobra.Command {
|
|||
return errors.Wrap(err, "gathering environment metadata")
|
||||
}
|
||||
|
||||
if behavior.Interactive() {
|
||||
prompt := fmt.Sprintf("This will permanently destroy all resources in the '%s' stack!", s.Name())
|
||||
if !confirmPrompt(prompt, s.Name().String()) {
|
||||
return errors.New("confirmation declined")
|
||||
}
|
||||
opts.Engine = engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
}
|
||||
opts.Display = backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: interactive,
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
}
|
||||
|
||||
err = s.Destroy(
|
||||
proj, root, m,
|
||||
engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
},
|
||||
behavior,
|
||||
backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: interactive,
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
},
|
||||
cancellationScopes,
|
||||
)
|
||||
err = s.Destroy(proj, root, m, opts, cancellationScopes)
|
||||
if err == context.Canceled {
|
||||
return errors.New("destroy cancelled")
|
||||
}
|
||||
|
@ -117,12 +111,11 @@ func newDestroyCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&diffDisplay, "diff", false,
|
||||
"Display operation as a rich diff showing the overall change")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().IntVarP(
|
||||
¶llel, "parallel", "p", 0,
|
||||
"Allow P resource operations to run in parallel at once (<=1 for no parallelism)")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&preview, "preview", "",
|
||||
"Preview behavior. Choices are: only (dry-run), skip (no preview, just update), auto (auto-accept)")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&showConfig, "show-config", false,
|
||||
"Show configuration keys and variables")
|
||||
|
@ -132,7 +125,12 @@ func newDestroyCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&showSames, "show-sames", false,
|
||||
"Show resources that don't need to be updated because they haven't changed, alongside those that do")
|
||||
cmd.PersistentFlags().BoolVar(&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&skipPreview, "skip-preview", false,
|
||||
"Do not perform a preview before performing the destroy")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&yes, "yes", false,
|
||||
"Automatically approve and perform the destroy after previewing it")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -3,21 +3,24 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/backend"
|
||||
"github.com/pulumi/pulumi/pkg/engine"
|
||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||
)
|
||||
|
||||
func newPreviewCmd() *cobra.Command {
|
||||
var debug bool
|
||||
var message string
|
||||
var stack string
|
||||
|
||||
// Flags for engine.UpdateOptions.
|
||||
var analyzers []string
|
||||
var color colorFlag
|
||||
var diffDisplay bool
|
||||
var nonInteractive bool
|
||||
var parallel int
|
||||
var showConfig bool
|
||||
var showReplacementSteps bool
|
||||
|
@ -27,12 +30,52 @@ func newPreviewCmd() *cobra.Command {
|
|||
Use: "preview",
|
||||
Aliases: []string{"pre"},
|
||||
SuggestFor: []string{"build", "plan"},
|
||||
Short: "Deprecated. Use 'pulumi update --preview' instead",
|
||||
Long: "Deprecated. Use 'pulumi update --preview' instead",
|
||||
Args: cmdutil.NoArgs,
|
||||
Short: "Show a preview of updates to a stack's resources",
|
||||
Long: "Show a preview of updates a stack's resources.\n" +
|
||||
"\n" +
|
||||
"This command displays a preview of the updates to an existing stack whose state is\n" +
|
||||
"represented by an existing snapshot file. The new desired state is computed by running\n" +
|
||||
"a Pulumi program, and extracting all resource allocations from its resulting object graph.\n" +
|
||||
"These allocations are then compared against the existing state to determine what\n" +
|
||||
"operations must take place to achieve the desired state. No changes to the stack will\n" +
|
||||
"actually take place.\n" +
|
||||
"\n" +
|
||||
"The program to run is loaded from the project in the current directory. Use the `-C` or\n" +
|
||||
"`--cwd` flag to use a different directory.",
|
||||
Args: cmdutil.NoArgs,
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
return errors.New(
|
||||
"`pulumi preview` is deprecated; `pulumi update` now perform previews by default")
|
||||
s, err := requireStack(stack, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proj, root, err := readProject()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := getUpdateMetadata("", root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "gathering environment metadata")
|
||||
}
|
||||
|
||||
opts := backend.UpdateOptions{
|
||||
Engine: engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
},
|
||||
Display: backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: isInteractive(nonInteractive),
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
},
|
||||
}
|
||||
return s.Preview(proj, root, m, opts, cancellationScopes)
|
||||
}),
|
||||
}
|
||||
|
||||
|
@ -43,6 +86,10 @@ func newPreviewCmd() *cobra.Command {
|
|||
&stack, "stack", "s", "",
|
||||
"Choose a stack other than the currently selected one")
|
||||
|
||||
cmd.PersistentFlags().StringVarP(
|
||||
&message, "message", "m", "",
|
||||
"Optional message to associate with the preview operation")
|
||||
|
||||
// Flags for engine.UpdateOptions.
|
||||
cmd.PersistentFlags().StringSliceVar(
|
||||
&analyzers, "analyzer", []string{},
|
||||
|
@ -52,6 +99,8 @@ func newPreviewCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&diffDisplay, "diff", false,
|
||||
"Display operation as a rich diff showing the overall change")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().IntVarP(
|
||||
¶llel, "parallel", "p", 0,
|
||||
"Allow P resource operations to run in parallel at once (<=1 for no parallelism)")
|
||||
|
|
|
@ -23,11 +23,12 @@ func newRefreshCmd() *cobra.Command {
|
|||
var color colorFlag
|
||||
var diffDisplay bool
|
||||
var parallel int
|
||||
var preview string
|
||||
var showConfig bool
|
||||
var showReplacementSteps bool
|
||||
var showSames bool
|
||||
var nonInteractive bool
|
||||
var skipPreview bool
|
||||
var yes bool
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "refresh",
|
||||
|
@ -43,8 +44,12 @@ func newRefreshCmd() *cobra.Command {
|
|||
"`--cwd` flag to use a different directory.",
|
||||
Args: cmdutil.NoArgs,
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
interactive := isInteractive(cmd)
|
||||
behavior, err := previewFlagsToBehavior(interactive, preview)
|
||||
interactive := isInteractive(nonInteractive)
|
||||
if !interactive {
|
||||
yes = true // auto-approve changes, since we cannot prompt.
|
||||
}
|
||||
|
||||
opts, err := updateFlagsToOptions(interactive, skipPreview, yes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -64,25 +69,22 @@ func newRefreshCmd() *cobra.Command {
|
|||
return errors.Wrap(err, "gathering environment metadata")
|
||||
}
|
||||
|
||||
err = s.Refresh(
|
||||
proj, root, m,
|
||||
engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
},
|
||||
behavior,
|
||||
backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: interactive,
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
},
|
||||
cancellationScopes,
|
||||
)
|
||||
opts.Engine = engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
}
|
||||
opts.Display = backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: interactive,
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
}
|
||||
|
||||
err = s.Refresh(proj, root, m, opts, cancellationScopes)
|
||||
if err == context.Canceled {
|
||||
return errors.New("refresh cancelled")
|
||||
}
|
||||
|
@ -110,19 +112,23 @@ func newRefreshCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&diffDisplay, "diff", false,
|
||||
"Display operation as a rich diff showing the overall change")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().IntVarP(
|
||||
¶llel, "parallel", "p", 0,
|
||||
"Allow P resource operations to run in parallel at once (<=1 for no parallelism)")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&preview, "preview", "",
|
||||
"Preview behavior. Choices are: only (dry-run), skip (no preview, just update), auto (auto-accept)")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&showReplacementSteps, "show-replacement-steps", false,
|
||||
"Show detailed resource replacement creates and deletes instead of a single step")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&showSames, "show-sames", false,
|
||||
"Show resources that needn't be updated because they haven't changed, alongside those that do")
|
||||
cmd.PersistentFlags().BoolVar(&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&skipPreview, "skip-preview", false,
|
||||
"Do not perform a preview before performing the refresh")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&yes, "yes", false,
|
||||
"Automatically approve and perform the refresh after previewing it")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -22,12 +22,13 @@ func newUpdateCmd() *cobra.Command {
|
|||
var analyzers []string
|
||||
var color colorFlag
|
||||
var diffDisplay bool
|
||||
var nonInteractive bool
|
||||
var parallel int
|
||||
var preview string
|
||||
var showConfig bool
|
||||
var showReplacementSteps bool
|
||||
var showSames bool
|
||||
var nonInteractive bool
|
||||
var skipPreview bool
|
||||
var yes bool
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "update",
|
||||
|
@ -47,8 +48,12 @@ func newUpdateCmd() *cobra.Command {
|
|||
"`--cwd` flag to use a different directory.",
|
||||
Args: cmdutil.NoArgs,
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
interactive := isInteractive(cmd)
|
||||
behavior, err := previewFlagsToBehavior(interactive, preview)
|
||||
interactive := isInteractive(nonInteractive)
|
||||
if !interactive {
|
||||
yes = true // auto-approve changes, since we cannot prompt.
|
||||
}
|
||||
|
||||
opts, err := updateFlagsToOptions(interactive, skipPreview, yes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -68,25 +73,22 @@ func newUpdateCmd() *cobra.Command {
|
|||
return errors.Wrap(err, "gathering environment metadata")
|
||||
}
|
||||
|
||||
err = s.Update(
|
||||
proj, root, m,
|
||||
engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
},
|
||||
behavior,
|
||||
backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: interactive,
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
},
|
||||
cancellationScopes,
|
||||
)
|
||||
opts.Engine = engine.UpdateOptions{
|
||||
Analyzers: analyzers,
|
||||
Parallel: parallel,
|
||||
Debug: debug,
|
||||
}
|
||||
opts.Display = backend.DisplayOptions{
|
||||
Color: color.Colorization(),
|
||||
ShowConfig: showConfig,
|
||||
ShowReplacementSteps: showReplacementSteps,
|
||||
ShowSameResources: showSames,
|
||||
IsInteractive: interactive,
|
||||
DiffDisplay: diffDisplay,
|
||||
Debug: debug,
|
||||
}
|
||||
|
||||
err = s.Update(proj, root, m, opts, cancellationScopes)
|
||||
if err == context.Canceled {
|
||||
return errors.New("update cancelled")
|
||||
}
|
||||
|
@ -114,9 +116,8 @@ func newUpdateCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&diffDisplay, "diff", false,
|
||||
"Display operation as a rich diff showing the overall change")
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&preview, "preview", "",
|
||||
"Preview behavior. Choices are: only (dry-run), skip (no preview, just update), auto (auto-accept)")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().IntVarP(
|
||||
¶llel, "parallel", "p", 0,
|
||||
"Allow P resource operations to run in parallel at once (<=1 for no parallelism)")
|
||||
|
@ -129,7 +130,12 @@ func newUpdateCmd() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolVar(
|
||||
&showSames, "show-sames", false,
|
||||
"Show resources that don't need be updated because they haven't changed, alongside those that do")
|
||||
cmd.PersistentFlags().BoolVar(&nonInteractive, "non-interactive", false, "Disable interactive mode")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&skipPreview, "skip-preview", false,
|
||||
"Do not perform a preview before performing the update")
|
||||
cmd.PersistentFlags().BoolVar(
|
||||
&yes, "yes", false,
|
||||
"Automatically approve and perform the update after previewing it")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
28
cmd/util.go
28
cmd/util.go
|
@ -17,7 +17,6 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
survey "gopkg.in/AlecAivazis/survey.v1"
|
||||
surveycore "gopkg.in/AlecAivazis/survey.v1/core"
|
||||
|
@ -502,25 +501,20 @@ func (cancellationScopeSource) NewScope(events chan<- engine.Event, isPreview bo
|
|||
|
||||
// isInteractive returns true if the environment and command line options indicate we should
|
||||
// do things interactively
|
||||
func isInteractive(cmd *cobra.Command) bool {
|
||||
nonInteractive, err := cmd.Flags().GetBool("non-interactive")
|
||||
contract.IgnoreError(err)
|
||||
func isInteractive(nonInteractive bool) bool {
|
||||
return !nonInteractive && terminal.IsTerminal(int(os.Stdout.Fd())) && !isCI()
|
||||
}
|
||||
|
||||
// previewFlagsToBehavior turns the CLI preview flag into a backend behavior enum.
|
||||
func previewFlagsToBehavior(interactive bool, preview string) (backend.PreviewBehavior, error) {
|
||||
behavior := backend.PreviewBehavior(preview)
|
||||
switch behavior {
|
||||
case backend.DefaultPreview, backend.OnlyPreview, backend.SkipPreview, backend.AutoPreview:
|
||||
// ok
|
||||
default:
|
||||
return "", errors.Errorf("unrecognized preview behavior: '%s'", preview)
|
||||
// updateFlagsToOptions ensures that the given update flags represent a valid combination. If so, an UpdateOptions
|
||||
// is returned with a nil-error; otherwise, the non-nil error contains information about why the combination is invalid.
|
||||
func updateFlagsToOptions(interactive, skipPreview, yes bool) (backend.UpdateOptions, error) {
|
||||
if !interactive && !yes {
|
||||
return backend.UpdateOptions{},
|
||||
errors.New("--yes must be passed in non-interactive mode")
|
||||
}
|
||||
|
||||
if !interactive && behavior.Interactive() {
|
||||
return "", errors.New("--preview must be only, skip, or auto when run in non-interactive mode")
|
||||
}
|
||||
|
||||
return behavior, nil
|
||||
return backend.UpdateOptions{
|
||||
AutoApprove: yes,
|
||||
SkipPreview: skipPreview,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -47,9 +47,6 @@ func TestExamples(t *testing.T) {
|
|||
"simple:config:x": "1",
|
||||
"simple:config:y": "1",
|
||||
},
|
||||
Verbose: true,
|
||||
DebugUpdates: true,
|
||||
DebugLogLevel: 12,
|
||||
},
|
||||
{
|
||||
Dir: path.Join(cwd, "dynamic-provider/multiple-turns"),
|
||||
|
@ -97,7 +94,7 @@ func TestExamples(t *testing.T) {
|
|||
for _, example := range examples {
|
||||
ex := example.With(integration.ProgramTestOptions{
|
||||
ReportStats: integration.NewS3Reporter("us-west-2", "eng.pulumi.com", "testreports"),
|
||||
Tracing: "https://tracing.pulumi-engineering.com/collector/api/v1/spans",
|
||||
Tracing: "https://tracing.pulumi-engineering.com/collector/api/v1/spans",
|
||||
})
|
||||
t.Run(example.Dir, func(t *testing.T) {
|
||||
integration.ProgramTest(t, &ex)
|
||||
|
|
|
@ -52,18 +52,18 @@ type Backend interface {
|
|||
// GetStackCrypter returns an encrypter/decrypter for the given stack's secret config values.
|
||||
GetStackCrypter(stackRef StackReference) (config.Crypter, error)
|
||||
|
||||
// Preview shows what would be updated given the current workspace's contents.
|
||||
Preview(stackRef StackReference, proj *workspace.Project, root string,
|
||||
m UpdateMetadata, opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
// Update updates the target stack with the current workspace's contents (config and code).
|
||||
Update(stackRef StackReference, proj *workspace.Project, root string,
|
||||
m UpdateMetadata, opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error
|
||||
m UpdateMetadata, opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
// Refresh refreshes the stack's state from the cloud provider.
|
||||
Refresh(stackRef StackReference, proj *workspace.Project, root string,
|
||||
m UpdateMetadata, opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error
|
||||
m UpdateMetadata, opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
// Destroy destroys all of this stack's resources.
|
||||
Destroy(stackRef StackReference, proj *workspace.Project, root string,
|
||||
m UpdateMetadata, opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error
|
||||
m UpdateMetadata, opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
|
||||
// GetHistory returns all updates for the stack. The returned UpdateInfo slice will be in
|
||||
// descending order (newest first).
|
||||
|
@ -79,44 +79,17 @@ type Backend interface {
|
|||
Logout() error
|
||||
}
|
||||
|
||||
// PreviewBehavior controls how previews are performed during an update.
|
||||
type PreviewBehavior string
|
||||
// UpdateOptions is the full set of update options, including backend and engine options.
|
||||
type UpdateOptions struct {
|
||||
// Engine contains all of the engine-specific options.
|
||||
Engine engine.UpdateOptions
|
||||
// Display contains all of the backend display options.
|
||||
Display DisplayOptions
|
||||
|
||||
const (
|
||||
// DefaultPreview is the default behavior of a preview followed by an confirmation-prompt-guarded update.
|
||||
DefaultPreview PreviewBehavior = ""
|
||||
// OnlyPreview only runs a preview but does not perform an update.
|
||||
OnlyPreview PreviewBehavior = "only"
|
||||
// SkipPreview skips a preview, and prompt, altogether, and simply performs the update.
|
||||
SkipPreview PreviewBehavior = "skip"
|
||||
// AutoPreview automatically confirms a preview after performing it.
|
||||
AutoPreview PreviewBehavior = "auto"
|
||||
)
|
||||
|
||||
// Only returns true if the update only entails a preview operation.
|
||||
func (preview PreviewBehavior) Only() bool {
|
||||
return preview == OnlyPreview
|
||||
}
|
||||
|
||||
// Skip returns true if the update should skip the preview altogether.
|
||||
func (preview PreviewBehavior) Skip() bool {
|
||||
return preview == SkipPreview
|
||||
}
|
||||
|
||||
// Should returns true if the update options mandate that a preview occur.
|
||||
func (preview PreviewBehavior) Should() bool {
|
||||
return !preview.Skip()
|
||||
}
|
||||
|
||||
// Interactive returns true if a preview behavior will require confirmation prompts.
|
||||
func (preview PreviewBehavior) Interactive() bool {
|
||||
return preview == DefaultPreview
|
||||
}
|
||||
|
||||
// AutoAccept returns true if a preview's result should be automatically accepted. This can be useful in
|
||||
// non-interactive situations where you'd like to still preview before performing an apply.
|
||||
func (preview PreviewBehavior) AutoAccept() bool {
|
||||
return preview == AutoPreview
|
||||
// AutoApprove, when true, will automatically approve previews.
|
||||
AutoApprove bool
|
||||
// SkipPreview, when true, causes the preview step to be skipped.
|
||||
SkipPreview bool
|
||||
}
|
||||
|
||||
// CancellationScope provides a scoped source of cancellation and termination requests.
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"github.com/golang/glog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
survey "gopkg.in/AlecAivazis/survey.v1"
|
||||
surveycore "gopkg.in/AlecAivazis/survey.v1/core"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/backend"
|
||||
|
@ -40,9 +42,6 @@ import (
|
|||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
"github.com/pulumi/pulumi/pkg/util/retry"
|
||||
"github.com/pulumi/pulumi/pkg/workspace"
|
||||
|
||||
survey "gopkg.in/AlecAivazis/survey.v1"
|
||||
surveycore "gopkg.in/AlecAivazis/survey.v1/core"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -482,6 +481,7 @@ var (
|
|||
previewText string
|
||||
text string
|
||||
}{
|
||||
string(client.UpdateKindPreview): {"update of", "Previewing"},
|
||||
string(client.UpdateKindUpdate): {"update of", "Updating"},
|
||||
string(client.UpdateKindRefresh): {"refresh of", "Refreshing"},
|
||||
string(client.UpdateKindDestroy): {"destroy of", "Destroying"},
|
||||
|
@ -543,57 +543,51 @@ func createDiff(events []engine.Event, displayOpts backend.DisplayOptions) strin
|
|||
|
||||
func (b *cloudBackend) PreviewThenPrompt(
|
||||
updateKind client.UpdateKind, stack backend.Stack, pkg *workspace.Project, root string,
|
||||
m backend.UpdateMetadata, opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
m backend.UpdateMetadata, opts backend.UpdateOptions, scopes backend.CancellationScopeSource) (bool, error) {
|
||||
|
||||
// create a channel to hear about the update events from the engine. this will be used so that
|
||||
// we can build up the diff display in case the user asks to see the details of the diff
|
||||
var eventsChannel chan engine.Event
|
||||
eventsChannel := make(chan engine.Event)
|
||||
defer func() {
|
||||
close(eventsChannel)
|
||||
}()
|
||||
|
||||
events := []engine.Event{}
|
||||
go func() {
|
||||
// pull the events from the channel and store them locally
|
||||
for e := range eventsChannel {
|
||||
if e.Type == engine.ResourcePreEvent ||
|
||||
e.Type == engine.ResourceOutputsEvent ||
|
||||
e.Type == engine.SummaryEvent {
|
||||
|
||||
// if we're previewing, we don't need to store the events as we're not going to prompt
|
||||
// the user to get details of what's happening.
|
||||
if !preview.Only() {
|
||||
eventsChannel = make(chan engine.Event)
|
||||
defer func() {
|
||||
close(eventsChannel)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// pull the events from the channel and store them locally
|
||||
for e := range eventsChannel {
|
||||
if e.Type == engine.ResourcePreEvent ||
|
||||
e.Type == engine.ResourceOutputsEvent ||
|
||||
e.Type == engine.SummaryEvent {
|
||||
|
||||
events = append(events, e)
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Perform the update operations, passing true for dryRun, so that we get a preview.
|
||||
err := b.updateStack(
|
||||
updateKind, stack, pkg, root, m,
|
||||
opts, displayOpts, eventsChannel, true /*dryRun*/, scopes)
|
||||
if err != nil || preview.Only() {
|
||||
// if we're just previewing, then we can stop at this point.
|
||||
return err
|
||||
hasChanges := true
|
||||
if !opts.SkipPreview {
|
||||
changes, err := b.updateStack(
|
||||
updateKind, stack, pkg, root, m, opts, eventsChannel, true /*dryRun*/, scopes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
hasChanges = changes.HasChanges()
|
||||
}
|
||||
|
||||
// Ensure the user wants to proceed.
|
||||
return confirmBeforeUpdating(stack, preview, events, displayOpts)
|
||||
// If there are no changes, or we're auto-approving or just previewing, we can skip the confirmation prompt.
|
||||
if !hasChanges || opts.AutoApprove || updateKind == client.UpdateKindPreview {
|
||||
return hasChanges, nil
|
||||
}
|
||||
|
||||
// Otherwise, ensure the user wants to proceed.
|
||||
return hasChanges, confirmBeforeUpdating(updateKind, stack, events, opts)
|
||||
}
|
||||
|
||||
// confirmBeforeUpdating asks the user whether to proceed. A nil error means yes.
|
||||
func confirmBeforeUpdating(stack backend.Stack, preview backend.PreviewBehavior,
|
||||
events []engine.Event, displayOpts backend.DisplayOptions) error {
|
||||
// If auto-accept is true, we can exit right away.
|
||||
if preview.AutoAccept() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, we will ask the user for confirmation before proceeding.
|
||||
func confirmBeforeUpdating(updateKind client.UpdateKind, stack backend.Stack,
|
||||
events []engine.Event, opts backend.UpdateOptions) error {
|
||||
for {
|
||||
var response string
|
||||
|
||||
|
@ -601,25 +595,32 @@ func confirmBeforeUpdating(stack backend.Stack, preview backend.PreviewBehavior,
|
|||
surveycore.QuestionIcon = ""
|
||||
surveycore.SelectFocusIcon = colors.ColorizeText(colors.BrightGreen + ">" + colors.Reset)
|
||||
|
||||
options := []string{string(yes), string(no)}
|
||||
choices := []string{string(yes), string(no)}
|
||||
|
||||
// if this is a managed stack, then we can get the details for the operation, as we will
|
||||
// have been able to collect the details while the preview ran. For ppc stacks, we don't
|
||||
// have that information since all the PPC does is forward stdout events to us.
|
||||
if stack.(Stack).RunLocally() {
|
||||
options = append(options, string(details))
|
||||
if stack.(Stack).RunLocally() && !opts.SkipPreview {
|
||||
choices = append(choices, string(details))
|
||||
}
|
||||
|
||||
var previewWarning string
|
||||
if opts.SkipPreview {
|
||||
previewWarning = colors.SpecWarning + " without a preview" + colors.BrightWhite
|
||||
}
|
||||
|
||||
if err := survey.AskOne(&survey.Select{
|
||||
Message: colors.ColorizeText(colors.BrightWhite + "Do you want to proceed?" + colors.Reset),
|
||||
Options: options,
|
||||
Message: "\b" + colors.ColorizeText(
|
||||
colors.BrightWhite+fmt.Sprintf("Do you want to perform this %s%s?",
|
||||
updateKind, previewWarning)+colors.Reset),
|
||||
Options: choices,
|
||||
Default: string(no),
|
||||
}, &response, nil); err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "confirmation cancelled, not proceeding with the %s", updateKind)
|
||||
}
|
||||
|
||||
if response == string(no) {
|
||||
return errors.New("confirmation declined")
|
||||
return errors.Errorf("confirmation declined, not proceeding with the %s", updateKind)
|
||||
}
|
||||
|
||||
if response == string(yes) {
|
||||
|
@ -627,7 +628,7 @@ func confirmBeforeUpdating(stack backend.Stack, preview backend.PreviewBehavior,
|
|||
}
|
||||
|
||||
if response == string(details) {
|
||||
diff := createDiff(events, displayOpts)
|
||||
diff := createDiff(events, opts.Display)
|
||||
_, err := os.Stdout.WriteString(diff + "\n\n")
|
||||
contract.IgnoreError(err)
|
||||
continue
|
||||
|
@ -637,8 +638,7 @@ func confirmBeforeUpdating(stack backend.Stack, preview backend.PreviewBehavior,
|
|||
|
||||
func (b *cloudBackend) PreviewThenPromptThenExecute(
|
||||
updateKind client.UpdateKind, stackRef backend.StackReference, pkg *workspace.Project, root string,
|
||||
m backend.UpdateMetadata, opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
m backend.UpdateMetadata, opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
// First get the stack.
|
||||
stack, err := getStack(b, stackRef)
|
||||
if err != nil {
|
||||
|
@ -646,54 +646,45 @@ func (b *cloudBackend) PreviewThenPromptThenExecute(
|
|||
}
|
||||
|
||||
if !stack.(Stack).RunLocally() && updateKind == client.UpdateKindDestroy {
|
||||
// The service does not support preview of a destroy for a stack managed by a PPC. So behave as if (--force)
|
||||
// had been passed (so we don't run the preview step)
|
||||
opts.Force = true
|
||||
// The service does not support preview of a destroy for a stack managed by a PPC. So skip the preview.
|
||||
opts.SkipPreview = true
|
||||
}
|
||||
|
||||
if preview.Should() {
|
||||
// If we're not skipping the preview, then preview the operation to the user and ask them if
|
||||
// they want to proceed.
|
||||
err = b.PreviewThenPrompt(updateKind, stack, pkg, root, m, opts, preview, displayOpts, scopes)
|
||||
if err != nil || preview.Only() {
|
||||
return err
|
||||
}
|
||||
// Preview the operation to the user and ask them if they want to proceed.
|
||||
hasChanges, err := b.PreviewThenPrompt(updateKind, stack, pkg, root, m, opts, scopes)
|
||||
if err != nil || !hasChanges || updateKind == client.UpdateKindPreview {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now do the real operation. We don't care about the events it issues, so just
|
||||
// pass a nil channel along.
|
||||
var unused chan engine.Event
|
||||
// Now do the real operation. We don't care about the events it issues, so just pass a nil channel along.
|
||||
_, err = b.updateStack(updateKind, stack, pkg, root, m, opts, nil, false /*dryRun*/, scopes)
|
||||
return err
|
||||
}
|
||||
|
||||
return b.updateStack(
|
||||
updateKind, stack, pkg,
|
||||
root, m, opts, displayOpts, unused, false /*dryRun*/, scopes)
|
||||
func (b *cloudBackend) Preview(stackRef backend.StackReference, pkg *workspace.Project, root string,
|
||||
m backend.UpdateMetadata, opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(client.UpdateKindPreview, stackRef, pkg, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (b *cloudBackend) Update(stackRef backend.StackReference, pkg *workspace.Project, root string,
|
||||
m backend.UpdateMetadata, opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(
|
||||
client.UpdateKindUpdate, stackRef, pkg, root, m, opts, preview, displayOpts, scopes)
|
||||
m backend.UpdateMetadata, opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(client.UpdateKindUpdate, stackRef, pkg, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (b *cloudBackend) Refresh(stackRef backend.StackReference, pkg *workspace.Project, root string,
|
||||
m backend.UpdateMetadata, opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(
|
||||
client.UpdateKindRefresh, stackRef, pkg, root, m, opts, preview, displayOpts, scopes)
|
||||
m backend.UpdateMetadata, opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(client.UpdateKindRefresh, stackRef, pkg, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (b *cloudBackend) Destroy(stackRef backend.StackReference, pkg *workspace.Project, root string,
|
||||
m backend.UpdateMetadata, opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(
|
||||
client.UpdateKindDestroy, stackRef, pkg, root, m, opts, preview, displayOpts, scopes)
|
||||
m backend.UpdateMetadata, opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.PreviewThenPromptThenExecute(client.UpdateKindDestroy, stackRef, pkg, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (b *cloudBackend) createAndStartUpdate(
|
||||
action client.UpdateKind, stackRef backend.StackReference,
|
||||
pkg *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, dryRun bool) (client.UpdateIdentifier, int, string, error) {
|
||||
opts backend.UpdateOptions, dryRun bool) (client.UpdateIdentifier, int, string, error) {
|
||||
|
||||
stack, err := b.getCloudStackIdentifier(stackRef)
|
||||
if err != nil {
|
||||
|
@ -716,7 +707,7 @@ func (b *cloudBackend) createAndStartUpdate(
|
|||
return getUpdateContents(context, pkg.UseDefaultIgnores(), showProgress)
|
||||
}
|
||||
update, err := b.client.CreateUpdate(
|
||||
action, stack, pkg, workspaceStack.Config, main, metadata, opts, dryRun, getContents)
|
||||
action, stack, pkg, workspaceStack.Config, main, metadata, opts.Engine, dryRun, getContents)
|
||||
if err != nil {
|
||||
return client.UpdateIdentifier{}, 0, "", err
|
||||
}
|
||||
|
@ -741,9 +732,9 @@ func (b *cloudBackend) createAndStartUpdate(
|
|||
// updateStack performs a the provided type of update on a stack hosted in the Pulumi Cloud.
|
||||
func (b *cloudBackend) updateStack(
|
||||
action client.UpdateKind, stack backend.Stack, pkg *workspace.Project,
|
||||
root string, m backend.UpdateMetadata, opts engine.UpdateOptions,
|
||||
displayOpts backend.DisplayOptions, callerEventsOpt chan<- engine.Event, dryRun bool,
|
||||
scopes backend.CancellationScopeSource) error {
|
||||
root string, m backend.UpdateMetadata, opts backend.UpdateOptions,
|
||||
callerEventsOpt chan<- engine.Event, dryRun bool,
|
||||
scopes backend.CancellationScopeSource) (engine.ResourceChanges, error) {
|
||||
|
||||
// Print a banner so it's clear this is going to the cloud.
|
||||
actionLabel := getActionLabel(string(action), dryRun)
|
||||
|
@ -757,11 +748,10 @@ func (b *cloudBackend) updateStack(
|
|||
var token string
|
||||
var err error
|
||||
if !stack.(Stack).RunLocally() || !dryRun {
|
||||
update, version, token, err = b.createAndStartUpdate(
|
||||
action, stack.Name(), pkg, root, m, opts, dryRun)
|
||||
update, version, token, err = b.createAndStartUpdate(action, stack.Name(), pkg, root, m, opts, dryRun)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if version != 0 {
|
||||
|
@ -779,19 +769,18 @@ func (b *cloudBackend) updateStack(
|
|||
// If we are targeting a stack that uses local operations, run the appropriate engine action locally.
|
||||
if stack.(Stack).RunLocally() {
|
||||
return b.runEngineAction(
|
||||
action, stack.Name(), pkg, root, opts, displayOpts,
|
||||
update, token, callerEventsOpt, dryRun, scopes)
|
||||
action, stack.Name(), pkg, root, opts, update, token, callerEventsOpt, dryRun, scopes)
|
||||
}
|
||||
|
||||
// Otherwise, wait for the update to complete while rendering its events to stdout/stderr.
|
||||
status, err := b.waitForUpdate(actionLabel, update, displayOpts)
|
||||
status, err := b.waitForUpdate(actionLabel, update, opts.Display)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "waiting for %s", action)
|
||||
return nil, errors.Wrapf(err, "waiting for %s", action)
|
||||
} else if status != apitype.StatusSucceeded {
|
||||
return errors.Errorf("%s unsuccessful: status %v", action, status)
|
||||
return nil, errors.Errorf("%s unsuccessful: status %v", action, status)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// uploadArchive archives the current Pulumi program and uploads it to a signed URL. "current"
|
||||
|
@ -821,13 +810,13 @@ func getUpdateContents(context string, useDefaultIgnores bool, progress bool) (i
|
|||
|
||||
func (b *cloudBackend) runEngineAction(
|
||||
action client.UpdateKind, stackRef backend.StackReference, pkg *workspace.Project,
|
||||
root string, opts engine.UpdateOptions, displayOpts backend.DisplayOptions,
|
||||
update client.UpdateIdentifier, token string,
|
||||
callerEventsOpt chan<- engine.Event, dryRun bool, scopes backend.CancellationScopeSource) error {
|
||||
root string, opts backend.UpdateOptions, update client.UpdateIdentifier, token string,
|
||||
callerEventsOpt chan<- engine.Event, dryRun bool,
|
||||
scopes backend.CancellationScopeSource) (engine.ResourceChanges, error) {
|
||||
|
||||
u, err := b.newUpdate(stackRef, pkg, root, update, token)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
persister := b.newSnapshotPersister(u.update, u.tokenSource)
|
||||
|
@ -835,8 +824,7 @@ func (b *cloudBackend) runEngineAction(
|
|||
displayEvents := make(chan engine.Event)
|
||||
displayDone := make(chan bool)
|
||||
|
||||
go u.RecordAndDisplayEvents(
|
||||
getActionLabel(string(action), dryRun), displayEvents, displayDone, displayOpts)
|
||||
go u.RecordAndDisplayEvents(getActionLabel(string(action), dryRun), displayEvents, displayDone, opts.Display)
|
||||
|
||||
engineEvents := make(chan engine.Event)
|
||||
|
||||
|
@ -856,18 +844,21 @@ func (b *cloudBackend) runEngineAction(
|
|||
|
||||
// Depending on the action, kick off the relevant engine activity. Note that we don't immediately check and
|
||||
// return error conditions, because we will do so below after waiting for the display channels to close.
|
||||
var changes engine.ResourceChanges
|
||||
engineCtx := &engine.Context{Cancel: scope.Context(), Events: engineEvents, SnapshotManager: manager}
|
||||
switch action {
|
||||
case client.UpdateKindPreview:
|
||||
changes, err = engine.Preview(u, engineCtx, opts.Engine)
|
||||
case client.UpdateKindUpdate:
|
||||
if dryRun {
|
||||
err = engine.Preview(u, engineCtx, opts)
|
||||
changes, err = engine.Preview(u, engineCtx, opts.Engine)
|
||||
} else {
|
||||
_, err = engine.Update(u, engineCtx, opts, dryRun)
|
||||
changes, err = engine.Update(u, engineCtx, opts.Engine, dryRun)
|
||||
}
|
||||
case client.UpdateKindRefresh:
|
||||
_, err = engine.Refresh(u, engineCtx, opts, dryRun)
|
||||
changes, err = engine.Refresh(u, engineCtx, opts.Engine, dryRun)
|
||||
case client.UpdateKindDestroy:
|
||||
_, err = engine.Destroy(u, engineCtx, opts, dryRun)
|
||||
changes, err = engine.Destroy(u, engineCtx, opts.Engine, dryRun)
|
||||
default:
|
||||
contract.Failf("Unrecognized action type: %s", action)
|
||||
}
|
||||
|
@ -891,7 +882,7 @@ func (b *cloudBackend) runEngineAction(
|
|||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return changes, err
|
||||
}
|
||||
|
||||
func (b *cloudBackend) CancelCurrentUpdate(stackRef backend.StackReference) error {
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/backend"
|
||||
"github.com/pulumi/pulumi/pkg/engine"
|
||||
"github.com/pulumi/pulumi/pkg/operations"
|
||||
"github.com/pulumi/pulumi/pkg/resource"
|
||||
"github.com/pulumi/pulumi/pkg/resource/config"
|
||||
|
@ -117,22 +116,24 @@ func (s *cloudStack) Remove(force bool) (bool, error) {
|
|||
return backend.RemoveStack(s, force)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Preview(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.PreviewStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Update(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.UpdateStack(s, proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.UpdateStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Refresh(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.RefreshStack(s, proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.RefreshStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *cloudStack) Destroy(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.DestroyStack(s, proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.DestroyStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *cloudStack) GetLogs(query operations.LogQuery) ([]operations.LogEntry, error) {
|
||||
|
|
|
@ -170,10 +170,22 @@ func (b *localBackend) GetStackCrypter(stackRef backend.StackReference) (config.
|
|||
return symmetricCrypter(stackRef.StackName())
|
||||
}
|
||||
|
||||
func (b *localBackend) Preview(
|
||||
stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.performEngineOp("previewing", backend.PreviewUpdate,
|
||||
stackRef.StackName(), proj, root, m, opts, scopes,
|
||||
func(u engine.UpdateInfo, ctx *engine.Context,
|
||||
opts engine.UpdateOptions, dryRun bool) (engine.ResourceChanges, error) {
|
||||
contract.Assert(dryRun)
|
||||
return engine.Preview(u, ctx, opts)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (b *localBackend) Update(
|
||||
stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
// The Pulumi Service will pick up changes to a stack's tags on each update. (e.g. changing the description
|
||||
// in Pulumi.yaml.) While this isn't necessary for local updates, we do the validation here to keep
|
||||
// parity with stacks managed by the Pulumi Service.
|
||||
|
@ -186,49 +198,41 @@ func (b *localBackend) Update(
|
|||
return errors.Wrap(err, "validating stack properties")
|
||||
}
|
||||
return b.performEngineOp("updating", backend.DeployUpdate,
|
||||
stackName, proj, root, m, opts, preview, displayOpts, engine.Update, scopes)
|
||||
stackName, proj, root, m, opts, scopes, engine.Update)
|
||||
}
|
||||
|
||||
func (b *localBackend) Refresh(
|
||||
stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.performEngineOp("refreshing", backend.RefreshUpdate,
|
||||
stackRef.StackName(), proj, root, m, opts, preview, displayOpts, engine.Refresh, scopes)
|
||||
stackRef.StackName(), proj, root, m, opts, scopes, engine.Refresh)
|
||||
}
|
||||
|
||||
func (b *localBackend) Destroy(
|
||||
stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return b.performEngineOp("destroying", backend.DestroyUpdate,
|
||||
stackRef.StackName(), proj, root, m, opts, preview, displayOpts, engine.Destroy, scopes)
|
||||
stackRef.StackName(), proj, root, m, opts, scopes, engine.Destroy)
|
||||
}
|
||||
|
||||
type engineOpFunc func(engine.UpdateInfo, *engine.Context, engine.UpdateOptions, bool) (engine.ResourceChanges, error)
|
||||
|
||||
func (b *localBackend) performEngineOp(op string, kind backend.UpdateKind,
|
||||
stackName tokens.QName, proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior, displayOpts backend.DisplayOptions,
|
||||
performEngineOp engineOpFunc, scopes backend.CancellationScopeSource) error {
|
||||
// At the moment, we don't support preview-confirm-update flows for local stacks.
|
||||
if preview == backend.DefaultPreview {
|
||||
return errors.Errorf("--preview or --skip-preview must be passed when %s a local stack", op)
|
||||
}
|
||||
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource, performEngineOp engineOpFunc) error {
|
||||
update, err := b.newUpdate(stackName, proj, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
events := make(chan engine.Event)
|
||||
dryRun := preview.Only()
|
||||
dryRun := (kind == backend.PreviewUpdate)
|
||||
|
||||
cancelScope := scopes.NewScope(events, dryRun)
|
||||
defer cancelScope.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go DisplayEvents(op, events, done, displayOpts)
|
||||
go DisplayEvents(op, events, done, opts.Display)
|
||||
|
||||
// Create the management machinery.
|
||||
persister := b.newSnapshotPersister(stackName)
|
||||
|
@ -237,7 +241,7 @@ func (b *localBackend) performEngineOp(op string, kind backend.UpdateKind,
|
|||
|
||||
// Perform the update
|
||||
start := time.Now().Unix()
|
||||
changes, updateErr := performEngineOp(update, engineCtx, opts, dryRun)
|
||||
changes, updateErr := performEngineOp(update, engineCtx, opts.Engine, dryRun)
|
||||
end := time.Now().Unix()
|
||||
|
||||
<-done
|
||||
|
|
|
@ -5,7 +5,6 @@ package local
|
|||
import (
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/backend"
|
||||
"github.com/pulumi/pulumi/pkg/engine"
|
||||
"github.com/pulumi/pulumi/pkg/operations"
|
||||
"github.com/pulumi/pulumi/pkg/resource/config"
|
||||
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
||||
|
@ -48,22 +47,24 @@ func (s *localStack) Remove(force bool) (bool, error) {
|
|||
return backend.RemoveStack(s, force)
|
||||
}
|
||||
|
||||
func (s *localStack) Preview(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.PreviewStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *localStack) Update(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.UpdateStack(s, proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.UpdateStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *localStack) Refresh(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.RefreshStack(s, proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.RefreshStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *localStack) Destroy(proj *workspace.Project, root string, m backend.UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview backend.PreviewBehavior,
|
||||
displayOpts backend.DisplayOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.DestroyStack(s, proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) error {
|
||||
return backend.DestroyStack(s, proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
func (s *localStack) GetLogs(query operations.LogQuery) ([]operations.LogEntry, error) {
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/apitype"
|
||||
"github.com/pulumi/pulumi/pkg/engine"
|
||||
"github.com/pulumi/pulumi/pkg/operations"
|
||||
"github.com/pulumi/pulumi/pkg/resource/config"
|
||||
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
||||
|
@ -22,18 +21,18 @@ type Stack interface {
|
|||
Snapshot() *deploy.Snapshot // the latest deployment snapshot.
|
||||
Backend() Backend // the backend this stack belongs to.
|
||||
|
||||
// Preview changes to this stack.
|
||||
Preview(proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
// Update this stack.
|
||||
Update(proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
// Refresh this stack's state from the cloud provider.
|
||||
Refresh(proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
// Destroy this stack's resources.
|
||||
Destroy(proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error
|
||||
|
||||
Remove(force bool) (bool, error) // remove this stack.
|
||||
GetLogs(query operations.LogQuery) ([]operations.LogEntry, error) // list log entries for this stack.
|
||||
|
@ -46,25 +45,28 @@ func RemoveStack(s Stack, force bool) (bool, error) {
|
|||
return s.Backend().RemoveStack(s.Name(), force)
|
||||
}
|
||||
|
||||
// PreviewStack previews changes to this stack.
|
||||
func PreviewStack(s Stack, proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Preview(s.Name(), proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
// UpdateStack updates the target stack with the current workspace's contents (config and code).
|
||||
func UpdateStack(s Stack, proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Update(s.Name(), proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Update(s.Name(), proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
// RefreshStack refresh's the stack's state from the cloud provider.
|
||||
func RefreshStack(s Stack, proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Refresh(s.Name(), proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Refresh(s.Name(), proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
// DestroyStack destroys all of this stack's resources.
|
||||
func DestroyStack(s Stack, proj *workspace.Project, root string, m UpdateMetadata,
|
||||
opts engine.UpdateOptions, preview PreviewBehavior,
|
||||
displayOpts DisplayOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Destroy(s.Name(), proj, root, m, opts, preview, displayOpts, scopes)
|
||||
opts UpdateOptions, scopes CancellationScopeSource) error {
|
||||
return s.Backend().Destroy(s.Name(), proj, root, m, opts, scopes)
|
||||
}
|
||||
|
||||
// GetStackCrypter fetches the encrypter/decrypter for a stack.
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/pulumi/pulumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
func Preview(u UpdateInfo, ctx *Context, opts UpdateOptions) error {
|
||||
func Preview(u UpdateInfo, ctx *Context, opts UpdateOptions) (ResourceChanges, error) {
|
||||
contract.Require(u != nil, "u")
|
||||
contract.Require(ctx != nil, "ctx")
|
||||
|
||||
|
@ -17,7 +17,7 @@ func Preview(u UpdateInfo, ctx *Context, opts UpdateOptions) error {
|
|||
|
||||
info, err := newPlanContext(u, "preview", ctx.ParentSpan)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer info.Close()
|
||||
|
||||
|
@ -30,10 +30,10 @@ func Preview(u UpdateInfo, ctx *Context, opts UpdateOptions) error {
|
|||
})
|
||||
}
|
||||
|
||||
func preview(ctx *Context, info *planContext, opts planOptions) error {
|
||||
func preview(ctx *Context, info *planContext, opts planOptions) (ResourceChanges, error) {
|
||||
result, err := plan(info, opts, true /*dryRun*/)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if result != nil {
|
||||
defer contract.IgnoreClose(result)
|
||||
|
@ -41,16 +41,14 @@ func preview(ctx *Context, info *planContext, opts planOptions) error {
|
|||
// Make the current working directory the same as the program's, and restore it upon exit.
|
||||
done, err := result.Chdir()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer done()
|
||||
|
||||
if _, err := printPlan(ctx, result, true /*dryRun*/); err != nil {
|
||||
return err
|
||||
}
|
||||
return printPlan(ctx, result, true /*dryRun*/)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type previewActions struct {
|
||||
|
|
|
@ -28,6 +28,17 @@ type UpdateOptions struct {
|
|||
// ResourceChanges contains the aggregate resource changes by operation type.
|
||||
type ResourceChanges map[deploy.StepOp]int
|
||||
|
||||
// HasChanges returns true if there are any non-same changes in the resulting summary.
|
||||
func (changes ResourceChanges) HasChanges() bool {
|
||||
var c int
|
||||
for op, count := range changes {
|
||||
if op != deploy.OpSame {
|
||||
c += count
|
||||
}
|
||||
}
|
||||
return c > 0
|
||||
}
|
||||
|
||||
func Update(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (ResourceChanges, error) {
|
||||
contract.Require(u != nil, "update")
|
||||
contract.Require(ctx != nil, "ctx")
|
||||
|
|
|
@ -526,7 +526,7 @@ func (pt *programTester) testLifeCycleInitialize(dir string) error {
|
|||
func (pt *programTester) testLifeCycleDestroy(dir string) error {
|
||||
// Destroy and remove the stack.
|
||||
fprintf(pt.opts.Stdout, "Destroying stack\n")
|
||||
destroy := []string{"destroy", "--non-interactive", "--force"}
|
||||
destroy := []string{"destroy", "--non-interactive", "--skip-preview"}
|
||||
if pt.opts.GetDebugUpdates() {
|
||||
destroy = append(destroy, "-d")
|
||||
}
|
||||
|
@ -567,8 +567,8 @@ func (pt *programTester) testPreviewUpdateAndEdits(dir string) error {
|
|||
}
|
||||
|
||||
func (pt *programTester) previewAndUpdate(dir string, name string, shouldFail bool) error {
|
||||
preview := []string{"update", "--non-interactive", "--preview=only"}
|
||||
update := []string{"update", "--non-interactive", "--preview=skip"}
|
||||
preview := []string{"preview", "--non-interactive"}
|
||||
update := []string{"update", "--non-interactive", "--skip-preview"}
|
||||
if pt.opts.GetDebugUpdates() {
|
||||
preview = append(preview, "-d")
|
||||
update = append(update, "-d")
|
||||
|
|
|
@ -58,7 +58,7 @@ func TestProjectMain(t *testing.T) {
|
|||
e.ImportDirectory("project_main_abs")
|
||||
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
||||
e.RunCommand("pulumi", "stack", "init", "main-abs")
|
||||
stdout, stderr := e.RunCommandExpectError("pulumi", "update", "--non-interactive", "--preview=skip")
|
||||
stdout, stderr := e.RunCommandExpectError("pulumi", "update", "--non-interactive", "--skip-preview", "--yes")
|
||||
assert.Equal(t, "", stdout)
|
||||
assert.Contains(t, stderr, "project 'main' must be a relative path")
|
||||
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
||||
|
@ -74,7 +74,7 @@ func TestProjectMain(t *testing.T) {
|
|||
e.ImportDirectory("project_main_parent")
|
||||
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
||||
e.RunCommand("pulumi", "stack", "init", "main-parent")
|
||||
stdout, stderr := e.RunCommandExpectError("pulumi", "update", "--non-interactive", "--preview=skip")
|
||||
stdout, stderr := e.RunCommandExpectError("pulumi", "update", "--non-interactive", "--skip-preview", "--yes")
|
||||
assert.Equal(t, "", stdout)
|
||||
assert.Contains(t, stderr, "project 'main' must be a subfolder")
|
||||
e.RunCommand("pulumi", "stack", "rm", "--yes")
|
||||
|
|
|
@ -169,7 +169,7 @@ func TestStackBackups(t *testing.T) {
|
|||
|
||||
// Now run pulumi update.
|
||||
before := time.Now().UnixNano()
|
||||
e.RunCommand("pulumi", "update", "--non-interactive", "--preview=skip")
|
||||
e.RunCommand("pulumi", "update", "--non-interactive", "--skip-preview", "--yes")
|
||||
after := time.Now().UnixNano()
|
||||
|
||||
// Verify the backup directory contains a single backup.
|
||||
|
@ -183,7 +183,7 @@ func TestStackBackups(t *testing.T) {
|
|||
|
||||
// Now run pulumi destroy.
|
||||
before = time.Now().UnixNano()
|
||||
e.RunCommand("pulumi", "destroy", "--non-interactive", "--preview=skip")
|
||||
e.RunCommand("pulumi", "destroy", "--non-interactive", "--skip-preview", "--yes")
|
||||
after = time.Now().UnixNano()
|
||||
|
||||
// Verify the backup directory has been updated with 1 additional backups.
|
||||
|
|
Loading…
Reference in a new issue