diff --git a/packer/rpc/builder.go b/packer/rpc/builder.go new file mode 100644 index 000000000..91f3f555e --- /dev/null +++ b/packer/rpc/builder.go @@ -0,0 +1,63 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// An implementation of packer.Builder where the builder is actually executed +// over an RPC connection. +type Builder struct { + client *rpc.Client +} + +// BuilderServer wraps a packer.Builder implementation and makes it exportable +// as part of a Golang RPC server. +type BuilderServer struct { + builder packer.Builder +} + +type BuilderPrepareArgs struct { + Config interface{} +} + +type BuilderRunArgs struct { + RPCAddress string +} + +func (b *Builder) Prepare(config interface{}) { + b.client.Call("Builder.Prepare", &BuilderPrepareArgs{config}, new(interface{})) +} + +func (b *Builder) Run(build packer.Build, ui packer.Ui) { + // Create and start the server for the Build and UI + // TODO: Error handling + server := NewServer() + server.RegisterBuild(build) + server.RegisterUi(ui) + server.Start() + defer server.Stop() + + args := &BuilderRunArgs{server.Address()} + b.client.Call("Builder.Run", args, new(interface{})) +} + +func (b *BuilderServer) Prepare(args *BuilderPrepareArgs, reply *interface{}) error { + b.builder.Prepare(args.Config) + *reply = nil + return nil +} + +func (b *BuilderServer) Run(args *BuilderRunArgs, reply *interface{}) error { + client, err := rpc.Dial("tcp", args.RPCAddress) + if err != nil { + return err + } + + build := &Build{client} + ui := &Ui{client} + b.builder.Run(build, ui) + + *reply = nil + return nil +} diff --git a/packer/rpc/builder_test.go b/packer/rpc/builder_test.go new file mode 100644 index 000000000..499a4fbed --- /dev/null +++ b/packer/rpc/builder_test.go @@ -0,0 +1,60 @@ +package rpc + +import ( + "cgl.tideland.biz/asserts" + "github.com/mitchellh/packer/packer" + "net/rpc" + "testing" +) + +type testBuilder struct { + prepareCalled bool + prepareConfig interface{} + runCalled bool + runBuild packer.Build + runUi packer.Ui +} + +func (b *testBuilder) Prepare(config interface{}) { + b.prepareCalled = true + b.prepareConfig = config +} + +func (b *testBuilder) Run(build packer.Build, ui packer.Ui) { + b.runCalled = true + b.runBuild = build + b.runUi = ui +} + +func TestBuilderRPC(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + // Create the interface to test + b := new(testBuilder) + + // Start the server + server := NewServer() + server.RegisterBuilder(b) + server.Start() + defer server.Stop() + + // Create the client over RPC and run some methods to verify it works + client, err := rpc.Dial("tcp", server.Address()) + assert.Nil(err, "should be able to connect") + + // Test Prepare + config := 42 + bClient := &Builder{client} + bClient.Prepare(config) + assert.True(b.prepareCalled, "prepare should be called") + assert.Equal(b.prepareConfig, 42, "prepare should be called with right arg") +} + +func TestBuilder_ImplementsBuild(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + var realBuilder packer.Builder + b := &Builder{nil} + + assert.Implementor(b, &realBuilder, "should be a Builder") +} diff --git a/packer/rpc/server.go b/packer/rpc/server.go index 4ed4cb25e..cc5c6792d 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -29,6 +29,14 @@ func (s *Server) Address() string { return s.listener.Addr().String() } +func (s *Server) RegisterBuild(b packer.Build) { + s.server.RegisterName("Build", &BuildServer{b}) +} + +func (s *Server) RegisterBuilder(b packer.Builder) { + s.server.RegisterName("Builder", &BuilderServer{b}) +} + func (s *Server) RegisterUi(ui packer.Ui) { s.server.RegisterName("Ui", &UiServer{ui}) }