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
1=== modified file 'environs/manual/agent.go'
2--- environs/manual/agent.go 2013-09-03 09:03:02 +0000
3+++ environs/manual/agent.go 2013-09-12 02:07:53 +0000
4@@ -7,7 +7,6 @@
5 "encoding/base64"
6 "fmt"
7 "os"
8- "os/exec"
9 "strings"
10
11 corecloudinit "launchpad.net/juju-core/cloudinit"
12@@ -45,12 +44,11 @@
13 }
14 scriptBase64 := base64.StdEncoding.EncodeToString([]byte(script))
15 script = fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, scriptBase64)
16- sshArgs := []string{
17+ cmd := sshCommand(
18 args.host,
19- "-t", // allocate a pseudo-tty
20- "--", fmt.Sprintf("sudo bash -c '%s'", script),
21- }
22- cmd := exec.Command("ssh", sshArgs...)
23+ fmt.Sprintf("sudo bash -c '%s'", script),
24+ allocateTTY,
25+ )
26 cmd.Stdout = os.Stdout
27 cmd.Stderr = os.Stderr
28 cmd.Stdin = os.Stdin
29
30=== modified file 'environs/manual/detection.go'
31--- environs/manual/detection.go 2013-08-23 06:57:09 +0000
32+++ environs/manual/detection.go 2013-09-12 02:07:53 +0000
33@@ -6,7 +6,6 @@
34 import (
35 "bytes"
36 "fmt"
37- "os/exec"
38 "regexp"
39 "strconv"
40 "strings"
41@@ -24,20 +23,25 @@
42 // checkProvisioned checks if any juju upstart jobs already
43 // exist on the host machine.
44 func checkProvisioned(sshHost string) (bool, error) {
45- cmd := exec.Command("ssh", sshHost, "bash")
46+ cmd := sshCommand(sshHost, "bash")
47+ var stdout, stderr bytes.Buffer
48 cmd.Stdin = bytes.NewBufferString(checkProvisionedScript)
49- out, err := cmd.CombinedOutput()
50- if err != nil {
51+ cmd.Stdout = &stdout
52+ cmd.Stderr = &stderr
53+ if err := cmd.Run(); err != nil {
54+ if stderr.Len() != 0 {
55+ err = fmt.Errorf("%v (%v)", err, strings.TrimSpace(stderr.String()))
56+ }
57 return false, err
58 }
59- return len(strings.TrimSpace(string(out))) > 0, nil
60+ return len(strings.TrimSpace(stdout.String())) > 0, nil
61 }
62
63 // detectSeriesHardwareCharacteristics detects the OS series
64 // and hardware characteristics of the remote machine by
65 // connecting to the machine and executing a bash script.
66 func detectSeriesAndHardwareCharacteristics(sshHost string) (hc instance.HardwareCharacteristics, series string, err error) {
67- cmd := exec.Command("ssh", sshHost, "bash")
68+ cmd := sshCommand(sshHost, "bash")
69 cmd.Stdin = bytes.NewBufferString(detectionScript)
70 out, err := cmd.CombinedOutput()
71 if err != nil {
72
73=== modified file 'environs/manual/detection_test.go'
74--- environs/manual/detection_test.go 2013-08-29 03:58:19 +0000
75+++ environs/manual/detection_test.go 2013-09-12 02:07:53 +0000
76@@ -13,6 +13,7 @@
77 gc "launchpad.net/gocheck"
78
79 "launchpad.net/juju-core/testing"
80+ jc "launchpad.net/juju-core/testing/checkers"
81 )
82
83 type detectionSuite struct {
84@@ -34,7 +35,10 @@
85 echo "ERROR: did not match expected input" >&2
86 exit $exitcode
87 fi
88-%s
89+ # stdout
90+ %s
91+ # stderr
92+ %s
93 exit %d
94 else
95 export PATH=${PATH#*:}
96@@ -44,14 +48,25 @@
97 // sshresponse creates a fake "ssh" command in a new $PATH,
98 // updates $PATH, and returns a function to reset $PATH to
99 // its original value when called.
100-func sshresponse(c *gc.C, input, output string, rc int) func() {
101+//
102+// output may be:
103+// - nil (no output)
104+// - a string (stdout)
105+// - a slice of strings, of length two (stdout, stderr)
106+func sshresponse(c *gc.C, input string, output interface{}, rc int) func() {
107 fakebin := c.MkDir()
108 ssh := filepath.Join(fakebin, "ssh")
109 sshexpectedinput := ssh + ".expected-input"
110- if output != "" {
111- output = fmt.Sprintf("cat<<EOF\n%s\nEOF", output)
112+ var stdout, stderr string
113+ switch output := output.(type) {
114+ case nil:
115+ case string:
116+ stdout = fmt.Sprintf("cat<<EOF\n%s\nEOF", output)
117+ case []string:
118+ stdout = fmt.Sprintf("cat<<EOF\n%s\nEOF", output[0])
119+ stderr = fmt.Sprintf("cat>&2<<EOF\n%s\nEOF", output[1])
120 }
121- script := fmt.Sprintf(sshscript, output, rc)
122+ script := fmt.Sprintf(sshscript, stdout, stderr, rc)
123 err := ioutil.WriteFile(ssh, []byte(script), 0777)
124 c.Assert(err, gc.IsNil)
125 err = ioutil.WriteFile(sshexpectedinput, []byte(input), 0644)
126@@ -139,3 +154,27 @@
127 c.Assert(hc.String(), gc.Equals, test.expectedHc)
128 }
129 }
130+
131+func (s *detectionSuite) TestCheckProvisioned(c *gc.C) {
132+ defer sshresponse(c, checkProvisionedScript, "", 0)()
133+ provisioned, err := checkProvisioned("example.com")
134+ c.Assert(err, gc.IsNil)
135+ c.Assert(provisioned, jc.IsFalse)
136+
137+ defer sshresponse(c, checkProvisionedScript, "non-empty", 0)()
138+ provisioned, err = checkProvisioned("example.com")
139+ c.Assert(err, gc.IsNil)
140+ c.Assert(provisioned, jc.IsTrue)
141+
142+ // stderr should not affect result.
143+ defer sshresponse(c, checkProvisionedScript, []string{"", "non-empty-stderr"}, 0)()
144+ provisioned, err = checkProvisioned("example.com")
145+ c.Assert(err, gc.IsNil)
146+ c.Assert(provisioned, jc.IsFalse)
147+
148+ // if the script fails for whatever reason, then checkProvisioned
149+ // will return an error. stderr will be included in the error message.
150+ defer sshresponse(c, checkProvisionedScript, []string{"non-empty-stdout", "non-empty-stderr"}, 255)()
151+ _, err = checkProvisioned("example.com")
152+ c.Assert(err, gc.ErrorMatches, "exit status 255 \\(non-empty-stderr\\)")
153+}
154
155=== added file 'environs/manual/ssh.go'
156--- environs/manual/ssh.go 1970-01-01 00:00:00 +0000
157+++ environs/manual/ssh.go 2013-09-12 02:07:53 +0000
158@@ -0,0 +1,25 @@
159+// Copyright 2013 Canonical Ltd.
160+// Licensed under the AGPLv3, see LICENCE file for details.
161+
162+package manual
163+
164+import (
165+ "os/exec"
166+)
167+
168+type sshOption []string
169+
170+var allocateTTY sshOption = []string{"-t"}
171+
172+// TODO(axw) 2013-09-12 bug #1224230
173+// Move this to a common package for use in cmd/juju, and others.
174+var commonSSHOptions = []string{"-o", "StrictHostKeyChecking no"}
175+
176+func sshCommand(host string, command string, options ...sshOption) *exec.Cmd {
177+ args := append([]string{}, commonSSHOptions...)
178+ for _, option := range options {
179+ args = append(args, option...)
180+ }
181+ args = append(args, host, "--", command)
182+ return exec.Command("ssh", args...)
183+}

Subscribers

People subscribed via source and target branches

to status/vote changes: