From 8d3cf7aa595dd8778e5b0759c6a5f8606b2f0a5e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 30 Jul 2013 14:44:03 -0700 Subject: [PATCH] builder/amazon/chroot: provision within the chroot --- builder/amazon/chroot/communicator.go | 82 +++++++++++++++++++ builder/amazon/chroot/communicator_test.go | 14 ++++ .../amazon/chroot/step_chroot_provision.go | 34 ++++++++ 3 files changed, 130 insertions(+) create mode 100644 builder/amazon/chroot/communicator.go create mode 100644 builder/amazon/chroot/communicator_test.go create mode 100644 builder/amazon/chroot/step_chroot_provision.go diff --git a/builder/amazon/chroot/communicator.go b/builder/amazon/chroot/communicator.go new file mode 100644 index 000000000..6e4387aa0 --- /dev/null +++ b/builder/amazon/chroot/communicator.go @@ -0,0 +1,82 @@ +package chroot + +import ( + "github.com/mitchellh/packer/packer" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "syscall" +) + +// Communicator is a special communicator that works by executing +// commands locally but within a chroot. +type Communicator struct { + Chroot string +} + +func (c *Communicator) Start(cmd *packer.RemoteCmd) error { + chrootCmdPath, err := exec.LookPath("chroot") + if err != nil { + return err + } + + localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command) + localCmd.Stdin = cmd.Stdin + localCmd.Stdout = cmd.Stdout + localCmd.Stderr = cmd.Stderr + log.Printf("Executing: %s %#v", localCmd.Path, localCmd.Args) + if err := localCmd.Start(); err != nil { + return err + } + + go func() { + exitStatus := 0 + if err := localCmd.Wait(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + exitStatus = 1 + + // There is no process-independent way to get the REAL + // exit status so we just try to go deeper. + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + exitStatus = status.ExitStatus() + } + } + } + + cmd.SetExited(exitStatus) + }() + + return nil +} + +func (c *Communicator) Upload(dst string, r io.Reader) error { + dst = filepath.Join(c.Chroot, dst) + f, err := os.Open(dst) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(f, r); err != nil { + return err + } + + return nil +} + +func (c *Communicator) Download(src string, w io.Writer) error { + src = filepath.Join(c.Chroot, src) + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(w, f); err != nil { + return err + } + + return nil +} diff --git a/builder/amazon/chroot/communicator_test.go b/builder/amazon/chroot/communicator_test.go new file mode 100644 index 000000000..56745a681 --- /dev/null +++ b/builder/amazon/chroot/communicator_test.go @@ -0,0 +1,14 @@ +package chroot + +import ( + "github.com/mitchellh/packer/packer" + "testing" +) + +func TestCommunicator_ImplementsCommunicator(t *testing.T) { + var raw interface{} + raw = &Communicator{} + if _, ok := raw.(packer.Communicator); !ok { + t.Fatalf("Communicator should be a communicator") + } +} diff --git a/builder/amazon/chroot/step_chroot_provision.go b/builder/amazon/chroot/step_chroot_provision.go new file mode 100644 index 000000000..a295f27bf --- /dev/null +++ b/builder/amazon/chroot/step_chroot_provision.go @@ -0,0 +1,34 @@ +package chroot + +import ( + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +// StepChrootProvision provisions the instance within a chroot. +type StepChrootProvision struct { + mounts []string +} + +func (s *StepChrootProvision) Run(state map[string]interface{}) multistep.StepAction { + hook := state["hook"].(packer.Hook) + mountPath := state["mount_path"].(string) + ui := state["ui"].(packer.Ui) + + // Create our communicator + comm := &Communicator{ + Chroot: mountPath, + } + + // Provision + log.Println("Running the provision hook") + if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil { + state["error"] = err + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepChrootProvision) Cleanup(state map[string]interface{}) {}