Merge lp:~thumper/juju-core/uniter-context-run-commands into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 2143
Proposed branch: lp:~thumper/juju-core/uniter-context-run-commands
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~thumper/juju-core/refactor-uniter-tests
Diff against target: 178 lines (+102/-5)
2 files modified
worker/uniter/context.go (+44/-1)
worker/uniter/context_test.go (+58/-4)
To merge this branch: bzr merge lp:~thumper/juju-core/uniter-context-run-commands
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+198671@code.launchpad.net

Commit message

Uniter context run commands.

The uniter.Context gains the ability to run arbitrary commands in a hook
context.

Execute the commands by passing as stdin to "/bin/bash -s".

https://codereview.appspot.com/40370047/

Description of the change

Uniter context run commands.

The uniter.Context gains the ability to run arbitrary commands in a hook
context.

Execute the commands by passing as stdin to "/bin/bash -s".

https://codereview.appspot.com/40370047/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (5.0 KiB)

Reviewers: mp+198671_code.launchpad.net,

Message:
Please take a look.

Description:
Uniter context run commands.

The uniter.Context gains the ability to run arbitrary commands in a hook
context.

Execute the commands by passing as stdin to "/bin/bash -s".

https://code.launchpad.net/~thumper/juju-core/uniter-context-run-commands/+merge/198671

Requires:
https://code.launchpad.net/~thumper/juju-core/refactor-uniter-tests/+merge/198668

(do not edit description out of merge proposal)

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

Affected files (+99, -0 lines):
   A [revision details]
   M worker/uniter/context.go
   M worker/uniter/context_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: <email address hidden>
+New revision: <email address hidden>

Index: worker/uniter/context.go
=== modified file 'worker/uniter/context.go'
--- worker/uniter/context.go 2013-12-12 05:18:00 +0000
+++ worker/uniter/context.go 2013-12-12 06:14:41 +0000
@@ -5,6 +5,7 @@

  import (
   "bufio"
+ "bytes"
   "fmt"
   "io"
   "os"
@@ -13,9 +14,11 @@
   "sort"
   "strings"
   "sync"
+ "syscall"
   "time"

   "launchpad.net/juju-core/charm"
+ "launchpad.net/juju-core/cmd"
   "launchpad.net/juju-core/state/api/params"
   "launchpad.net/juju-core/state/api/uniter"
   unitdebug "launchpad.net/juju-core/worker/uniter/debug"
@@ -209,6 +212,14 @@

  // RunHook executes a hook in an environment which allows it to to call
back
  // into ctx to execute jujuc tools.
+func (ctx *HookContext) RunCommands(commands, charmDir, toolsDir,
socketPath string) (*cmd.RemoteResponse, error) {
+ env := ctx.hookVars(charmDir, toolsDir, socketPath)
+ result, err := runCommands(commands, charmDir, env)
+ return result, ctx.finalizeContext("run commands", err)
+}
+
+// RunHook executes a hook in an environment which allows it to to call
back
+// into ctx to execute jujuc tools.
  func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath
string) error {
   var err error
   env := ctx.hookVars(charmDir, toolsDir, socketPath)
@@ -253,6 +264,38 @@
   return err
  }

+func runCommands(commands, charmDir string, env []string)
(*cmd.RemoteResponse, error) {
+ ps := exec.Command("/bin/bash", "-s")
+ ps.Env = env
+ ps.Dir = charmDir
+ ps.Stdin = bytes.NewBufferString(commands)
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ ps.Stdout = stdout
+ ps.Stderr = stderr
+
+ err := ps.Start()
+ if err == nil {
+ err = ps.Wait()
+ }
+ result := &cmd.RemoteResponse{
+ Stdout: stdout.Bytes(),
+ Stderr: stderr.Bytes(),
+ }
+ if ee, ok := err.(*exec.ExitError); ok && err != nil {
+ status := ee.ProcessState.Sys().(syscall.WaitStatus)
+ if status.Exited() {
+ // A non-zero return code isn't considered an error here.
+ result.Code = status.ExitStatus()
+ err = nil
+ }
+ logger.Infof("run result: %v", ee)
+ }
+ return result, err
+}
+
  type hookLogger struct {
   r io.ReadCloser
   done chan struct{}

Index: worker/uniter/co...

Read more...

Revision history for this message
Ian Booth (wallyworld) wrote :

LGTM with a couple of fixes

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context.go
File worker/uniter/context.go (right):

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context.go#newcode214
worker/uniter/context.go:214: // into ctx to execute jujuc tools.
Doc string is out of date

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context_test.go
File worker/uniter/context_test.go (right):

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context_test.go#newcode733
worker/uniter/context_test.go:733: func (s *RunCommandSuite)
GetHookContext(c *gc.C) *uniter.HookContext {
this doesn't need to be exported

https://codereview.appspot.com/40370047/

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

Please take a look.

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context.go
File worker/uniter/context.go (right):

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context.go#newcode214
worker/uniter/context.go:214: // into ctx to execute jujuc tools.
On 2013/12/13 00:32:03, wallyworld wrote:
> Doc string is out of date

Done.

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context_test.go
File worker/uniter/context_test.go (right):

https://codereview.appspot.com/40370047/diff/1/worker/uniter/context_test.go#newcode733
worker/uniter/context_test.go:733: func (s *RunCommandSuite)
GetHookContext(c *gc.C) *uniter.HookContext {
On 2013/12/13 00:32:03, wallyworld wrote:
> this doesn't need to be exported

The height of pedentry :P

Unexported.

https://codereview.appspot.com/40370047/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'worker/uniter/context.go'
2--- worker/uniter/context.go 2013-12-13 02:58:09 +0000
3+++ worker/uniter/context.go 2013-12-13 02:58:09 +0000
4@@ -5,6 +5,7 @@
5
6 import (
7 "bufio"
8+ "bytes"
9 "fmt"
10 "io"
11 "os"
12@@ -13,9 +14,11 @@
13 "sort"
14 "strings"
15 "sync"
16+ "syscall"
17 "time"
18
19 "launchpad.net/juju-core/charm"
20+ "launchpad.net/juju-core/cmd"
21 "launchpad.net/juju-core/state/api/params"
22 "launchpad.net/juju-core/state/api/uniter"
23 unitdebug "launchpad.net/juju-core/worker/uniter/debug"
24@@ -207,8 +210,16 @@
25 return err
26 }
27
28+// RunCommands executes the commands in an environment which allows it to to
29+// call back into the hook context to execute jujuc tools.
30+func (ctx *HookContext) RunCommands(commands, charmDir, toolsDir, socketPath string) (*cmd.RemoteResponse, error) {
31+ env := ctx.hookVars(charmDir, toolsDir, socketPath)
32+ result, err := runCommands(commands, charmDir, env)
33+ return result, ctx.finalizeContext("run commands", err)
34+}
35+
36 // RunHook executes a hook in an environment which allows it to to call back
37-// into ctx to execute jujuc tools.
38+// into the hook context to execute jujuc tools.
39 func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error {
40 var err error
41 env := ctx.hookVars(charmDir, toolsDir, socketPath)
42@@ -253,6 +264,38 @@
43 return err
44 }
45
46+func runCommands(commands, charmDir string, env []string) (*cmd.RemoteResponse, error) {
47+ ps := exec.Command("/bin/bash", "-s")
48+ ps.Env = env
49+ ps.Dir = charmDir
50+ ps.Stdin = bytes.NewBufferString(commands)
51+
52+ stdout := &bytes.Buffer{}
53+ stderr := &bytes.Buffer{}
54+
55+ ps.Stdout = stdout
56+ ps.Stderr = stderr
57+
58+ err := ps.Start()
59+ if err == nil {
60+ err = ps.Wait()
61+ }
62+ result := &cmd.RemoteResponse{
63+ Stdout: stdout.Bytes(),
64+ Stderr: stderr.Bytes(),
65+ }
66+ if ee, ok := err.(*exec.ExitError); ok && err != nil {
67+ status := ee.ProcessState.Sys().(syscall.WaitStatus)
68+ if status.Exited() {
69+ // A non-zero return code isn't considered an error here.
70+ result.Code = status.ExitStatus()
71+ err = nil
72+ }
73+ logger.Infof("run result: %v", ee)
74+ }
75+ return result, err
76+}
77+
78 type hookLogger struct {
79 r io.ReadCloser
80 done chan struct{}
81
82=== modified file 'worker/uniter/context_test.go'
83--- worker/uniter/context_test.go 2013-12-13 02:58:09 +0000
84+++ worker/uniter/context_test.go 2013-12-13 02:58:09 +0000
85@@ -208,7 +208,7 @@
86 c.Assert(err, gc.IsNil)
87 for i, t := range runHookTests {
88 c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)
89- ctx := s.GetHookContext(c, uuid.String(), t.relid, t.remote)
90+ ctx := s.getHookContext(c, uuid.String(), t.relid, t.remote)
91 var charmDir, outPath string
92 var hookExists bool
93 if t.spec.perm == 0 {
94@@ -260,7 +260,7 @@
95 // Create a charm with a breaking hook.
96 uuid, err := utils.NewUUID()
97 c.Assert(err, gc.IsNil)
98- ctx := s.GetHookContext(c, uuid.String(), -1, "")
99+ ctx := s.getHookContext(c, uuid.String(), -1, "")
100 charmDir, _ := makeCharm(c, hookSpec{
101 name: "something-happened",
102 perm: 0700,
103@@ -551,7 +551,7 @@
104 remoteName string) jujuc.Context {
105 uuid, err := utils.NewUUID()
106 c.Assert(err, gc.IsNil)
107- return s.HookContextSuite.GetHookContext(c, uuid.String(), relId, remoteName)
108+ return s.HookContextSuite.getHookContext(c, uuid.String(), relId, remoteName)
109 }
110
111 func (s *InterfaceSuite) TestUtils(c *gc.C) {
112@@ -696,7 +696,7 @@
113 s.relctxs[rel.Id()] = uniter.NewContextRelation(apiRelUnit, nil)
114 }
115
116-func (s *HookContextSuite) GetHookContext(c *gc.C, uuid string, relid int,
117+func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int,
118 remote string) *uniter.HookContext {
119 if relid != -1 {
120 _, found := s.relctxs[relid]
121@@ -723,3 +723,57 @@
122 }
123 return result
124 }
125+
126+type RunCommandSuite struct {
127+ HookContextSuite
128+}
129+
130+var _ = gc.Suite(&RunCommandSuite{})
131+
132+func (s *RunCommandSuite) getHookContext(c *gc.C) *uniter.HookContext {
133+ uuid, err := utils.NewUUID()
134+ c.Assert(err, gc.IsNil)
135+ return s.HookContextSuite.getHookContext(c, uuid.String(), -1, "")
136+}
137+
138+func (s *RunCommandSuite) TestRunCommandsHasEnvironSet(c *gc.C) {
139+ context := s.getHookContext(c)
140+ charmDir := c.MkDir()
141+ result, err := context.RunCommands("env | sort", charmDir, "/path/to/tools", "/path/to/socket")
142+ c.Assert(err, gc.IsNil)
143+
144+ executionEnvironment := map[string]string{}
145+ for _, value := range strings.Split(string(result.Stdout), "\n") {
146+ bits := strings.SplitN(value, "=", 2)
147+ if len(bits) == 2 {
148+ executionEnvironment[bits[0]] = bits[1]
149+ }
150+ }
151+ expected := map[string]string{
152+ "APT_LISTCHANGES_FRONTEND": "none",
153+ "DEBIAN_FRONTEND": "noninteractive",
154+ "CHARM_DIR": charmDir,
155+ "JUJU_CONTEXT_ID": "TestCtx",
156+ "JUJU_AGENT_SOCKET": "/path/to/socket",
157+ "JUJU_UNIT_NAME": "u/0",
158+ }
159+ for key, value := range expected {
160+ c.Check(executionEnvironment[key], gc.Equals, value)
161+ }
162+}
163+
164+func (s *RunCommandSuite) TestRunCommandsStdOutAndErrAndRC(c *gc.C) {
165+ context := s.getHookContext(c)
166+ charmDir := c.MkDir()
167+ commands := `
168+echo this is standard out
169+echo this is standard err >&2
170+exit 42
171+`
172+ result, err := context.RunCommands(commands, charmDir, "/path/to/tools", "/path/to/socket")
173+ c.Assert(err, gc.IsNil)
174+
175+ c.Assert(result.Code, gc.Equals, 42)
176+ c.Assert(string(result.Stdout), gc.Equals, "this is standard out\n")
177+ c.Assert(string(result.Stderr), gc.Equals, "this is standard err\n")
178+}

Subscribers

People subscribed via source and target branches

to status/vote changes: