Merge lp:~axwalk/juju-core/lp1223277-check-provisioned-ignore-stderr into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 1811
Proposed branch: lp:~axwalk/juju-core/lp1223277-check-provisioned-ignore-stderr
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 183 lines (+83/-17)
4 files modified
environs/manual/agent.go (+4/-6)
environs/manual/detection.go (+10/-6)
environs/manual/detection_test.go (+44/-5)
environs/manual/ssh.go (+25/-0)
To merge this branch: bzr merge lp:~axwalk/juju-core/lp1223277-check-provisioned-ignore-stderr
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+184911@code.launchpad.net

Commit message

environs/manual: ignore stderr in checkProvisioned

ssh may issue warnings on stderr, for example if
the target host does not exist in known_hosts.
This was tripping up the checkProvisioned function,
which was incorrectly checking that both stdout and
stderr were empty.

Fixes #1223277

https://codereview.appspot.com/13477045/

Description of the change

environs/manual: ignore stderr in checkProvisioned

ssh may issue warnings on stderr, for example if
the target host does not exist in known_hosts.
This was tripping up the checkProvisioned function,
which was incorrectly checking that both stdout and
stderr were empty.

Fixes #1223277

https://codereview.appspot.com/13477045/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (4.7 KiB)

Reviewers: mp+184911_code.launchpad.net,

Message:
Please take a look.

Description:
environs/manual: ignore stderr in checkProvisioned

ssh may issue warnings on stderr, for example if
the target host does not exist in known_hosts.
This was tripping up the checkProvisioned function,
which was incorrectly checking that both stdout and
stderr were empty.

Fixes #1223277

https://code.launchpad.net/~axwalk/juju-core/lp1223277-check-provisioned-ignore-stderr/+merge/184911

(do not edit description out of merge proposal)

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

Affected files (+54, -8 lines):
   A [revision details]
   M environs/manual/detection.go
   M environs/manual/detection_test.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-20130910174617-1ib2wgm1qymgkvvs
+New revision: <email address hidden>

Index: environs/manual/detection.go
=== modified file 'environs/manual/detection.go'
--- environs/manual/detection.go 2013-08-23 06:57:09 +0000
+++ environs/manual/detection.go 2013-09-11 01:58:11 +0000
@@ -26,11 +26,16 @@
  func checkProvisioned(sshHost string) (bool, error) {
   cmd := exec.Command("ssh", sshHost, "bash")
   cmd.Stdin = bytes.NewBufferString(checkProvisionedScript)
- out, err := cmd.CombinedOutput()
- if err != nil {
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ if stderr.Len() != 0 {
+ err = fmt.Errorf("%v (%v)", err, strings.TrimSpace(stderr.String()))
+ }
    return false, err
   }
- return len(strings.TrimSpace(string(out))) > 0, nil
+ return len(strings.TrimSpace(stdout.String())) > 0, nil
  }

  // detectSeriesHardwareCharacteristics detects the OS series

Index: environs/manual/detection_test.go
=== modified file 'environs/manual/detection_test.go'
--- environs/manual/detection_test.go 2013-08-29 03:58:19 +0000
+++ environs/manual/detection_test.go 2013-09-11 01:58:11 +0000
@@ -13,6 +13,7 @@
   gc "launchpad.net/gocheck"

   "launchpad.net/juju-core/testing"
+ jc "launchpad.net/juju-core/testing/checkers"
  )

  type detectionSuite struct {
@@ -34,7 +35,10 @@
          echo "ERROR: did not match expected input" >&2
          exit $exitcode
      fi
-%s
+ # stdout
+ %s
+ # stderr
+ %s
      exit %d
  else
      export PATH=${PATH#*:}
@@ -44,14 +48,25 @@
  // sshresponse creates a fake "ssh" command in a new $PATH,
  // updates $PATH, and returns a function to reset $PATH to
  // its original value when called.
-func sshresponse(c *gc.C, input, output string, rc int) func() {
+//
+// output may be:
+// - nil (no output)
+// - a string (stdout)
+// - a slice of strings, of length two (stdout, stderr)
+func sshresponse(c *gc.C, input string, output interface{}, rc int) func()
{
   fakebin := c.MkDir()
   ssh := filepath.Join(fakebin, "ssh")
   sshexpectedinput := ssh + ".expected-input"
- if output != "" {
- output = fmt.Sprintf("cat<<EOF\n%s\nEOF", output)
+ var stdout, stderr string
+ switch output := output.(typ...

Read more...

Revision history for this message
William Reade (fwereade) wrote :

On 2013/09/11 02:21:17, dfc wrote:
> On 2013/09/11 02:07:37, axw wrote:
> > Please take a look.

> Can I suggest a different solution.

> 1. Use cmd.Output, not CombinedOutput

> 2. pass "-o", "StrictHostKeyChecking no", we do that elsewhere in
cmd/juju so it
> has a precident.

+1. Isn't there a common set of ssh flags somewhere?

https://codereview.appspot.com/13477045/

Revision history for this message
Dave Cheney (dave-cheney) wrote :

Well, there is only that one, but your argument continues to be valid.

On 12/09/2013, at 0:24, William Reade <email address hidden> wrote:

> On 2013/09/11 02:21:17, dfc wrote:
>> On 2013/09/11 02:07:37, axw wrote:
>>> Please take a look.
>
>> Can I suggest a different solution.
>
>> 1. Use cmd.Output, not CombinedOutput
>
>> 2. pass "-o", "StrictHostKeyChecking no", we do that elsewhere in
> cmd/juju so it
>> has a precident.
>
> +1. Isn't there a common set of ssh flags somewhere?
>
> https://codereview.appspot.com/13477045/
>
> --
> https://code.launchpad.net/~axwalk/juju-core/lp1223277-check-provisioned-ignore-stderr/+merge/184911
> You are subscribed to branch lp:juju-core.

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2013/09/11 14:24:04, fwereade wrote:
> On 2013/09/11 02:21:17, dfc wrote:
> > On 2013/09/11 02:07:37, axw wrote:
> > > Please take a look.
> >
> > Can I suggest a different solution.
> >
> > 1. Use cmd.Output, not CombinedOutput

I want stderr still (for the error), so I need to set Stderr. So I'm
setting Stdout for consistency.

> > 2. pass "-o", "StrictHostKeyChecking no", we do that elsewhere in
cmd/juju so
> it
> > has a precident.

Okay. The stderr is getting squashed anyway, unless there's a real
error, so it's not a real security concern.

> +1. Isn't there a common set of ssh flags somewhere?

No. I'll create a tech-debt bug.

https://codereview.appspot.com/13477045/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
William Reade (fwereade) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'environs/manual/agent.go'
--- environs/manual/agent.go 2013-09-03 09:03:02 +0000
+++ environs/manual/agent.go 2013-09-12 02:07:53 +0000
@@ -7,7 +7,6 @@
7 "encoding/base64"7 "encoding/base64"
8 "fmt"8 "fmt"
9 "os"9 "os"
10 "os/exec"
11 "strings"10 "strings"
1211
13 corecloudinit "launchpad.net/juju-core/cloudinit"12 corecloudinit "launchpad.net/juju-core/cloudinit"
@@ -45,12 +44,11 @@
45 }44 }
46 scriptBase64 := base64.StdEncoding.EncodeToString([]byte(script))45 scriptBase64 := base64.StdEncoding.EncodeToString([]byte(script))
47 script = fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, scriptBase64)46 script = fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, scriptBase64)
48 sshArgs := []string{47 cmd := sshCommand(
49 args.host,48 args.host,
50 "-t", // allocate a pseudo-tty49 fmt.Sprintf("sudo bash -c '%s'", script),
51 "--", fmt.Sprintf("sudo bash -c '%s'", script),50 allocateTTY,
52 }51 )
53 cmd := exec.Command("ssh", sshArgs...)
54 cmd.Stdout = os.Stdout52 cmd.Stdout = os.Stdout
55 cmd.Stderr = os.Stderr53 cmd.Stderr = os.Stderr
56 cmd.Stdin = os.Stdin54 cmd.Stdin = os.Stdin
5755
=== modified file 'environs/manual/detection.go'
--- environs/manual/detection.go 2013-08-23 06:57:09 +0000
+++ environs/manual/detection.go 2013-09-12 02:07:53 +0000
@@ -6,7 +6,6 @@
6import (6import (
7 "bytes"7 "bytes"
8 "fmt"8 "fmt"
9 "os/exec"
10 "regexp"9 "regexp"
11 "strconv"10 "strconv"
12 "strings"11 "strings"
@@ -24,20 +23,25 @@
24// checkProvisioned checks if any juju upstart jobs already23// checkProvisioned checks if any juju upstart jobs already
25// exist on the host machine.24// exist on the host machine.
26func checkProvisioned(sshHost string) (bool, error) {25func checkProvisioned(sshHost string) (bool, error) {
27 cmd := exec.Command("ssh", sshHost, "bash")26 cmd := sshCommand(sshHost, "bash")
27 var stdout, stderr bytes.Buffer
28 cmd.Stdin = bytes.NewBufferString(checkProvisionedScript)28 cmd.Stdin = bytes.NewBufferString(checkProvisionedScript)
29 out, err := cmd.CombinedOutput()29 cmd.Stdout = &stdout
30 if err != nil {30 cmd.Stderr = &stderr
31 if err := cmd.Run(); err != nil {
32 if stderr.Len() != 0 {
33 err = fmt.Errorf("%v (%v)", err, strings.TrimSpace(stderr.String()))
34 }
31 return false, err35 return false, err
32 }36 }
33 return len(strings.TrimSpace(string(out))) > 0, nil37 return len(strings.TrimSpace(stdout.String())) > 0, nil
34}38}
3539
36// detectSeriesHardwareCharacteristics detects the OS series40// detectSeriesHardwareCharacteristics detects the OS series
37// and hardware characteristics of the remote machine by41// and hardware characteristics of the remote machine by
38// connecting to the machine and executing a bash script.42// connecting to the machine and executing a bash script.
39func detectSeriesAndHardwareCharacteristics(sshHost string) (hc instance.HardwareCharacteristics, series string, err error) {43func detectSeriesAndHardwareCharacteristics(sshHost string) (hc instance.HardwareCharacteristics, series string, err error) {
40 cmd := exec.Command("ssh", sshHost, "bash")44 cmd := sshCommand(sshHost, "bash")
41 cmd.Stdin = bytes.NewBufferString(detectionScript)45 cmd.Stdin = bytes.NewBufferString(detectionScript)
42 out, err := cmd.CombinedOutput()46 out, err := cmd.CombinedOutput()
43 if err != nil {47 if err != nil {
4448
=== modified file 'environs/manual/detection_test.go'
--- environs/manual/detection_test.go 2013-08-29 03:58:19 +0000
+++ environs/manual/detection_test.go 2013-09-12 02:07:53 +0000
@@ -13,6 +13,7 @@
13 gc "launchpad.net/gocheck"13 gc "launchpad.net/gocheck"
1414
15 "launchpad.net/juju-core/testing"15 "launchpad.net/juju-core/testing"
16 jc "launchpad.net/juju-core/testing/checkers"
16)17)
1718
18type detectionSuite struct {19type detectionSuite struct {
@@ -34,7 +35,10 @@
34 echo "ERROR: did not match expected input" >&235 echo "ERROR: did not match expected input" >&2
35 exit $exitcode36 exit $exitcode
36 fi37 fi
37%s38 # stdout
39 %s
40 # stderr
41 %s
38 exit %d42 exit %d
39else43else
40 export PATH=${PATH#*:}44 export PATH=${PATH#*:}
@@ -44,14 +48,25 @@
44// sshresponse creates a fake "ssh" command in a new $PATH,48// sshresponse creates a fake "ssh" command in a new $PATH,
45// updates $PATH, and returns a function to reset $PATH to49// updates $PATH, and returns a function to reset $PATH to
46// its original value when called.50// its original value when called.
47func sshresponse(c *gc.C, input, output string, rc int) func() {51//
52// output may be:
53// - nil (no output)
54// - a string (stdout)
55// - a slice of strings, of length two (stdout, stderr)
56func sshresponse(c *gc.C, input string, output interface{}, rc int) func() {
48 fakebin := c.MkDir()57 fakebin := c.MkDir()
49 ssh := filepath.Join(fakebin, "ssh")58 ssh := filepath.Join(fakebin, "ssh")
50 sshexpectedinput := ssh + ".expected-input"59 sshexpectedinput := ssh + ".expected-input"
51 if output != "" {60 var stdout, stderr string
52 output = fmt.Sprintf("cat<<EOF\n%s\nEOF", output)61 switch output := output.(type) {
62 case nil:
63 case string:
64 stdout = fmt.Sprintf("cat<<EOF\n%s\nEOF", output)
65 case []string:
66 stdout = fmt.Sprintf("cat<<EOF\n%s\nEOF", output[0])
67 stderr = fmt.Sprintf("cat>&2<<EOF\n%s\nEOF", output[1])
53 }68 }
54 script := fmt.Sprintf(sshscript, output, rc)69 script := fmt.Sprintf(sshscript, stdout, stderr, rc)
55 err := ioutil.WriteFile(ssh, []byte(script), 0777)70 err := ioutil.WriteFile(ssh, []byte(script), 0777)
56 c.Assert(err, gc.IsNil)71 c.Assert(err, gc.IsNil)
57 err = ioutil.WriteFile(sshexpectedinput, []byte(input), 0644)72 err = ioutil.WriteFile(sshexpectedinput, []byte(input), 0644)
@@ -139,3 +154,27 @@
139 c.Assert(hc.String(), gc.Equals, test.expectedHc)154 c.Assert(hc.String(), gc.Equals, test.expectedHc)
140 }155 }
141}156}
157
158func (s *detectionSuite) TestCheckProvisioned(c *gc.C) {
159 defer sshresponse(c, checkProvisionedScript, "", 0)()
160 provisioned, err := checkProvisioned("example.com")
161 c.Assert(err, gc.IsNil)
162 c.Assert(provisioned, jc.IsFalse)
163
164 defer sshresponse(c, checkProvisionedScript, "non-empty", 0)()
165 provisioned, err = checkProvisioned("example.com")
166 c.Assert(err, gc.IsNil)
167 c.Assert(provisioned, jc.IsTrue)
168
169 // stderr should not affect result.
170 defer sshresponse(c, checkProvisionedScript, []string{"", "non-empty-stderr"}, 0)()
171 provisioned, err = checkProvisioned("example.com")
172 c.Assert(err, gc.IsNil)
173 c.Assert(provisioned, jc.IsFalse)
174
175 // if the script fails for whatever reason, then checkProvisioned
176 // will return an error. stderr will be included in the error message.
177 defer sshresponse(c, checkProvisionedScript, []string{"non-empty-stdout", "non-empty-stderr"}, 255)()
178 _, err = checkProvisioned("example.com")
179 c.Assert(err, gc.ErrorMatches, "exit status 255 \\(non-empty-stderr\\)")
180}
142181
=== added file 'environs/manual/ssh.go'
--- environs/manual/ssh.go 1970-01-01 00:00:00 +0000
+++ environs/manual/ssh.go 2013-09-12 02:07:53 +0000
@@ -0,0 +1,25 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package manual
5
6import (
7 "os/exec"
8)
9
10type sshOption []string
11
12var allocateTTY sshOption = []string{"-t"}
13
14// TODO(axw) 2013-09-12 bug #1224230
15// Move this to a common package for use in cmd/juju, and others.
16var commonSSHOptions = []string{"-o", "StrictHostKeyChecking no"}
17
18func sshCommand(host string, command string, options ...sshOption) *exec.Cmd {
19 args := append([]string{}, commonSSHOptions...)
20 for _, option := range options {
21 args = append(args, option...)
22 }
23 args = append(args, host, "--", command)
24 return exec.Command("ssh", args...)
25}

Subscribers

People subscribed via source and target branches

to status/vote changes: