Merge lp:~natefinch/juju-core/022-destroyer into lp:~go-bot/juju-core/trunk

Proposed by Nate Finch
Status: Merged
Approved by: Nate Finch
Approved revision: no longer in the source branch.
Merged at revision: 2022
Proposed branch: lp:~natefinch/juju-core/022-destroyer
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 187 lines (+47/-22)
2 files modified
cmd/juju/cmd_test.go (+15/-12)
cmd/juju/destroyenvironment.go (+32/-10)
To merge this branch: bzr merge lp:~natefinch/juju-core/022-destroyer
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+191501@code.launchpad.net

Commit message

Destroy environment now requires that you always type the name of the environment you're destroying, it will not use the default or current environment. This is to prevent people from accidentally destroying the wrong environment, since no one really reads the warning messages, and destroy environment is pretty cataclysmic.

Description of the change

Add safety belt to destroy-environment

Destroy-environment is dangerous and we should prevent people from shooting themselves in the foot if at all possible. This change removes the -y flag from destroy-environment and changes the "are you sure?" prompt to force the user to type "destroy <environment_name>". The flag is too easy to pass without ththinking, and the y at the prompt is similarly too easy to type in without the user actually thinking about what they're doing. Forcing them to type the environment name will also ensure they don't accidentally destroy the wrong environment by accident.

https://codereview.appspot.com/14502070/

To post a comment you must log in.
Revision history for this message
Nate Finch (natefinch) wrote :
Download full text (7.0 KiB)

Reviewers: mp+191501_code.launchpad.net,

Message:
Please take a look.

Description:
Add safety belt to destroy-environment

Destroy-environment is dangerous and we should prevent people from
shooting themselves in the foot if at all possible. This change removes
the -y flag from destroy-environment and changes the "are you sure?"
prompt to force the user to type "destroy <environment_name>". The flag
is too easy to pass without ththinking, and the y at the prompt is
similarly too easy to type in without the user actually thinking about
what they're doing. Forcing them to type the environment name will also
ensure they don't accidentally destroy the wrong environment by
accident.

https://code.launchpad.net/~natefinch/juju-core/022-destroyer/+merge/191501

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/14502070/

Affected files (+33, -44 lines):
   A [revision details]
   M cmd/juju/cmd_test.go
   M cmd/juju/destroyenvironment.go

Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-20131016154732-13eh4uj8dhsfc1mw
+New revision: <email address hidden>

Index: cmd/juju/cmd_test.go
=== modified file 'cmd/juju/cmd_test.go'
--- cmd/juju/cmd_test.go 2013-10-10 23:17:43 +0000
+++ cmd/juju/cmd_test.go 2013-10-16 20:26:42 +0000
@@ -160,6 +160,10 @@
  }

  func (*CmdSuite) TestDestroyEnvironmentCommand(c *gc.C) {
+ var stdin bytes.Buffer
+ ctx := cmd.DefaultContext()
+ ctx.Stdin = &stdin
+
   // Prepare the environment so we can destroy it.
   store, err := configstore.Default()
   c.Assert(err, gc.IsNil)
@@ -171,7 +175,8 @@
   c.Assert(err, gc.IsNil)

   // normal destroy
- opc, errc := runCommand(nullContext(),
new(DestroyEnvironmentCommand), "--yes")
+ stdin.WriteString("destroy peckham")
+ opc, errc := runCommand(ctx, new(DestroyEnvironmentCommand))
   c.Check(<-errc, gc.IsNil)
   c.Check((<-opc).(dummy.OpDestroy).Env, gc.Equals, "peckham")

@@ -183,26 +188,13 @@
   c.Assert(err, gc.IsNil)

   // destroy with broken environment
- opc, errc = runCommand(nullContext(),
new(DestroyEnvironmentCommand), "--yes", "-e", "brokenenv")
+ stdin.WriteString("destroy brokenenv")
+ opc, errc = runCommand(ctx,
new(DestroyEnvironmentCommand), "-e", "brokenenv")
   c.Check(<-opc, gc.IsNil)
   c.Check(<-errc, gc.ErrorMatches, "dummy.Destroy is broken")
   c.Check(<-opc, gc.IsNil)
  }

-func (*CmdSuite) TestDestroyEnvironmentCommandConfirmationFlag(c *gc.C) {
- com := new(DestroyEnvironmentCommand)
- c.Check(coretesting.InitCommand(com, nil), gc.IsNil)
- c.Check(com.assumeYes, gc.Equals, false)
-
- com = new(DestroyEnvironmentCommand)
- c.Check(coretesting.InitCommand(com, []string{"-y"}), gc.IsNil)
- c.Check(com.assumeYes, gc.Equals, true)
-
- com = new(DestroyEnvironmentCommand)
- c.Check(coretesting.InitCommand(com, []string{"--yes"}), gc.IsNil)
- c.Check(com.assumeYes, gc.Equals, true)
-}
-
  func (*CmdSuite) TestDestroyEnvironmentCommandConfirmation(c *gc.C) {
   var stdin, stdout bytes.Buffer
   ctx := cmd.DefaultContex...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 2013/10/16 20:50:46, nate.finch wrote:
> Please take a look.

Please no!

I want to be able to use juju easily in scripts. This is
Unix and our target audience are developers and admins. Please
allow us to do dangerous things on the command line.

https://codereview.appspot.com/14502070/

Revision history for this message
Nate Finch (natefinch) wrote :
Revision history for this message
Nate Finch (natefinch) wrote :

I updated the code so that the -y flag is back, but you must always
specify the environment... it'll never take the current or default
environment.

The format of the command is now:

juju destroy-environment <envname> [-y|--yes]

Note, I chose not to use the normal -e environment flag because a flag
implies it's optional (which it's not).

https://codereview.appspot.com/14502070/

Revision history for this message
Tim Penhey (thumper) wrote :

LGTM

I think this is an overall improvement to the UI. Still scriptable IMO.

https://codereview.appspot.com/14502070/diff/5001/cmd/juju/destroyenvironment.go
File cmd/juju/destroyenvironment.go (right):

https://codereview.appspot.com/14502070/diff/5001/cmd/juju/destroyenvironment.go#newcode54
cmd/juju/destroyenvironment.go:54: scanner :=
bufio.NewScanner(ctx.Stdin)
Just curious, but what benefit do we get over using bufio.NewScanner
over fmt.Fscanln?

https://codereview.appspot.com/14502070/

Revision history for this message
Nate Finch (natefinch) wrote :

On 2013/10/31 22:06:09, thumper wrote:
> LGTM

> I think this is an overall improvement to the UI. Still scriptable
IMO.

https://codereview.appspot.com/14502070/diff/5001/cmd/juju/destroyenvironment.go
> File cmd/juju/destroyenvironment.go (right):

https://codereview.appspot.com/14502070/diff/5001/cmd/juju/destroyenvironment.go#newcode54
> cmd/juju/destroyenvironment.go:54: scanner :=
bufio.NewScanner(ctx.Stdin)
> Just curious, but what benefit do we get over using bufio.NewScanner
over
> fmt.Fscanln?

  fmt.Fscanln actually fails to return the entire string, and returns an
error about no EOL. We were ignoring it, but it bit me when I was
twiddling with the code and tried to get it to read the full line... so
I figured it was better to make the code a good example rather than a
bad example, even if functionally it behaved the same in this particular
case.

https://codereview.appspot.com/14502070/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/juju/cmd_test.go'
2--- cmd/juju/cmd_test.go 2013-10-10 23:17:43 +0000
3+++ cmd/juju/cmd_test.go 2013-10-17 17:43:53 +0000
4@@ -88,7 +88,6 @@
5 // flags, and that extra arguments will cause parsing to fail.
6 var EnvironmentInitTests = []func() (cmd.Command, []string){
7 func() (cmd.Command, []string) { return new(BootstrapCommand), nil },
8- func() (cmd.Command, []string) { return new(DestroyEnvironmentCommand), nil },
9 func() (cmd.Command, []string) {
10 return new(DeployCommand), []string{"charm-name", "service-name"}
11 },
12@@ -170,8 +169,12 @@
13 _, err = store.ReadInfo("peckham")
14 c.Assert(err, gc.IsNil)
15
16+ // check environment is mandatory
17+ opc, errc := runCommand(nullContext(), new(DestroyEnvironmentCommand))
18+ c.Check(<-errc, gc.Equals, NoEnvironmentError)
19+
20 // normal destroy
21- opc, errc := runCommand(nullContext(), new(DestroyEnvironmentCommand), "--yes")
22+ opc, errc = runCommand(nullContext(), new(DestroyEnvironmentCommand), "peckham", "--yes")
23 c.Check(<-errc, gc.IsNil)
24 c.Check((<-opc).(dummy.OpDestroy).Env, gc.Equals, "peckham")
25
26@@ -183,7 +186,7 @@
27 c.Assert(err, gc.IsNil)
28
29 // destroy with broken environment
30- opc, errc = runCommand(nullContext(), new(DestroyEnvironmentCommand), "--yes", "-e", "brokenenv")
31+ opc, errc = runCommand(nullContext(), new(DestroyEnvironmentCommand), "brokenenv", "--yes")
32 c.Check(<-opc, gc.IsNil)
33 c.Check(<-errc, gc.ErrorMatches, "dummy.Destroy is broken")
34 c.Check(<-opc, gc.IsNil)
35@@ -191,15 +194,15 @@
36
37 func (*CmdSuite) TestDestroyEnvironmentCommandConfirmationFlag(c *gc.C) {
38 com := new(DestroyEnvironmentCommand)
39- c.Check(coretesting.InitCommand(com, nil), gc.IsNil)
40+ c.Check(coretesting.InitCommand(com, []string{"peckham"}), gc.IsNil)
41 c.Check(com.assumeYes, gc.Equals, false)
42
43 com = new(DestroyEnvironmentCommand)
44- c.Check(coretesting.InitCommand(com, []string{"-y"}), gc.IsNil)
45+ c.Check(coretesting.InitCommand(com, []string{"peckham", "-y"}), gc.IsNil)
46 c.Check(com.assumeYes, gc.Equals, true)
47
48 com = new(DestroyEnvironmentCommand)
49- c.Check(coretesting.InitCommand(com, []string{"--yes"}), gc.IsNil)
50+ c.Check(coretesting.InitCommand(com, []string{"peckham", "--yes"}), gc.IsNil)
51 c.Check(com.assumeYes, gc.Equals, true)
52 }
53
54@@ -219,16 +222,16 @@
55
56 // Ensure confirmation is requested if "-y" is not specified.
57 stdin.WriteString("n")
58- opc, errc := runCommand(ctx, new(DestroyEnvironmentCommand))
59+ opc, errc := runCommand(ctx, new(DestroyEnvironmentCommand), "peckham")
60 c.Check(<-errc, gc.ErrorMatches, "Environment destruction aborted")
61 c.Check(<-opc, gc.IsNil)
62- c.Check(stdout.String(), gc.Matches, "WARNING:.*peckham.*\\(type: dummy\\)(.|\n)*")
63+ c.Check(stdout.String(), gc.Matches, "WARNING!.*peckham.*\\(type: dummy\\)(.|\n)*")
64 assertEnvironNotDestroyed(c, env, store)
65
66 // EOF on stdin: equivalent to answering no.
67 stdin.Reset()
68 stdout.Reset()
69- opc, errc = runCommand(ctx, new(DestroyEnvironmentCommand))
70+ opc, errc = runCommand(ctx, new(DestroyEnvironmentCommand), "peckham")
71 c.Check(<-opc, gc.IsNil)
72 c.Check(<-errc, gc.ErrorMatches, "Environment destruction aborted")
73 assertEnvironNotDestroyed(c, env, store)
74@@ -236,7 +239,7 @@
75 // "--yes" passed: no confirmation request.
76 stdin.Reset()
77 stdout.Reset()
78- opc, errc = runCommand(ctx, new(DestroyEnvironmentCommand), "--yes")
79+ opc, errc = runCommand(ctx, new(DestroyEnvironmentCommand), "peckham", "--yes")
80 c.Check(<-errc, gc.IsNil)
81 c.Check((<-opc).(dummy.OpDestroy).Env, gc.Equals, "peckham")
82 c.Check(stdout.String(), gc.Equals, "")
83@@ -251,10 +254,10 @@
84 stdin.Reset()
85 stdout.Reset()
86 stdin.WriteString(answer)
87- opc, errc = runCommand(ctx, new(DestroyEnvironmentCommand))
88+ opc, errc = runCommand(ctx, new(DestroyEnvironmentCommand), "peckham")
89 c.Check(<-errc, gc.IsNil)
90 c.Check((<-opc).(dummy.OpDestroy).Env, gc.Equals, "peckham")
91- c.Check(stdout.String(), gc.Matches, "WARNING:.*peckham.*\\(type: dummy\\)(.|\n)*")
92+ c.Check(stdout.String(), gc.Matches, "WARNING!.*peckham.*\\(type: dummy\\)(.|\n)*")
93 assertEnvironDestroyed(c, env, store)
94 }
95 }
96
97=== modified file 'cmd/juju/destroyenvironment.go'
98--- cmd/juju/destroyenvironment.go 2013-10-02 10:34:22 +0000
99+++ cmd/juju/destroyenvironment.go 2013-10-17 17:43:53 +0000
100@@ -4,8 +4,10 @@
101 package main
102
103 import (
104+ "bufio"
105 "errors"
106 "fmt"
107+ "io"
108 "strings"
109
110 "launchpad.net/gnuflag"
111@@ -15,21 +17,24 @@
112 "launchpad.net/juju-core/environs/configstore"
113 )
114
115+var NoEnvironmentError = errors.New("no environment specified")
116+
117 // DestroyEnvironmentCommand destroys an environment.
118 type DestroyEnvironmentCommand struct {
119- cmd.EnvCommandBase
120+ cmd.CommandBase
121+ envName string
122 assumeYes bool
123 }
124
125 func (c *DestroyEnvironmentCommand) Info() *cmd.Info {
126 return &cmd.Info{
127 Name: "destroy-environment",
128+ Args: "<environment name>",
129 Purpose: "terminate all machines and other associated resources for an environment",
130 }
131 }
132
133 func (c *DestroyEnvironmentCommand) SetFlags(f *gnuflag.FlagSet) {
134- c.EnvCommandBase.SetFlags(f)
135 f.BoolVar(&c.assumeYes, "y", false, "Do not ask for confirmation")
136 f.BoolVar(&c.assumeYes, "yes", false, "")
137 }
138@@ -39,15 +44,20 @@
139 if err != nil {
140 return fmt.Errorf("cannot open environment info storage: %v", err)
141 }
142- environ, err := environs.NewFromName(c.EnvName, store)
143+ environ, err := environs.NewFromName(c.envName, store)
144 if err != nil {
145 return err
146 }
147 if !c.assumeYes {
148- var answer string
149- fmt.Fprintf(ctx.Stdout, destroyEnvMsg[1:], environ.Name(), environ.Config().Type())
150- fmt.Fscanln(ctx.Stdin, &answer) // ignore error, treat as "n"
151- answer = strings.ToLower(answer)
152+ fmt.Fprintf(ctx.Stdout, destroyEnvMsg, environ.Name(), environ.Config().Type())
153+
154+ scanner := bufio.NewScanner(ctx.Stdin)
155+ scanner.Scan()
156+ err := scanner.Err()
157+ if err != nil && err != io.EOF {
158+ return fmt.Errorf("Environment destruction aborted: %s", err)
159+ }
160+ answer := strings.ToLower(scanner.Text())
161 if answer != "y" && answer != "yes" {
162 return errors.New("Environment destruction aborted")
163 }
164@@ -60,8 +70,20 @@
165 return environs.Destroy(environ, store)
166 }
167
168-const destroyEnvMsg = `
169-WARNING: this command will destroy the %q environment (type: %s)
170+func (c *DestroyEnvironmentCommand) Init(args []string) error {
171+ switch len(args) {
172+ case 0:
173+ return NoEnvironmentError
174+ case 1:
175+ c.envName = args[0]
176+ return nil
177+ default:
178+ return cmd.CheckEmpty(args[1:])
179+ }
180+}
181+
182+var destroyEnvMsg = `
183+WARNING! this command will destroy the %q environment (type: %s)
184 This includes all machines, services, data and other resources.
185
186-Continue [y/N]? `
187+Continue [y/N]? `[1:]

Subscribers

People subscribed via source and target branches

to status/vote changes: