packer/rpc: Support Hooks

This commit is contained in:
Mitchell Hashimoto 2013-05-11 09:51:49 -07:00
parent 8ed313e7b5
commit 30ab944437
7 changed files with 148 additions and 3 deletions

View File

@ -6,7 +6,8 @@ package packer
// Run is called when the hook is called, with the name of the hook and // 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, // 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 // 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 { type Hook interface {
Run(string, interface{}) Run(string, interface{}, Ui)
} }

View File

@ -4,6 +4,6 @@ type TestHook struct {
runCalled bool runCalled bool
} }
func (t *TestHook) Run(string, interface{}) { func (t *TestHook) Run(string, interface{}, Ui) {
t.runCalled = true t.runCalled = true
} }

View File

@ -43,6 +43,23 @@ func (e *Environment) Cli(args []string) (result int, err error) {
return 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 { func (e *Environment) Ui() packer.Ui {
var reply string var reply string
e.client.Call("Environment.Ui", new(interface{}), &reply) e.client.Call("Environment.Ui", new(interface{}), &reply)
@ -71,6 +88,20 @@ func (e *EnvironmentServer) Cli(args *EnvironmentCliArgs, reply *int) (err error
return 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 { func (e *EnvironmentServer) Ui(args *interface{}, reply *string) error {
ui := e.env.Ui() ui := e.env.Ui()

View File

@ -15,6 +15,8 @@ type testEnvironment struct {
builderName string builderName string
cliCalled bool cliCalled bool
cliArgs []string cliArgs []string
hookCalled bool
hookName string
uiCalled bool uiCalled bool
} }
@ -30,6 +32,12 @@ func (e *testEnvironment) Cli(args []string) (int, error) {
return 42, nil 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 { func (e *testEnvironment) Ui() packer.Ui {
e.uiCalled = true e.uiCalled = true
return testEnvUi return testEnvUi

50
packer/rpc/hook.go Normal file
View File

@ -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
}

49
packer/rpc/hook_test.go Normal file
View File

@ -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")
}

View File

@ -29,6 +29,12 @@ func RegisterEnvironment(s *rpc.Server, e packer.Environment) {
s.RegisterName("Environment", &EnvironmentServer{e}) 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 // Registers the appropriate endpoint on an RPC server to serve a
// Packer UI // Packer UI
func RegisterUi(s *rpc.Server, ui packer.Ui) { func RegisterUi(s *rpc.Server, ui packer.Ui) {