Don't print error prefix when a confirmation prompt is declined

Use `result.Result` in more places, so when a confirmation prompt is
declined, we just return `result.Bail()` after printing a message
without the `error: ` prefix.

Fixes #2070
This commit is contained in:
Matt Ellis 2019-03-25 13:45:12 -07:00
parent f34aef2f4d
commit ccd958777c
7 changed files with 64 additions and 51 deletions

View file

@ -1,9 +1,10 @@
## 0.17.4 (Unreleased) ## 0.17.4 (Unreleased)
## 0.17.3 (Released March 26, 2019) ## Improvements
- Add support for serializing JavaScript function that capture [BigInts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). - Don't print the `error:` prefix when Pulumi exists because of a declined confirmation prompt (fixes [pulumi/pulumi#458](https://github.com/pulumi/pulumi/issues/2070))
- Support serializing arrow-functions with deconstructed parameters.
## 0.17.3 (Released March 26, 2019)
### Improvements ### Improvements
@ -12,7 +13,8 @@
- A bug in the previous version of the Pulumi CLI occasionally caused the Pulumi Engine to load the incorrect resource - A bug in the previous version of the Pulumi CLI occasionally caused the Pulumi Engine to load the incorrect resource
plugin when processing an update. This bug has been fixed in 0.17.3 by performing a deterministic selection of the plugin when processing an update. This bug has been fixed in 0.17.3 by performing a deterministic selection of the
best set of plugins available to the engine before starting up. See best set of plugins available to the engine before starting up. See
[pulumi/pulumi#2579](https://github.com/pulumi/pulumi/issues/2579) for discussion on this issue. - Add support for serializing JavaScript function that capture [BigInts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt).
- Support serializing arrow-functions with deconstructed parameters.
## 0.17.2 (Released March 15, 2019) ## 0.17.2 (Released March 15, 2019)

View file

@ -17,7 +17,8 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/util/result"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/backend/display" "github.com/pulumi/pulumi/pkg/backend/display"
@ -41,11 +42,11 @@ func newCancelCmd() *cobra.Command {
"\n" + "\n" +
"After this command completes successfully, the stack will be ready for further\n" + "After this command completes successfully, the stack will be ready for further\n" +
"updates.", "updates.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { Run: cmdutil.RunResultFunc(func(cmd *cobra.Command, args []string) result.Result {
// Use the stack provided or, if missing, default to the current one. // Use the stack provided or, if missing, default to the current one.
if len(args) > 0 { if len(args) > 0 {
if stack != "" { if stack != "" {
return errors.New("only one of --stack or argument stack name may be specified, not both") return result.Error("only one of --stack or argument stack name may be specified, not both")
} }
stack = args[0] stack = args[0]
@ -57,25 +58,26 @@ func newCancelCmd() *cobra.Command {
s, err := requireStack(stack, false, opts, true /*setCurrent*/) s, err := requireStack(stack, false, opts, true /*setCurrent*/)
if err != nil { if err != nil {
return err return result.FromError(err)
} }
// Ensure that we are targeting the Pulumi cloud. // Ensure that we are targeting the Pulumi cloud.
backend, ok := s.Backend().(httpstate.Backend) backend, ok := s.Backend().(httpstate.Backend)
if !ok { if !ok {
return errors.New("the `cancel` command is not supported for local stacks") return result.Error("the `cancel` command is not supported for local stacks")
} }
// Ensure the user really wants to do this. // Ensure the user really wants to do this.
stackName := string(s.Ref().Name()) stackName := string(s.Ref().Name())
prompt := fmt.Sprintf("This will irreversibly cancel the currently running update for '%s'!", stackName) prompt := fmt.Sprintf("This will irreversibly cancel the currently running update for '%s'!", stackName)
if !yes && !confirmPrompt(prompt, stackName, opts) { if !yes && !confirmPrompt(prompt, stackName, opts) {
return errors.New("confirmation declined") fmt.Println("confirmation declined")
return result.Bail()
} }
// Cancel the update. // Cancel the update.
if err := backend.CancelCurrentUpdate(commandContext(), s.Ref()); err != nil { if err := backend.CancelCurrentUpdate(commandContext(), s.Ref()); err != nil {
return err return result.FromError(err)
} }
msg := fmt.Sprintf( msg := fmt.Sprintf(

View file

@ -18,7 +18,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/util/result"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/backend/display" "github.com/pulumi/pulumi/pkg/backend/display"
@ -44,11 +45,11 @@ func newStackRmCmd() *cobra.Command {
"`destroy` command for removing a resources, as this is a distinct operation.\n" + "`destroy` command for removing a resources, as this is a distinct operation.\n" +
"\n" + "\n" +
"After this command completes, the stack will no longer be available for updates.", "After this command completes, the stack will no longer be available for updates.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { Run: cmdutil.RunResultFunc(func(cmd *cobra.Command, args []string) result.Result {
// Use the stack provided or, if missing, default to the current one. // Use the stack provided or, if missing, default to the current one.
if len(args) > 0 { if len(args) > 0 {
if stack != "" { if stack != "" {
return errors.New("only one of --stack or argument stack name may be specified, not both") return result.Error("only one of --stack or argument stack name may be specified, not both")
} }
stack = args[0] stack = args[0]
} }
@ -59,29 +60,30 @@ func newStackRmCmd() *cobra.Command {
s, err := requireStack(stack, false, opts, true /*setCurrent*/) s, err := requireStack(stack, false, opts, true /*setCurrent*/)
if err != nil { if err != nil {
return err return result.FromError(err)
} }
// Ensure the user really wants to do this. // Ensure the user really wants to do this.
prompt := fmt.Sprintf("This will permanently remove the '%s' stack!", s.Ref()) prompt := fmt.Sprintf("This will permanently remove the '%s' stack!", s.Ref())
if !yes && !confirmPrompt(prompt, s.Ref().String(), opts) { if !yes && !confirmPrompt(prompt, s.Ref().String(), opts) {
return errors.New("confirmation declined") fmt.Println("confirmation declined")
return result.Bail()
} }
hasResources, err := s.Remove(commandContext(), force) hasResources, err := s.Remove(commandContext(), force)
if err != nil { if err != nil {
if hasResources { if hasResources {
return errors.Errorf( return result.Errorf(
"'%s' still has resources; removal rejected; pass --force to override", s.Ref()) "'%s' still has resources; removal rejected; pass --force to override", s.Ref())
} }
return err return result.FromError(err)
} }
if !preserveConfig { if !preserveConfig {
// Blow away stack specific settings if they exist. If we get an ENOENT error, ignore it. // Blow away stack specific settings if they exist. If we get an ENOENT error, ignore it.
if path, err := workspace.DetectProjectStackPath(s.Ref().Name()); err == nil { if path, err := workspace.DetectProjectStackPath(s.Ref().Name()); err == nil {
if err = os.Remove(path); err != nil && !os.IsNotExist(err) { if err = os.Remove(path); err != nil && !os.IsNotExist(err) {
return err return result.FromError(err)
} }
} }
} }

View file

@ -18,6 +18,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/pulumi/pulumi/pkg/util/result"
"github.com/pulumi/pulumi/pkg/util/contract" "github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -109,7 +111,7 @@ func locateStackResource(opts display.Options, snap *deploy.Snapshot, urn resour
} }
// runStateEdit runs the given state edit function on a resource with the given URN in a given stack. // runStateEdit runs the given state edit function on a resource with the given URN in a given stack.
func runStateEdit(stackName string, urn resource.URN, operation edit.OperationFunc) error { func runStateEdit(stackName string, urn resource.URN, operation edit.OperationFunc) result.Result {
return runTotalStateEdit(stackName, func(opts display.Options, snap *deploy.Snapshot) error { return runTotalStateEdit(stackName, func(opts display.Options, snap *deploy.Snapshot) error {
res, err := locateStackResource(opts, snap, urn) res, err := locateStackResource(opts, snap, urn)
if err != nil { if err != nil {
@ -122,17 +124,18 @@ func runStateEdit(stackName string, urn resource.URN, operation edit.OperationFu
// runTotalStateEdit runs a snapshot-mutating function on the entirity of the given stack's snapshot. Before mutating // runTotalStateEdit runs a snapshot-mutating function on the entirity of the given stack's snapshot. Before mutating
// the snapshot, the user is prompted for confirmation if the current session is interactive. // the snapshot, the user is prompted for confirmation if the current session is interactive.
func runTotalStateEdit(stackName string, operation func(opts display.Options, snap *deploy.Snapshot) error) error { func runTotalStateEdit(stackName string,
operation func(opts display.Options, snap *deploy.Snapshot) error) result.Result {
opts := display.Options{ opts := display.Options{
Color: cmdutil.GetGlobalColorization(), Color: cmdutil.GetGlobalColorization(),
} }
s, err := requireStack(stackName, true, opts, true /*setCurrent*/) s, err := requireStack(stackName, true, opts, true /*setCurrent*/)
if err != nil { if err != nil {
return err return result.FromError(err)
} }
snap, err := s.Snapshot(commandContext()) snap, err := s.Snapshot(commandContext())
if err != nil { if err != nil {
return err return result.FromError(err)
} }
if cmdutil.Interactive() { if cmdutil.Interactive() {
@ -145,7 +148,8 @@ func runTotalStateEdit(stackName string, operation func(opts display.Options, sn
if err = survey.AskOne(&survey.Confirm{ if err = survey.AskOne(&survey.Confirm{
Message: prompt, Message: prompt,
}, &confirm, nil); err != nil || !confirm { }, &confirm, nil); err != nil || !confirm {
return errors.New("confirmation declined") fmt.Println("confirmation declined")
return result.Bail()
} }
} }
@ -154,7 +158,7 @@ func runTotalStateEdit(stackName string, operation func(opts display.Options, sn
// before we mutated it, we'll assert that we didn't make it invalid by mutating it. // before we mutated it, we'll assert that we didn't make it invalid by mutating it.
stackIsAlreadyHosed := snap.VerifyIntegrity() != nil stackIsAlreadyHosed := snap.VerifyIntegrity() != nil
if err = operation(opts, snap); err != nil { if err = operation(opts, snap); err != nil {
return err return result.FromError(err)
} }
// If the stack is already broken, don't bother verifying the integrity here. // If the stack is already broken, don't bother verifying the integrity here.
@ -165,11 +169,11 @@ func runTotalStateEdit(stackName string, operation func(opts display.Options, sn
// Once we've mutated the snapshot, import it back into the backend so that it can be persisted. // Once we've mutated the snapshot, import it back into the backend so that it can be persisted.
bytes, err := json.Marshal(stack.SerializeDeployment(snap)) bytes, err := json.Marshal(stack.SerializeDeployment(snap))
if err != nil { if err != nil {
return err return result.FromError(err)
} }
dep := apitype.UntypedDeployment{ dep := apitype.UntypedDeployment{
Version: apitype.DeploymentSchemaVersionCurrent, Version: apitype.DeploymentSchemaVersionCurrent,
Deployment: bytes, Deployment: bytes,
} }
return s.ImportDeployment(commandContext(), &dep) return result.WrapIfNonNil(s.ImportDeployment(commandContext(), &dep))
} }

View file

@ -17,13 +17,14 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/pulumi/pulumi/pkg/util/result"
"github.com/pulumi/pulumi/pkg/diag" "github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource" "github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy" "github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/resource/edit" "github.com/pulumi/pulumi/pkg/resource/edit"
"github.com/pulumi/pulumi/pkg/util/cmdutil" "github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -42,9 +43,9 @@ by its Pulumi URN (use 'pulumi stack --show-urns' to get it).
Resources can't be deleted if there exist other resources that depend on it or are parented to it. Protected resources Resources can't be deleted if there exist other resources that depend on it or are parented to it. Protected resources
will not be deleted unless it is specifically requested using the --force flag.`, will not be deleted unless it is specifically requested using the --force flag.`,
Args: cmdutil.ExactArgs(1), Args: cmdutil.ExactArgs(1),
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { Run: cmdutil.RunResultFunc(func(cmd *cobra.Command, args []string) result.Result {
urn := resource.URN(args[0]) urn := resource.URN(args[0])
err := runStateEdit(stack, urn, func(snap *deploy.Snapshot, res *resource.State) error { res := runStateEdit(stack, urn, func(snap *deploy.Snapshot, res *resource.State) error {
if !force { if !force {
return edit.DeleteResource(snap, res) return edit.DeleteResource(snap, res)
} }
@ -56,8 +57,8 @@ will not be deleted unless it is specifically requested using the --force flag.`
return edit.DeleteResource(snap, res) return edit.DeleteResource(snap, res)
}) })
if err != nil { if res != nil {
switch e := err.(type) { switch e := res.Error().(type) {
case edit.ResourceHasDependenciesError: case edit.ResourceHasDependenciesError:
message := "This resource can't be safely deleted because the following resources depend on it:\n" message := "This resource can't be safely deleted because the following resources depend on it:\n"
for _, dependentResource := range e.Dependencies { for _, dependentResource := range e.Dependencies {
@ -66,13 +67,13 @@ will not be deleted unless it is specifically requested using the --force flag.`
} }
message += "\nDelete those resources first before deleting this one." message += "\nDelete those resources first before deleting this one."
return errors.New(message) return result.Error(message)
case edit.ResourceProtectedError: case edit.ResourceProtectedError:
return errors.New( return result.Error(
"This resource can't be safely deleted because it is protected. " + "This resource can't be safely deleted because it is protected. " +
"Re-run this command with --force to force deletion") "Re-run this command with --force to force deletion")
default: default:
return err return res
} }
} }
fmt.Println("Resource deleted successfully") fmt.Println("Resource deleted successfully")

View file

@ -17,9 +17,10 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/pulumi/pulumi/pkg/util/result"
"github.com/pulumi/pulumi/pkg/util/contract" "github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/backend/display" "github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/resource/deploy" "github.com/pulumi/pulumi/pkg/resource/deploy"
@ -41,13 +42,13 @@ func newStateUnprotectCommand() *cobra.Command {
This command clears the 'protect' bit on one or more resources, allowing those resources to be deleted.`, This command clears the 'protect' bit on one or more resources, allowing those resources to be deleted.`,
Args: cmdutil.MaximumNArgs(1), Args: cmdutil.MaximumNArgs(1),
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { Run: cmdutil.RunResultFunc(func(cmd *cobra.Command, args []string) result.Result {
if unprotectAll { if unprotectAll {
return unprotectAllResources(stack) return unprotectAllResources(stack)
} }
if len(args) != 1 { if len(args) != 1 {
return errors.New("must provide a URN corresponding to a resource") return result.Error("must provide a URN corresponding to a resource")
} }
urn := resource.URN(args[0]) urn := resource.URN(args[0])
@ -62,8 +63,8 @@ This command clears the 'protect' bit on one or more resources, allowing those r
return cmd return cmd
} }
func unprotectAllResources(stackName string) error { func unprotectAllResources(stackName string) result.Result {
err := runTotalStateEdit(stackName, func(_ display.Options, snap *deploy.Snapshot) error { res := runTotalStateEdit(stackName, func(_ display.Options, snap *deploy.Snapshot) error {
for _, res := range snap.Resources { for _, res := range snap.Resources {
err := edit.UnprotectResource(snap, res) err := edit.UnprotectResource(snap, res)
contract.AssertNoError(err) contract.AssertNoError(err)
@ -72,17 +73,17 @@ func unprotectAllResources(stackName string) error {
return nil return nil
}) })
if err != nil { if res != nil {
return err return res
} }
fmt.Println("All resources successfully unprotected") fmt.Println("All resources successfully unprotected")
return nil return nil
} }
func unprotectResource(stackName string, urn resource.URN) error { func unprotectResource(stackName string, urn resource.URN) result.Result {
err := runStateEdit(stackName, urn, edit.UnprotectResource) res := runStateEdit(stackName, urn, edit.UnprotectResource)
if err != nil { if res != nil {
return err return res
} }
fmt.Println("Resource successfully unprotected") fmt.Println("Resource successfully unprotected")
return nil return nil

View file

@ -123,14 +123,14 @@ func PreviewThenPrompt(ctx context.Context, kind apitype.UpdateKind, stack Stack
} }
// Otherwise, ensure the user wants to proceed. // Otherwise, ensure the user wants to proceed.
err := confirmBeforeUpdating(kind, stack, events, op.Opts) res = confirmBeforeUpdating(kind, stack, events, op.Opts)
close(eventsChannel) close(eventsChannel)
return changes, result.WrapIfNonNil(err) return changes, res
} }
// confirmBeforeUpdating asks the user whether to proceed. A nil error means yes. // confirmBeforeUpdating asks the user whether to proceed. A nil error means yes.
func confirmBeforeUpdating(kind apitype.UpdateKind, stack Stack, func confirmBeforeUpdating(kind apitype.UpdateKind, stack Stack,
events []engine.Event, opts UpdateOptions) error { events []engine.Event, opts UpdateOptions) result.Result {
for { for {
var response string var response string
@ -167,11 +167,12 @@ func confirmBeforeUpdating(kind apitype.UpdateKind, stack Stack,
Options: choices, Options: choices,
Default: string(no), Default: string(no),
}, &response, nil); err != nil { }, &response, nil); err != nil {
return errors.Wrapf(err, "confirmation cancelled, not proceeding with the %s", kind) return result.FromError(errors.Wrapf(err, "confirmation cancelled, not proceeding with the %s", kind))
} }
if response == string(no) { if response == string(no) {
return errors.Errorf("confirmation declined, not proceeding with the %s", kind) fmt.Printf("confirmation declined, not proceeding with the %s\n", kind)
return result.Bail()
} }
if response == string(yes) { if response == string(yes) {