// 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
}
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: /code.launchpad .net/~thumper/ juju-core/ refactor- uniter- tests/+ merge/198668
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/40370047/
Affected files (+99, -0 lines): uniter/ context. go uniter/ context_ test.go
A [revision details]
M worker/
M worker/
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 uniter/ context. go' uniter/ context. go 2013-12-12 05:18:00 +0000 uniter/ context. go 2013-12-12 06:14:41 +0000
=== modified file 'worker/
--- worker/
+++ worker/
@@ -5,6 +5,7 @@
import (
"bufio"
+ "bytes"
"fmt"
"io"
"os"
@@ -13,9 +14,11 @@
"sort"
"strings"
"sync"
+ "syscall"
"time"
"launchpad. net/juju- core/charm" net/juju- core/cmd" net/juju- core/state/ api/params" net/juju- core/state/ api/uniter" net/juju- core/worker/ uniter/ debug"
+ "launchpad.
"launchpad.
"launchpad.
unitdebug "launchpad.
@@ -209,6 +212,14 @@
// RunHook executes a hook in an environment which allows it to to call commands, charmDir, toolsDir, ponse, error) { charmDir, toolsDir, socketPath) commands, charmDir, env) text("run commands", err) charmDir, toolsDir, socketPath)
back
// into ctx to execute jujuc tools.
+func (ctx *HookContext) RunCommands(
socketPath string) (*cmd.RemoteRes
+ env := ctx.hookVars(
+ result, err := runCommands(
+ return result, ctx.finalizeCon
+}
+
+// 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(
@@ -253,6 +264,38 @@
return err
}
+func runCommands( commands, charmDir string, env []string) ponse, error) { "/bin/bash" , "-s") String( commands) onse{ ExitError) ; ok && err != nil { .Sys(). (syscall. WaitStatus)
(*cmd.RemoteRes
+ ps := exec.Command(
+ ps.Env = env
+ ps.Dir = charmDir
+ ps.Stdin = bytes.NewBuffer
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ ps.Stdout = stdout
+ ps.Stderr = stderr
+
+ err := ps.Start()
+ if err == nil {
+ err = ps.Wait()
+ }
+ result := &cmd.RemoteResp
+ Stdout: stdout.Bytes(),
+ Stderr: stderr.Bytes(),
+ }
+ if ee, ok := err.(*exec.
+ status := ee.ProcessState
+ 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/ context_ test.go uniter/ context_ test.go' uniter/ context_ test.go 2013-12-12 05:18:00 +0000 uniter/ context_ test.go 2013-12-12 06:14:41 +0000 &RunCommandSuit e{}) ite.GetHookCont ext(c, uuid.String(), -1, "") HasEnvironSet( c *gc.C) { RunCommands( "env | sort", nment := map[string]string{} Split(string( result. Stdout) , "\n") { SplitN( value, "=", 2) nment[bits[ 0]] = bits[1] S_FRONTEND" : "none", SOCKET" : "/path/to/socket", executionEnviro nment[key] , gc.Equals, value) StdOutAndErrAnd RC(c *gc.C) { RunCommands( commands, result. Code, gc.Equals, 42) string( result. Stdout) , gc.Equals, "this is standard out\n") string( result. Stderr) , gc.Equals, "this is standard err\n")
=== modified file 'worker/
--- worker/
+++ worker/
@@ -723,3 +723,57 @@
}
return result
}
+
+type RunCommandSuite struct {
+ HookContextSuite
+}
+
+var _ = gc.Suite(
+
+func (s *RunCommandSuite) GetHookContext(c *gc.C) *uniter.HookContext {
+ uuid, err := utils.NewUUID()
+ c.Assert(err, gc.IsNil)
+ return s.HookContextSu
+}
+
+func (s *RunCommandSuite) TestRunCommands
+ context := s.GetHookContext(c)
+ charmDir := c.MkDir()
+ result, err := context.
charmDir, "/path/to/tools", "/path/to/socket")
+ c.Assert(err, gc.IsNil)
+
+ executionEnviro
+ for _, value := range strings.
+ bits := strings.
+ if len(bits) == 2 {
+ executionEnviro
+ }
+ }
+ expected := map[string]string{
+ "APT_LISTCHANGE
+ "DEBIAN_FRONTEND": "noninteractive",
+ "CHARM_DIR": charmDir,
+ "JUJU_CONTEXT_ID": "TestCtx",
+ "JUJU_AGENT_
+ "JUJU_UNIT_NAME": "u/0",
+ }
+ for key, value := range expected {
+ c.Check(
+ }
+}
+
+func (s *RunCommandSuite) TestRunCommands
+ context := s.GetHookContext(c)
+ charmDir := c.MkDir()
+ commands := `
+echo this is standard out
+echo this is standard err >&2
+exit 42
+`
+ result, err := context.
charmDir, "/path/to/tools", "/path/to/socket")
+ c.Assert(err, gc.IsNil)
+
+ c.Assert(
+ c.Assert(
+ c.Assert(
+}