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
=== modified file 'worker/uniter/context.go'
--- worker/uniter/context.go 2013-12-13 02:58:09 +0000
+++ worker/uniter/context.go 2013-12-13 02:58:09 +0000
@@ -5,6 +5,7 @@
55
6import (6import (
7 "bufio"7 "bufio"
8 "bytes"
8 "fmt"9 "fmt"
9 "io"10 "io"
10 "os"11 "os"
@@ -13,9 +14,11 @@
13 "sort"14 "sort"
14 "strings"15 "strings"
15 "sync"16 "sync"
17 "syscall"
16 "time"18 "time"
1719
18 "launchpad.net/juju-core/charm"20 "launchpad.net/juju-core/charm"
21 "launchpad.net/juju-core/cmd"
19 "launchpad.net/juju-core/state/api/params"22 "launchpad.net/juju-core/state/api/params"
20 "launchpad.net/juju-core/state/api/uniter"23 "launchpad.net/juju-core/state/api/uniter"
21 unitdebug "launchpad.net/juju-core/worker/uniter/debug"24 unitdebug "launchpad.net/juju-core/worker/uniter/debug"
@@ -207,8 +210,16 @@
207 return err210 return err
208}211}
209212
213// RunCommands executes the commands in an environment which allows it to to
214// call back into the hook context to execute jujuc tools.
215func (ctx *HookContext) RunCommands(commands, charmDir, toolsDir, socketPath string) (*cmd.RemoteResponse, error) {
216 env := ctx.hookVars(charmDir, toolsDir, socketPath)
217 result, err := runCommands(commands, charmDir, env)
218 return result, ctx.finalizeContext("run commands", err)
219}
220
210// RunHook executes a hook in an environment which allows it to to call back221// RunHook executes a hook in an environment which allows it to to call back
211// into ctx to execute jujuc tools.222// into the hook context to execute jujuc tools.
212func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error {223func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error {
213 var err error224 var err error
214 env := ctx.hookVars(charmDir, toolsDir, socketPath)225 env := ctx.hookVars(charmDir, toolsDir, socketPath)
@@ -253,6 +264,38 @@
253 return err264 return err
254}265}
255266
267func runCommands(commands, charmDir string, env []string) (*cmd.RemoteResponse, error) {
268 ps := exec.Command("/bin/bash", "-s")
269 ps.Env = env
270 ps.Dir = charmDir
271 ps.Stdin = bytes.NewBufferString(commands)
272
273 stdout := &bytes.Buffer{}
274 stderr := &bytes.Buffer{}
275
276 ps.Stdout = stdout
277 ps.Stderr = stderr
278
279 err := ps.Start()
280 if err == nil {
281 err = ps.Wait()
282 }
283 result := &cmd.RemoteResponse{
284 Stdout: stdout.Bytes(),
285 Stderr: stderr.Bytes(),
286 }
287 if ee, ok := err.(*exec.ExitError); ok && err != nil {
288 status := ee.ProcessState.Sys().(syscall.WaitStatus)
289 if status.Exited() {
290 // A non-zero return code isn't considered an error here.
291 result.Code = status.ExitStatus()
292 err = nil
293 }
294 logger.Infof("run result: %v", ee)
295 }
296 return result, err
297}
298
256type hookLogger struct {299type hookLogger struct {
257 r io.ReadCloser300 r io.ReadCloser
258 done chan struct{}301 done chan struct{}
259302
=== modified file 'worker/uniter/context_test.go'
--- worker/uniter/context_test.go 2013-12-13 02:58:09 +0000
+++ worker/uniter/context_test.go 2013-12-13 02:58:09 +0000
@@ -208,7 +208,7 @@
208 c.Assert(err, gc.IsNil)208 c.Assert(err, gc.IsNil)
209 for i, t := range runHookTests {209 for i, t := range runHookTests {
210 c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)210 c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)
211 ctx := s.GetHookContext(c, uuid.String(), t.relid, t.remote)211 ctx := s.getHookContext(c, uuid.String(), t.relid, t.remote)
212 var charmDir, outPath string212 var charmDir, outPath string
213 var hookExists bool213 var hookExists bool
214 if t.spec.perm == 0 {214 if t.spec.perm == 0 {
@@ -260,7 +260,7 @@
260 // Create a charm with a breaking hook.260 // Create a charm with a breaking hook.
261 uuid, err := utils.NewUUID()261 uuid, err := utils.NewUUID()
262 c.Assert(err, gc.IsNil)262 c.Assert(err, gc.IsNil)
263 ctx := s.GetHookContext(c, uuid.String(), -1, "")263 ctx := s.getHookContext(c, uuid.String(), -1, "")
264 charmDir, _ := makeCharm(c, hookSpec{264 charmDir, _ := makeCharm(c, hookSpec{
265 name: "something-happened",265 name: "something-happened",
266 perm: 0700,266 perm: 0700,
@@ -551,7 +551,7 @@
551 remoteName string) jujuc.Context {551 remoteName string) jujuc.Context {
552 uuid, err := utils.NewUUID()552 uuid, err := utils.NewUUID()
553 c.Assert(err, gc.IsNil)553 c.Assert(err, gc.IsNil)
554 return s.HookContextSuite.GetHookContext(c, uuid.String(), relId, remoteName)554 return s.HookContextSuite.getHookContext(c, uuid.String(), relId, remoteName)
555}555}
556556
557func (s *InterfaceSuite) TestUtils(c *gc.C) {557func (s *InterfaceSuite) TestUtils(c *gc.C) {
@@ -696,7 +696,7 @@
696 s.relctxs[rel.Id()] = uniter.NewContextRelation(apiRelUnit, nil)696 s.relctxs[rel.Id()] = uniter.NewContextRelation(apiRelUnit, nil)
697}697}
698698
699func (s *HookContextSuite) GetHookContext(c *gc.C, uuid string, relid int,699func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int,
700 remote string) *uniter.HookContext {700 remote string) *uniter.HookContext {
701 if relid != -1 {701 if relid != -1 {
702 _, found := s.relctxs[relid]702 _, found := s.relctxs[relid]
@@ -723,3 +723,57 @@
723 }723 }
724 return result724 return result
725}725}
726
727type RunCommandSuite struct {
728 HookContextSuite
729}
730
731var _ = gc.Suite(&RunCommandSuite{})
732
733func (s *RunCommandSuite) getHookContext(c *gc.C) *uniter.HookContext {
734 uuid, err := utils.NewUUID()
735 c.Assert(err, gc.IsNil)
736 return s.HookContextSuite.getHookContext(c, uuid.String(), -1, "")
737}
738
739func (s *RunCommandSuite) TestRunCommandsHasEnvironSet(c *gc.C) {
740 context := s.getHookContext(c)
741 charmDir := c.MkDir()
742 result, err := context.RunCommands("env | sort", charmDir, "/path/to/tools", "/path/to/socket")
743 c.Assert(err, gc.IsNil)
744
745 executionEnvironment := map[string]string{}
746 for _, value := range strings.Split(string(result.Stdout), "\n") {
747 bits := strings.SplitN(value, "=", 2)
748 if len(bits) == 2 {
749 executionEnvironment[bits[0]] = bits[1]
750 }
751 }
752 expected := map[string]string{
753 "APT_LISTCHANGES_FRONTEND": "none",
754 "DEBIAN_FRONTEND": "noninteractive",
755 "CHARM_DIR": charmDir,
756 "JUJU_CONTEXT_ID": "TestCtx",
757 "JUJU_AGENT_SOCKET": "/path/to/socket",
758 "JUJU_UNIT_NAME": "u/0",
759 }
760 for key, value := range expected {
761 c.Check(executionEnvironment[key], gc.Equals, value)
762 }
763}
764
765func (s *RunCommandSuite) TestRunCommandsStdOutAndErrAndRC(c *gc.C) {
766 context := s.getHookContext(c)
767 charmDir := c.MkDir()
768 commands := `
769echo this is standard out
770echo this is standard err >&2
771exit 42
772`
773 result, err := context.RunCommands(commands, charmDir, "/path/to/tools", "/path/to/socket")
774 c.Assert(err, gc.IsNil)
775
776 c.Assert(result.Code, gc.Equals, 42)
777 c.Assert(string(result.Stdout), gc.Equals, "this is standard out\n")
778 c.Assert(string(result.Stderr), gc.Equals, "this is standard err\n")
779}

Subscribers

People subscribed via source and target branches

to status/vote changes: