diff --git a/packer/rpc/post_processor.go b/packer/rpc/post_processor.go new file mode 100644 index 000000000..67750b82b --- /dev/null +++ b/packer/rpc/post_processor.go @@ -0,0 +1,91 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// An implementation of packer.PostProcessor where the PostProcessor is actually +// executed over an RPC connection. +type postProcessor struct { + client *rpc.Client +} + +// PostProcessorServer wraps a packer.PostProcessor implementation and makes it +// exportable as part of a Golang RPC server. +type PostProcessorServer struct { + p packer.PostProcessor +} + +type PostProcessorProcessResponse struct { + Err error + RPCAddress string +} + +func PostProcessor(client *rpc.Client) *postProcessor { + return &postProcessor{client} +} +func (p *postProcessor) Configure(raw interface{}) (err error) { + if cerr := p.client.Call("PostProcessor.Configure", &raw, &err); cerr != nil { + err = cerr + } + + return +} + +func (p *postProcessor) PostProcess(a packer.Artifact) (packer.Artifact, error) { + server := rpc.NewServer() + RegisterArtifact(server, a) + + var response PostProcessorProcessResponse + if err := p.client.Call("PostProcessor.PostProcess", serveSingleConn(server), &response); err != nil { + return nil, err + } + + if response.Err != nil { + return nil, response.Err + } + + if response.RPCAddress == "" { + return nil, nil + } + + client, err := rpc.Dial("tcp", response.RPCAddress) + if err != nil { + return nil, err + } + + return Artifact(client), nil +} + +func (p *PostProcessorServer) Configure(raw *interface{}, reply *error) error { + *reply = p.p.Configure(*raw) + return nil +} + +func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorProcessResponse) error { + client, err := rpc.Dial("tcp", address) + if err != nil { + return err + } + + responseAddress := "" + + artifact, err := p.p.PostProcess(Artifact(client)) + if err == nil && artifact != nil { + server := rpc.NewServer() + RegisterArtifact(server, artifact) + responseAddress = serveSingleConn(server) + } + + if err != nil { + err = NewBasicError(err) + } + + *reply = PostProcessorProcessResponse{ + Err: err, + RPCAddress: responseAddress, + } + + return nil +} diff --git a/packer/rpc/post_processor_test.go b/packer/rpc/post_processor_test.go new file mode 100644 index 000000000..e3d672549 --- /dev/null +++ b/packer/rpc/post_processor_test.go @@ -0,0 +1,87 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" + "testing" +) + +var testPostProcessorArtifact = new(testArtifact) + +type TestPostProcessor struct { + configCalled bool + configVal interface{} + ppCalled bool + ppArtifact packer.Artifact +} + +func (pp *TestPostProcessor) Configure(v interface{}) error { + pp.configCalled = true + pp.configVal = v + return nil +} + +func (pp *TestPostProcessor) PostProcess(a packer.Artifact) (packer.Artifact, error) { + pp.ppCalled = true + pp.ppArtifact = a + return testPostProcessorArtifact, nil +} + +func TestPostProcessorRPC(t *testing.T) { + // Create the interface to test + p := new(TestPostProcessor) + + // Start the server + server := rpc.NewServer() + RegisterPostProcessor(server, p) + address := serveSingleConn(server) + + // Create the client over RPC and run some methods to verify it works + client, err := rpc.Dial("tcp", address) + if err != nil { + t.Fatalf("Error connecting to rpc: %s", err) + } + + // Test Configure + config := 42 + pClient := PostProcessor(client) + err = pClient.Configure(config) + if err != nil { + t.Fatalf("error: %s", err) + } + + if !p.configCalled { + t.Fatal("config should be called") + } + + if p.configVal != 42 { + t.Fatalf("unknown config value: %#v", p.configVal) + } + + // Test PostProcess + a := new(testArtifact) + artifact, err := pClient.PostProcess(a) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !p.ppCalled { + t.Fatal("postprocess should be called") + } + + if p.ppArtifact.BuilderId() != "bid" { + t.Fatal("unknown artifact") + } + + if artifact.BuilderId() != "bid" { + t.Fatal("unknown result artifact") + } +} + +func TestPostProcessor_Implements(t *testing.T) { + var raw interface{} + raw = PostProcessor(nil) + if _, ok := raw.(packer.PostProcessor); !ok { + t.Fatal("not a postprocessor") + } +} diff --git a/packer/rpc/server.go b/packer/rpc/server.go index 9117582e3..f6098ea7a 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -53,6 +53,12 @@ func RegisterHook(s *rpc.Server, hook packer.Hook) { s.RegisterName("Hook", &HookServer{hook}) } +// Registers the appropriate endpoing on an RPC server to serve a +// PostProcessor. +func RegisterPostProcessor(s *rpc.Server, p packer.PostProcessor) { + s.RegisterName("PostProcessor", &PostProcessorServer{p}) +} + // Registers the appropriate endpoint on an RPC server to serve a packer.Provisioner func RegisterProvisioner(s *rpc.Server, p packer.Provisioner) { s.RegisterName("Provisioner", &ProvisionerServer{p})