Merge lp:~fwereade/pyjuju/go-jujuc into lp:pyjuju/go

Proposed by William Reade
Status: Work in progress
Proposed branch: lp:~fwereade/pyjuju/go-jujuc
Merge into: lp:pyjuju/go
Prerequisite: lp:~fwereade/pyjuju/go-cmd-server-package
Diff against target: 298 lines (+235/-10)
4 files modified
cmd/jujuc/main.go (+61/-0)
cmd/jujuc/main_test.go (+164/-0)
cmd/server/server.go (+8/-8)
cmd/server/server_test.go (+2/-2)
To merge this branch: bzr merge lp:~fwereade/pyjuju/go-jujuc
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+96143@code.launchpad.net

Description of the change

Add cmd/jujuc tool, for calling Commands in a separate host process

prereq: lp:~fwereade/juju/go-cmd-server-package

Should be pretty self-explanatory; only impact on other code is rename of
server.jujucDoc so that jujuc can produce consistent output.

https://codereview.appspot.com/5757049/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Reviewers: mp+96143_code.launchpad.net,

Message:
Please take a look.

Description:
prereq: lp:~fwereade/juju/go-cmd-server-package

Should be pretty self-explanatory; only impact on other code is rename
of
server.jujucDoc so that jujuc can produce consistent output.

https://code.launchpad.net/~fwereade/juju/go-jujuc/+merge/96143

(do not edit description out of merge proposal)

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

Affected files:
   M cmd/command.go
   A cmd/context.go
   A cmd/context_test.go
   M cmd/juju/bootstrap.go
   M cmd/juju/main_test.go
   A cmd/jujuc/main.go
   A cmd/jujuc/main_test.go
   M cmd/jujud/initzk.go
   M cmd/jujud/machine.go
   M cmd/jujud/main_test.go
   M cmd/jujud/provisioning.go
   M cmd/jujud/unit.go
   A cmd/server/server.go
   A cmd/server/server_test.go
   M cmd/supercommand.go
   M cmd/supercommand_test.go

lp:~fwereade/pyjuju/go-jujuc updated
85. By William Reade

merge parent

Revision history for this message
William Reade (fwereade) wrote :
lp:~fwereade/pyjuju/go-jujuc updated
86. By William Reade

merge parent, fix outstanding client id mentions

87. By William Reade

merge parent

88. By William Reade

merge parent

Unmerged revisions

88. By William Reade

merge parent

87. By William Reade

merge parent

86. By William Reade

merge parent, fix outstanding client id mentions

85. By William Reade

merge parent

84. By William Reade

add jujuc command

83. By William Reade

add cmd/server package

82. By William Reade

anticipatory SuperCommand tweaks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'cmd/jujuc'
2=== added file 'cmd/jujuc/main.go'
3--- cmd/jujuc/main.go 1970-01-01 00:00:00 +0000
4+++ cmd/jujuc/main.go 2012-03-13 10:27:18 +0000
5@@ -0,0 +1,61 @@
6+package main
7+
8+import (
9+ "fmt"
10+ "launchpad.net/juju/go/cmd/server"
11+ "net/rpc"
12+ "os"
13+ "path/filepath"
14+)
15+
16+func die(err error) {
17+ fmt.Fprintf(os.Stderr, "fatal: %v\n", err)
18+ fmt.Fprintf(os.Stderr, server.JUJUC_DOC)
19+ os.Exit(1)
20+}
21+
22+func requireEnv(name string) string {
23+ value := os.Getenv(name)
24+ if value == "" {
25+ die(fmt.Errorf("%s not set", name))
26+ }
27+ return value
28+}
29+
30+func requireWd() string {
31+ dir, err := os.Getwd()
32+ if err != nil {
33+ die(err)
34+ }
35+ abs, err := filepath.Abs(dir)
36+ if err != nil {
37+ die(err)
38+ }
39+ return abs
40+}
41+
42+// Main uses JUJU_CONTEXT_ID and JUJU_AGENT_SOCKET to ask a running unit agent to
43+// execute a Command on our behalf. This function is not redundant with main,
44+// because it provides an entry point for testing with arbitrary command line
45+// arguments. Individual commands should be exposed by symlinking the command
46+// name to this executable.
47+func Main(args []string) {
48+ req := server.Request{requireEnv("JUJU_CONTEXT_ID"), requireWd(), args}
49+ client, err := rpc.Dial("unix", requireEnv("JUJU_AGENT_SOCKET"))
50+ if err != nil {
51+ die(err)
52+ }
53+ defer client.Close()
54+ var resp server.Response
55+ err = client.Call("RPCcmd.Main", req, &resp)
56+ if err != nil {
57+ die(err)
58+ }
59+ os.Stdout.Write([]byte(resp.Stdout))
60+ os.Stderr.Write([]byte(resp.Stderr))
61+ os.Exit(resp.Code)
62+}
63+
64+func main() {
65+ Main(os.Args)
66+}
67
68=== added file 'cmd/jujuc/main_test.go'
69--- cmd/jujuc/main_test.go 1970-01-01 00:00:00 +0000
70+++ cmd/jujuc/main_test.go 2012-03-13 10:27:18 +0000
71@@ -0,0 +1,164 @@
72+package main_test
73+
74+import (
75+ "errors"
76+ "flag"
77+ "fmt"
78+ "launchpad.net/gnuflag"
79+ . "launchpad.net/gocheck"
80+ "launchpad.net/juju/go/cmd"
81+ main "launchpad.net/juju/go/cmd/jujuc"
82+ "launchpad.net/juju/go/cmd/server"
83+ "os"
84+ "os/exec"
85+ "path/filepath"
86+ "strings"
87+ "testing"
88+)
89+
90+func Test(t *testing.T) { TestingT(t) }
91+
92+var flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing")
93+
94+// Reentrancy point for testing (something as close as possible to) the jujuc
95+// tool itself.
96+func TestRunMain(t *testing.T) {
97+ if *flagRunMain {
98+ main.Main(flag.Args())
99+ }
100+}
101+
102+type RemoteCommand struct {
103+ msg string
104+}
105+
106+var expectUsage = `usage: (-> jujuc) remote [options]
107+purpose: test jujuc
108+
109+options:
110+--error (= "")
111+ if set, fail
112+
113+here is some documentation
114+`
115+
116+func (c *RemoteCommand) Info() *cmd.Info {
117+ return &cmd.Info{
118+ "remote", "[options]", "test jujuc", "here is some documentation", true}
119+}
120+
121+func (c *RemoteCommand) InitFlagSet(f *gnuflag.FlagSet) {
122+ f.StringVar(&c.msg, "error", "", "if set, fail")
123+}
124+
125+func (c *RemoteCommand) ParsePositional(args []string) error {
126+ return cmd.CheckEmpty(args)
127+}
128+
129+func (c *RemoteCommand) Run(ctx *cmd.Context) error {
130+ if c.msg != "" {
131+ return errors.New(c.msg)
132+ }
133+ fmt.Fprintf(ctx.Stdout, "success!\n")
134+ return nil
135+}
136+
137+func run(c *C, sockPath string, contextId string, exit int, cmd ...string) string {
138+ args := append([]string{"-test.run", "TestRunMain", "-run-main", "--"}, cmd...)
139+ ps := exec.Command(os.Args[0], args...)
140+ ps.Dir = c.MkDir()
141+ ps.Env = []string{
142+ fmt.Sprintf("JUJU_AGENT_SOCKET=%s", sockPath),
143+ fmt.Sprintf("JUJU_CONTEXT_ID=%s", contextId),
144+ }
145+ output, err := ps.CombinedOutput()
146+ if exit == 0 {
147+ c.Assert(err, IsNil)
148+ } else {
149+ c.Assert(err, ErrorMatches, fmt.Sprintf("exit status %d", exit))
150+ }
151+ return string(output)
152+}
153+
154+type MainSuite struct {
155+ sockPath string
156+ server *server.Server
157+}
158+
159+var _ = Suite(&MainSuite{})
160+
161+func (s *MainSuite) SetUpSuite(c *C) {
162+ factory := func(contextId string) ([]cmd.Command, error) {
163+ if contextId != "bill" {
164+ return nil, fmt.Errorf("bad context: %s", contextId)
165+ }
166+ return []cmd.Command{&RemoteCommand{}}, nil
167+ }
168+ s.sockPath = filepath.Join(c.MkDir(), "test.sock")
169+ srv, err := server.NewServer(factory, s.sockPath)
170+ c.Assert(err, IsNil)
171+ s.server = srv
172+}
173+
174+func (s *MainSuite) TearDownSuite(c *C) {
175+ c.Assert(s.server.Close(), IsNil)
176+}
177+
178+func (s *MainSuite) TestHappyPath(c *C) {
179+ output := run(c, s.sockPath, "bill", 0, "remote")
180+ c.Assert(output, Equals, "success!\n")
181+}
182+
183+func (s *MainSuite) TestBadRun(c *C) {
184+ output := run(c, s.sockPath, "bill", 1, "remote", "--error", "borken")
185+ c.Assert(output, Equals, "borken\n")
186+}
187+
188+func (s *MainSuite) TestBadFlag(c *C) {
189+ output := run(c, s.sockPath, "bill", 2, "remote", "--unknown")
190+ c.Assert(output, Equals, "flag provided but not defined: --unknown\n"+expectUsage)
191+}
192+
193+func (s *MainSuite) TestBadArg(c *C) {
194+ output := run(c, s.sockPath, "bill", 2, "remote", "unwanted")
195+ c.Assert(output, Equals, "unrecognised args: [unwanted]\n"+expectUsage)
196+}
197+
198+func (s *MainSuite) TestBadCommand(c *C) {
199+ output := run(c, s.sockPath, "bill", 2, "unknown")
200+ lines := strings.Split(output, "\n")
201+ c.Assert(lines[:3], DeepEquals, []string{
202+ "unrecognised command: (-> jujuc) unknown",
203+ "usage: (-> jujuc) <command> [options] ...",
204+ "purpose: invoke a command implemented by another process",
205+ })
206+ c.Assert(lines[len(lines)-3:], DeepEquals, []string{
207+ "commands:",
208+ " remote test jujuc",
209+ "",
210+ })
211+}
212+
213+func AssertOutput(c *C, actual, expected string) {
214+ c.Assert(actual, Matches, expected+server.JUJUC_DOC)
215+}
216+
217+func (s *MainSuite) TestNoSockPath(c *C) {
218+ output := run(c, "", "bill", 1, "remote")
219+ AssertOutput(c, output, "fatal: JUJU_AGENT_SOCKET not set\n")
220+}
221+
222+func (s *MainSuite) TestBadSockPath(c *C) {
223+ output := run(c, filepath.Join(c.MkDir(), "bad.sock"), "bill", 1, "remote")
224+ AssertOutput(c, output, "fatal: dial unix .*: no such file or directory\n")
225+}
226+
227+func (s *MainSuite) TestNoClientId(c *C) {
228+ output := run(c, s.sockPath, "", 1, "remote")
229+ AssertOutput(c, output, "fatal: JUJU_CONTEXT_ID not set\n")
230+}
231+
232+func (s *MainSuite) TestBadClientId(c *C) {
233+ output := run(c, s.sockPath, "ben", 1, "remote")
234+ AssertOutput(c, output, "fatal: bad request: bad context: ben\n")
235+}
236
237=== modified file 'cmd/server/server.go'
238--- cmd/server/server.go 2012-03-13 10:27:18 +0000
239+++ cmd/server/server.go 2012-03-13 10:27:18 +0000
240@@ -16,6 +16,13 @@
241 "path/filepath"
242 )
243
244+var JUJUC_DOC = `
245+The jujuc command forwards invocations over RPC for execution by another
246+process. It expects to be called via a symlink named for the desired remote
247+command, and requires that JUJU_AGENT_SOCKET and JUJU_CONTEXT_ID be set in
248+its environment.
249+`
250+
251 // Request contains the information necessary to run a Command whose view of the
252 // world is filtered according to its ContextId.
253 type Request struct {
254@@ -90,13 +97,6 @@
255 factory CmdFactory
256 }
257
258-var jujucDoc = `
259-The jujuc command forwards invocations over RPC for execution by another
260-process. It expects to be called via a symlink named for the desired remote
261-command, and requires that JUJU_AGENT_SOCKET and JUJU_CONTEXT_ID be set in
262-its environment.
263-`
264-
265 // command returns a cmd.SuperCommand which is responsible for selecting one
266 // of the Commands produced by calling rpcc.factory.
267 func (rpcc *RPCcmd) command(contextId string) (cmd.Command, error) {
268@@ -104,7 +104,7 @@
269 if err != nil {
270 return nil, err
271 }
272- sc := cmd.NewSuperCommand("(-> jujuc)", jujucDoc)
273+ sc := cmd.NewSuperCommand("(-> jujuc)", JUJUC_DOC)
274 sc.SetsLog = false
275 sc.Purpose = "invoke a command implemented by another process"
276 for _, c := range cmds {
277
278=== modified file 'cmd/server/server_test.go'
279--- cmd/server/server_test.go 2012-03-13 10:27:18 +0000
280+++ cmd/server/server_test.go 2012-03-13 10:27:18 +0000
281@@ -43,7 +43,7 @@
282
283 func factory(contextId string) ([]cmd.Command, error) {
284 if contextId != "merlin" {
285- return nil, errors.New("unknown client")
286+ return nil, errors.New("unknown context")
287 }
288 return []cmd.Command{&RpcCommand{}}, nil
289 }
290@@ -114,7 +114,7 @@
291
292 func (s *ServerSuite) TestBadContextId(c *C) {
293 _, err := s.Call(c, server.Request{"mordred", c.MkDir(), []string{"magic"}})
294- c.Assert(err, ErrorMatches, "bad request: unknown client")
295+ c.Assert(err, ErrorMatches, "bad request: unknown context")
296 }
297
298 func (s *ServerSuite) AssertBadCommand(c *C, args []string, code int) server.Response {

Subscribers

People subscribed via source and target branches