diff --git a/packer/command.go b/packer/command.go index a10246cf3..4f4f9ae35 100644 --- a/packer/command.go +++ b/packer/command.go @@ -1,7 +1,5 @@ package packer -import "net/rpc" - // A command is a runnable sub-command of the `packer` application. // When `packer` is called with the proper subcommand, this will be // called. @@ -19,44 +17,3 @@ type Command interface { Run(env Environment, args []string) int Synopsis() string } - -// An RPCCommand is an implementation of the Command interface where the -// command is actually executed over an RPC connection. -type RPCClientCommand struct { - client *rpc.Client -} - -// An RPCServerCommand wraps a Command and makes it exportable as part -// of a Golang RPC server. -type RPCServerCommand struct { - command Command -} - -type RPCCommandRunArgs struct { - Env Environment - Args []string -} - -type RPCCommandSynopsisArgs byte - -func (c *RPCClientCommand) Run(env Environment, args []string) (result int) { - // TODO: Environment - rpcArgs := &RPCCommandRunArgs{nil, args} - c.client.Call("Command.Run", rpcArgs, &result) - return -} - -func (c *RPCClientCommand) Synopsis() (result string) { - c.client.Call("Command.Synopsis", RPCCommandSynopsisArgs(0), &result) - return -} - -func (c *RPCServerCommand) Run(args *RPCCommandRunArgs, reply *int) error { - *reply = c.command.Run(args.Env, args.Args) - return nil -} - -func (c *RPCServerCommand) Synopsis(args *RPCCommandSynopsisArgs, reply *string) error { - *reply = c.command.Synopsis() - return nil -} diff --git a/packer/command_test.go b/packer/command_test.go index 446805850..904ef2657 100644 --- a/packer/command_test.go +++ b/packer/command_test.go @@ -1,12 +1,5 @@ package packer -import ( - "cgl.tideland.biz/asserts" - "net" - "net/rpc" - "testing" -) - type TestCommand struct { runArgs []string runCalled bool @@ -23,73 +16,3 @@ func (tc *TestCommand) Run(env Environment, args []string) int { func (tc *TestCommand) Synopsis() string { return "foo" } - -// This starts a RPC server for the given command listening on the -// given address. The RPC server is ready when "readyChan" receives a message -// and the RPC server will quit when "stopChan" receives a message. -// -// This function should be run in a goroutine. -func testCommandRPCServer(laddr string, command interface{}, readyChan chan int, stopChan <-chan int) { - listener, err := net.Listen("tcp", laddr) - if err != nil { - panic(err) - } - - // Close the listener when we exit so that the RPC server ends - defer listener.Close() - - // Start the RPC server - server := rpc.NewServer() - server.RegisterName("Command", command) - - go func() { - for { - conn, err := listener.Accept() - if err != nil { - // If there is an error, just ignore it. - break - } - - go server.ServeConn(conn) - } - }() - - // We're ready! - readyChan <- 1 - - // Block on waiting to receive from the channel - <-stopChan -} - -func TestRPCCommand(t *testing.T) { - assert := asserts.NewTestingAsserts(t, true) - - // Create the command - command := new(TestCommand) - serverCommand := &RPCServerCommand{command} - - // Start the RPC server, and make sure to exit it at the end - // of the test. - readyChan := make(chan int) - stopChan := make(chan int) - defer func() { stopChan <- 1 }() - go testCommandRPCServer(":1234", serverCommand, readyChan, stopChan) - <-readyChan - - // Create the command client over RPC and run some methods to verify - // we get the proper behavior. - client, err := rpc.Dial("tcp", ":1234") - if err != nil { - panic(err) - } - - clientComm := &RPCClientCommand{client} - runArgs := []string{"foo", "bar"} - testEnv := testEnvironment() - exitCode := clientComm.Run(testEnv, runArgs) - synopsis := clientComm.Synopsis() - - assert.Equal(command.runArgs, runArgs, "Correct args should be sent") - assert.Equal(exitCode, 0, "Exit code should be correct") - assert.Equal(synopsis, "foo", "Synopsis should be correct") -} diff --git a/packer/rpc/command.go b/packer/rpc/command.go new file mode 100644 index 000000000..c0e8f4d1d --- /dev/null +++ b/packer/rpc/command.go @@ -0,0 +1,47 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// A ClientCommand is an implementation of the Command interface where the +// command is actually executed over an RPC connection. +type ClientCommand struct { + client *rpc.Client +} + +// A ServerCommand wraps a Command and makes it exportable as part +// of a Golang RPC server. +type ServerCommand struct { + command packer.Command +} + +type CommandRunArgs struct { + Env packer.Environment + Args []string +} + +type CommandSynopsisArgs byte + +func (c *ClientCommand) Run(env packer.Environment, args []string) (result int) { + // TODO: Environment + rpcArgs := &CommandRunArgs{nil, args} + c.client.Call("Command.Run", rpcArgs, &result) + return +} + +func (c *ClientCommand) Synopsis() (result string) { + c.client.Call("Command.Synopsis", CommandSynopsisArgs(0), &result) + return +} + +func (c *ServerCommand) Run(args *CommandRunArgs, reply *int) error { + *reply = c.command.Run(args.Env, args.Args) + return nil +} + +func (c *ServerCommand) Synopsis(args *CommandSynopsisArgs, reply *string) error { + *reply = c.command.Synopsis() + return nil +} diff --git a/packer/rpc/command_test.go b/packer/rpc/command_test.go new file mode 100644 index 000000000..00276ef6e --- /dev/null +++ b/packer/rpc/command_test.go @@ -0,0 +1,112 @@ +package rpc + +import ( + "bytes" + "cgl.tideland.biz/asserts" + "github.com/mitchellh/packer/packer" + "net" + "net/rpc" + "testing" +) + +type TestCommand struct { + runArgs []string + runCalled bool + runEnv packer.Environment +} + +func (tc *TestCommand) Run(env packer.Environment, args []string) int { + tc.runCalled = true + tc.runArgs = args + tc.runEnv = env + return 0 +} + +func (tc *TestCommand) Synopsis() string { + return "foo" +} + +func testEnvironment() packer.Environment { + config := &packer.EnvironmentConfig{} + config.Ui = &packer.ReaderWriterUi{ + new(bytes.Buffer), + new(bytes.Buffer), + } + + env, err := packer.NewEnvironment(config) + if err != nil { + panic(err) + } + + return env +} + +// This starts a RPC server for the given command listening on the +// given address. The RPC server is ready when "readyChan" receives a message +// and the RPC server will quit when "stopChan" receives a message. +// +// This function should be run in a goroutine. +func testCommandRPCServer(laddr string, command interface{}, readyChan chan int, stopChan <-chan int) { + listener, err := net.Listen("tcp", laddr) + if err != nil { + panic(err) + } + + // Close the listener when we exit so that the RPC server ends + defer listener.Close() + + // Start the RPC server + server := rpc.NewServer() + server.RegisterName("Command", command) + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + // If there is an error, just ignore it. + break + } + + go server.ServeConn(conn) + } + }() + + // We're ready! + readyChan <- 1 + + // Block on waiting to receive from the channel + <-stopChan +} + +func TestRPCCommand(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + // Create the command + command := new(TestCommand) + serverCommand := &ServerCommand{command} + + // Start the RPC server, and make sure to exit it at the end + // of the test. + readyChan := make(chan int) + stopChan := make(chan int) + defer func() { stopChan <- 1 }() + go testCommandRPCServer(":1234", serverCommand, readyChan, stopChan) + <-readyChan + + // Create the command client over RPC and run some methods to verify + // we get the proper behavior. + client, err := rpc.Dial("tcp", ":1234") + if err != nil { + panic(err) + } + + clientComm := &ClientCommand{client} + runArgs := []string{"foo", "bar"} + testEnv := testEnvironment() + exitCode := clientComm.Run(testEnv, runArgs) + synopsis := clientComm.Synopsis() + + assert.Equal(command.runArgs, runArgs, "Correct args should be sent") + assert.Equal(exitCode, 0, "Exit code should be correct") + assert.Equal(synopsis, "foo", "Synopsis should be correct") +}