From 30ab94443707c62613a0a0e774b2e8a84061da82 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 11 May 2013 09:51:49 -0700 Subject: [PATCH] packer/rpc: Support Hooks --- packer/hook.go | 5 ++-- packer/hook_test.go | 2 +- packer/rpc/environment.go | 31 +++++++++++++++++++++ packer/rpc/environment_test.go | 8 ++++++ packer/rpc/hook.go | 50 ++++++++++++++++++++++++++++++++++ packer/rpc/hook_test.go | 49 +++++++++++++++++++++++++++++++++ packer/rpc/server.go | 6 ++++ 7 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 packer/rpc/hook.go create mode 100644 packer/rpc/hook_test.go diff --git a/packer/hook.go b/packer/hook.go index bcf963711..1bb629736 100644 --- a/packer/hook.go +++ b/packer/hook.go @@ -6,7 +6,8 @@ package packer // Run is called when the hook is called, with the name of the hook and // arbitrary data associated with it. To know what format the data is in, // you must reference the documentation for the specific hook you're interested -// in. +// in. In addition to that, the Hook is given access to a UI so that it can +// output things to the user. type Hook interface { - Run(string, interface{}) + Run(string, interface{}, Ui) } diff --git a/packer/hook_test.go b/packer/hook_test.go index b180884ad..22229d4a4 100644 --- a/packer/hook_test.go +++ b/packer/hook_test.go @@ -4,6 +4,6 @@ type TestHook struct { runCalled bool } -func (t *TestHook) Run(string, interface{}) { +func (t *TestHook) Run(string, interface{}, Ui) { t.runCalled = true } diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go index 4c8e3d424..302c716d2 100644 --- a/packer/rpc/environment.go +++ b/packer/rpc/environment.go @@ -43,6 +43,23 @@ func (e *Environment) Cli(args []string) (result int, err error) { return } +func (e *Environment) Hook(name string) (h packer.Hook, err error) { + var reply string + err = e.client.Call("Environment.Hook", name, &reply) + if err != nil { + return + } + + _, err = rpc.Dial("tcp", reply) + if err != nil { + return + } + + // TODO: Hook + h = nil + return +} + func (e *Environment) Ui() packer.Ui { var reply string e.client.Call("Environment.Ui", new(interface{}), &reply) @@ -71,6 +88,20 @@ func (e *EnvironmentServer) Cli(args *EnvironmentCliArgs, reply *int) (err error return } +func (e *EnvironmentServer) Hook(name *string, reply *string) error { + _, err := e.env.Hook(*name) + if err != nil { + return err + } + + // Wrap it + // TODO: Register hook + server := rpc.NewServer() + + *reply = serveSingleConn(server) + return nil +} + func (e *EnvironmentServer) Ui(args *interface{}, reply *string) error { ui := e.env.Ui() diff --git a/packer/rpc/environment_test.go b/packer/rpc/environment_test.go index 7fd294dd7..1116de05d 100644 --- a/packer/rpc/environment_test.go +++ b/packer/rpc/environment_test.go @@ -15,6 +15,8 @@ type testEnvironment struct { builderName string cliCalled bool cliArgs []string + hookCalled bool + hookName string uiCalled bool } @@ -30,6 +32,12 @@ func (e *testEnvironment) Cli(args []string) (int, error) { return 42, nil } +func (e *testEnvironment) Hook(name string) (packer.Hook, error) { + e.hookCalled = true + e.hookName = name + return nil, nil +} + func (e *testEnvironment) Ui() packer.Ui { e.uiCalled = true return testEnvUi diff --git a/packer/rpc/hook.go b/packer/rpc/hook.go new file mode 100644 index 000000000..3d6b5a532 --- /dev/null +++ b/packer/rpc/hook.go @@ -0,0 +1,50 @@ +package rpc + +import ( + "github.com/mitchellh/packer/packer" + "net/rpc" +) + +// An implementation of packer.Hook where the hook is actually executed +// over an RPC connection. +type hook struct { + client *rpc.Client +} + +// HookServer wraps a packer.Hook implementation and makes it exportable +// as part of a Golang RPC server. +type HookServer struct { + hook packer.Hook +} + +type HookRunArgs struct { + Name string + Data interface{} + RPCAddress string +} + +func Hook(client *rpc.Client) *hook { + return &hook{client} +} + +func (h *hook) Run(name string, data interface{}, ui packer.Ui) { + server := rpc.NewServer() + RegisterUi(server, ui) + address := serveSingleConn(server) + + args := &HookRunArgs{name, data, address} + h.client.Call("Hook.Run", args, new(interface{})) + return +} + +func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error { + client, err := rpc.Dial("tcp", args.RPCAddress) + if err != nil { + return err + } + + h.hook.Run(args.Name, args.Data, &Ui{client}) + + *reply = nil + return nil +} diff --git a/packer/rpc/hook_test.go b/packer/rpc/hook_test.go new file mode 100644 index 000000000..dd8f42aa9 --- /dev/null +++ b/packer/rpc/hook_test.go @@ -0,0 +1,49 @@ +package rpc + +import ( + "cgl.tideland.biz/asserts" + "github.com/mitchellh/packer/packer" + "net/rpc" + "testing" +) + +type testHook struct { + runCalled bool + runUi packer.Ui +} + +func (h *testHook) Run(name string, data interface{}, ui packer.Ui) { + h.runCalled = true +} + +func TestHookRPC(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + // Create the UI to test + h := new(testHook) + + // Serve + server := rpc.NewServer() + RegisterHook(server, h) + address := serveSingleConn(server) + + // Create the client over RPC and run some methods to verify it works + client, err := rpc.Dial("tcp", address) + assert.Nil(err, "should be able to connect") + + hClient := Hook(client) + + // Test Run + ui := &testUi{} + hClient.Run("foo", 42, ui) + assert.True(h.runCalled, "run should be called") +} + +func TestHook_Implements(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + var r packer.Hook + h := &hook{nil} + + assert.Implementor(h, &r, "should be a Hook") +} diff --git a/packer/rpc/server.go b/packer/rpc/server.go index e0b0cf753..e9059bef8 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -29,6 +29,12 @@ func RegisterEnvironment(s *rpc.Server, e packer.Environment) { s.RegisterName("Environment", &EnvironmentServer{e}) } +// Registers the appropriate endpoint on an RPC server to serve a +// Hook. +func RegisterHook(s *rpc.Server, hook packer.Hook) { + s.RegisterName("Hook", &HookServer{hook}) +} + // Registers the appropriate endpoint on an RPC server to serve a // Packer UI func RegisterUi(s *rpc.Server, ui packer.Ui) {