packer/rpc: Support provisioners

This commit is contained in:
Mitchell Hashimoto 2013-05-22 15:35:52 -07:00
parent 638e191186
commit 92a4f27868
5 changed files with 195 additions and 0 deletions

View File

@ -59,6 +59,22 @@ func (e *Environment) Hook(name string) (h packer.Hook, err error) {
return return
} }
func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error) {
var reply string
err = e.client.Call("Environment.Provisioner", name, &reply)
if err != nil {
return
}
client, err := rpc.Dial("tcp", reply)
if err != nil {
return
}
p = Provisioner(client)
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)
@ -101,6 +117,19 @@ func (e *EnvironmentServer) Hook(name *string, reply *string) error {
return nil return nil
} }
func (e *EnvironmentServer) Provisioner(name *string, reply *string) error {
prov, err := e.env.Provisioner(*name)
if err != nil {
return err
}
server := rpc.NewServer()
RegisterProvisioner(server, prov)
*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

@ -17,6 +17,8 @@ type testEnvironment struct {
cliArgs []string cliArgs []string
hookCalled bool hookCalled bool
hookName string hookName string
provCalled bool
provName string
uiCalled bool uiCalled bool
} }
@ -38,6 +40,12 @@ func (e *testEnvironment) Hook(name string) (packer.Hook, error) {
return nil, nil return nil, nil
} }
func (e *testEnvironment) Provisioner(name string) (packer.Provisioner, error) {
e.provCalled = true
e.provName = 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
@ -74,6 +82,11 @@ func TestEnvironmentRPC(t *testing.T) {
assert.Equal(e.cliArgs, cliArgs, "args should match") assert.Equal(e.cliArgs, cliArgs, "args should match")
assert.Equal(result, 42, "result shuld be 42") assert.Equal(result, 42, "result shuld be 42")
// Test Provisioner
_, _ = eClient.Provisioner("foo")
assert.True(e.provCalled, "provisioner should be called")
assert.Equal(e.provName, "foo", "should have proper name")
// Test Ui // Test Ui
ui := eClient.Ui() ui := eClient.Ui()
assert.True(e.uiCalled, "Ui should've been called") assert.True(e.uiCalled, "Ui should've been called")

74
packer/rpc/provisioner.go Normal file
View File

@ -0,0 +1,74 @@
package rpc
import (
"github.com/mitchellh/packer/packer"
"net/rpc"
)
// An implementation of packer.Provisioner where the provisioner is actually
// executed over an RPC connection.
type provisioner struct {
client *rpc.Client
}
// ProvisionerServer wraps a packer.Provisioner implementation and makes it
// exportable as part of a Golang RPC server.
type ProvisionerServer struct {
p packer.Provisioner
}
type ProvisionerPrepareArgs struct {
Config interface{}
RPCAddress string
}
type ProvisionerProvisionArgs struct {
RPCAddress string
}
func Provisioner(client *rpc.Client) *provisioner {
return &provisioner{client}
}
func (p *provisioner) Prepare(config interface{}, ui packer.Ui) {
// TODO: Error handling
server := rpc.NewServer()
RegisterUi(server, ui)
args := &ProvisionerPrepareArgs{config, serveSingleConn(server)}
p.client.Call("Provisioner.Prepare", args, new(interface{}))
}
func (p *provisioner) Provision(ui packer.Ui, comm packer.Communicator) {
// TODO: Error handling
server := rpc.NewServer()
RegisterCommunicator(server, comm)
RegisterUi(server, ui)
args := &ProvisionerProvisionArgs{serveSingleConn(server)}
p.client.Call("Provisioner.Provision", args, new(interface{}))
}
func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *interface{}) error {
client, err := rpc.Dial("tcp", args.RPCAddress)
if err != nil {
return err
}
ui := &Ui{client}
p.p.Prepare(args.Config, ui)
return nil
}
func (p *ProvisionerServer) Provision(args *ProvisionerProvisionArgs, reply *interface{}) error {
client, err := rpc.Dial("tcp", args.RPCAddress)
if err != nil {
return err
}
comm := Communicator(client)
ui := &Ui{client}
p.p.Provision(ui, comm)
return nil
}

View File

@ -0,0 +1,74 @@
package rpc
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"net/rpc"
"testing"
)
type testProvisioner struct {
prepareCalled bool
prepareConfig interface{}
prepareUi packer.Ui
provCalled bool
provComm packer.Communicator
provUi packer.Ui
}
func (p *testProvisioner) Prepare(config interface{}, ui packer.Ui) {
p.prepareCalled = true
p.prepareConfig = config
p.prepareUi = ui
}
func (p *testProvisioner) Provision(ui packer.Ui, comm packer.Communicator) {
p.provCalled = true
p.provComm = comm
p.provUi = ui
}
func TestProvisionerRPC(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
// Create the interface to test
p := new(testProvisioner)
// Start the server
server := rpc.NewServer()
RegisterProvisioner(server, p)
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")
// Test Prepare
config := 42
ui := &testUi{}
pClient := Provisioner(client)
pClient.Prepare(config, ui)
assert.True(p.prepareCalled, "prepare should be called")
assert.Equal(p.prepareConfig, 42, "prepare should be called with right arg")
p.prepareUi.Say("foo")
assert.True(ui.sayCalled, "say should be called")
// Test Provision
ui = &testUi{}
comm := &testCommunicator{}
pClient.Provision(ui, comm)
assert.True(p.provCalled, "provision should be called")
p.provUi.Say("foo")
assert.True(ui.sayCalled, "say should be called")
}
func TestProvisioner_Implements(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var r packer.Provisioner
p := Provisioner(nil)
assert.Implementor(p, &r, "should be a provisioner")
}

View File

@ -47,6 +47,11 @@ func RegisterHook(s *rpc.Server, hook packer.Hook) {
s.RegisterName("Hook", &HookServer{hook}) s.RegisterName("Hook", &HookServer{hook})
} }
// 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})
}
// 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) {