diff --git a/config.go b/config.go index 80579adb9..d7faeae1e 100644 --- a/config.go +++ b/config.go @@ -64,6 +64,17 @@ func (c *config) CommandNames() (result []string) { return } +func (c *config) LoadBuilder(name string) (packer.Builder, error) { + log.Printf("Loading builder: %s\n", name) + bin, ok := c.Builders[name] + if !ok { + log.Printf("Builder not found: %s\n", name) + return nil, nil + } + + return plugin.Builder(exec.Command(bin)) +} + // This is a proper packer.CommandFunc that can be used to load packer.Command // implementations from the defined plugins. func (c *config) LoadCommand(name string) (packer.Command, error) { diff --git a/packer.go b/packer.go index 08d7d8b35..b3041eba8 100644 --- a/packer.go +++ b/packer.go @@ -84,6 +84,7 @@ func main() { } envConfig := packer.DefaultEnvironmentConfig() + envConfig.BuilderFunc = config.LoadBuilder envConfig.Commands = config.CommandNames() envConfig.CommandFunc = config.LoadCommand diff --git a/packer/plugin/builder.go b/packer/plugin/builder.go new file mode 100644 index 000000000..8716be11d --- /dev/null +++ b/packer/plugin/builder.go @@ -0,0 +1,76 @@ +package plugin + +import ( + "github.com/mitchellh/packer/packer" + "log" + "net/rpc" + "os/exec" + packrpc "github.com/mitchellh/packer/packer/rpc" +) + +type cmdBuilder struct { + builder packer.Builder + client *client +} + +func (b *cmdBuilder) Prepare(config interface{}) { + defer func() { + r := recover() + b.checkExit(r, nil) + }() + + b.builder.Prepare(config) +} + +func (b *cmdBuilder) Run(build packer.Build, ui packer.Ui) { + defer func() { + r := recover() + b.checkExit(r, nil) + }() + + b.builder.Run(build, ui) +} + +func (c *cmdBuilder) checkExit(p interface{}, cb func()) { + if c.client.Exited() && cb != nil { + cb() + } else if p != nil { + log.Panic(p) + } +} + + +// Returns a valid packer.Builder where the builder is executed via RPC +// to a plugin that is within a subprocess. +// +// This method will start the given exec.Cmd, which should point to +// the plugin binary to execute. Some configuration will be done to +// the command, such as overriding Stdout and some environmental variables. +// +// This function guarantees the subprocess will end in a timely manner. +func Builder(cmd *exec.Cmd) (result packer.Builder, err error) { + cmdClient := NewManagedClient(cmd) + address, err := cmdClient.Start() + if err != nil { + return + } + + defer func() { + // Make sure the command is properly killed in the case of an error + if err != nil { + cmdClient.Kill() + } + }() + + client, err := rpc.Dial("tcp", address) + if err != nil { + return + } + + result = &cmdBuilder{ + packrpc.Builder(client), + cmdClient, + } + + return +} diff --git a/packer/rpc/builder.go b/packer/rpc/builder.go index aa50db598..7f8c57ebf 100644 --- a/packer/rpc/builder.go +++ b/packer/rpc/builder.go @@ -7,7 +7,7 @@ import ( // An implementation of packer.Builder where the builder is actually executed // over an RPC connection. -type Builder struct { +type builder struct { client *rpc.Client } @@ -25,11 +25,15 @@ type BuilderRunArgs struct { RPCAddress string } -func (b *Builder) Prepare(config interface{}) { +func Builder(client *rpc.Client) *builder { + return &builder{client} +} + +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) { +func (b *builder) Run(build packer.Build, ui packer.Ui) { // Create and start the server for the Build and UI // TODO: Error handling server := rpc.NewServer() diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go index f62a771f1..4c8e3d424 100644 --- a/packer/rpc/environment.go +++ b/packer/rpc/environment.go @@ -33,7 +33,7 @@ func (e *Environment) Builder(name string) (b packer.Builder, err error) { return } - b = &Builder{client} + b = Builder(client) return }