builder/amazon/chroot: CommandWrapper

/cc @mwhooker - I changed the interface up a bit to return an error,
since things should return errors in Go (the ui.Error bit was kind of
ghetto because it had no way to bubble that error up except through the
UI).

Using this, I made it so that the communicator uses both a
CommandWrapper and ShellCommand with chroot so that the chroot commannd
is also wrapped (it wasn't before).

I think the functionality of all this is the same but I'd love if you
could look it over and make sure.
This commit is contained in:
Mitchell Hashimoto 2013-09-30 09:33:57 -07:00
parent 535888d9d8
commit b554a0dd86
8 changed files with 97 additions and 88 deletions

View File

@ -13,7 +13,6 @@ import (
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log" "log"
"os/exec"
"runtime" "runtime"
) )
@ -165,15 +164,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region) ec2conn := ec2.New(auth, region)
wrappedCommand := func(command string) *exec.Cmd { wrappedCommand := func(command string) (string, error) {
wrapped, err := b.config.tpl.Process( return b.config.tpl.Process(
b.config.CommandWrapper, &wrappedCommandTemplate{ b.config.CommandWrapper, &wrappedCommandTemplate{
Command: command, Command: command,
}) })
if err != nil {
ui.Error(err.Error())
}
return ShellCommand(wrapped)
} }
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
@ -182,7 +177,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ec2", ec2conn) state.Put("ec2", ec2conn)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
state.Put("wrappedCommand", Command(wrappedCommand)) state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
// Build the steps // Build the steps
steps := []multistep.Step{ steps := []multistep.Step{

View File

@ -0,0 +1,15 @@
package chroot
import (
"os/exec"
)
// CommandWrapper is a type that given a command, will possibly modify that
// command in-flight. This might return an error.
type CommandWrapper func(string) (string, error)
// ShellCommand takes a command string and returns an *exec.Cmd to execute
// it within the context of a shell (/bin/sh).
func ShellCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}

View File

@ -1,6 +1,6 @@
package chroot package chroot
// pf := func () { somefunc("a str", 1) } // pf := func () { somefunc("a str", 1) }
import ( import (
"fmt" "fmt"
@ -14,18 +14,21 @@ import (
"syscall" "syscall"
) )
type Command func(string) *exec.Cmd
// Communicator is a special communicator that works by executing // Communicator is a special communicator that works by executing
// commands locally but within a chroot. // commands locally but within a chroot.
type Communicator struct { type Communicator struct {
Chroot string Chroot string
ChrootCmd Command CmdWrapper CommandWrapper
WrappedCommand Command
} }
func (c *Communicator) Start(cmd *packer.RemoteCmd) error { func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
localCmd := c.ChrootCmd(cmd.Command) command, err := c.CmdWrapper(
fmt.Sprintf("sudo chroot %s '%s'", c.Chroot, cmd.Command))
if err != nil {
return err
}
localCmd := ShellCommand(command)
localCmd.Stdin = cmd.Stdin localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr localCmd.Stderr = cmd.Stderr
@ -66,41 +69,25 @@ func (c *Communicator) Upload(dst string, r io.Reader) error {
} }
defer os.Remove(tf.Name()) defer os.Remove(tf.Name())
io.Copy(tf, r) io.Copy(tf, r)
cpCmd := fmt.Sprintf("cp %s %s", tf.Name(), dst)
return (c.WrappedCommand(cpCmd)).Run() cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst))
if err != nil {
return err
}
return ShellCommand(cpCmd).Run()
} }
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
/*
walkFn := func(fullPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
path, err := filepath.Rel(src, fullPath)
if err != nil {
return err
}
for _, e := range exclude {
if e == path {
log.Printf("Skipping excluded file: %s", path)
return nil
}
}
chrootDest := filepath.Join(c.Chroot, dst, path)
log.Printf("Uploading dir %s to chroot dir: %s", src, dst)
cpCmd := fmt.Sprintf("cp %s %s", fullPath, chrootDest)
return c.WrappedCommand(cpCmd).Run()
}
*/
// TODO: remove any file copied if it appears in `exclude` // TODO: remove any file copied if it appears in `exclude`
chrootDest := filepath.Join(c.Chroot, dst) chrootDest := filepath.Join(c.Chroot, dst)
log.Printf("Uploading directory '%s' to '%s'", src, chrootDest) log.Printf("Uploading directory '%s' to '%s'", src, chrootDest)
cpCmd := fmt.Sprintf("cp -R %s* %s", src, chrootDest) cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s* %s", src, chrootDest))
return c.WrappedCommand(cpCmd).Run() if err != nil {
return err
}
return ShellCommand(cpCmd).Run()
} }
func (c *Communicator) Download(src string, w io.Writer) error { func (c *Communicator) Download(src string, w io.Writer) error {

View File

@ -1,19 +1 @@
package chroot package chroot
import (
"fmt"
"log"
"os/exec"
)
func ChrootCommand(chroot string, command string) *exec.Cmd {
cmd := fmt.Sprintf("sudo chroot %s", chroot)
return ShellCommand(cmd, command)
}
func ShellCommand(commands ...string) *exec.Cmd {
cmds := append([]string{"-c"}, commands...)
cmd := exec.Command("/bin/sh", cmds...)
log.Printf("ShellCommand: %s %v", cmd.Path, cmd.Args[1:])
return cmd
}

View File

@ -4,7 +4,6 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log" "log"
"os/exec"
) )
// StepChrootProvision provisions the instance within a chroot. // StepChrootProvision provisions the instance within a chroot.
@ -16,16 +15,12 @@ func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction
hook := state.Get("hook").(packer.Hook) hook := state.Get("hook").(packer.Hook)
mountPath := state.Get("mount_path").(string) mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(Command) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
chrootCmd := func(command string) *exec.Cmd {
return ChrootCommand(mountPath, command)
}
// Create our communicator // Create our communicator
comm := &Communicator{ comm := &Communicator{
Chroot: mountPath, Chroot: mountPath,
ChrootCmd: chrootCmd, CmdWrapper: wrappedCommand,
WrappedCommand: wrappedCommand,
} }
// Provision // Provision

View File

@ -22,7 +22,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string) mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(Command) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
s.files = make([]string, 0, len(config.CopyFiles)) s.files = make([]string, 0, len(config.CopyFiles))
@ -33,8 +33,16 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
chrootPath := filepath.Join(mountPath, path) chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath) log.Printf("Copying '%s' to '%s'", path, chrootPath)
cmd := wrappedCommand(fmt.Sprintf("cp %s %s", path, chrootPath)) cmdText, err := wrappedCommand(fmt.Sprintf("cp %s %s", path, chrootPath))
if err != nil {
err := fmt.Errorf("Error building copy command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
stderr.Reset() stderr.Reset()
cmd := ShellCommand(cmdText)
cmd.Stderr = stderr cmd.Stderr = stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
err := fmt.Errorf( err := fmt.Errorf(
@ -60,11 +68,16 @@ func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
} }
func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error { func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
wrappedCommand := state.Get("wrappedCommand").(Command) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if s.files != nil { if s.files != nil {
for _, file := range s.files { for _, file := range s.files {
log.Printf("Removing: %s", file) log.Printf("Removing: %s", file)
localCmd := wrappedCommand(fmt.Sprintf("rm -f %s", file)) localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file))
if err != nil {
return err
}
localCmd := ShellCommand(localCmdText)
if err := localCmd.Run(); err != nil { if err := localCmd.Run(); err != nil {
return err return err
} }

View File

@ -27,6 +27,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string) device := state.Get("device").(string)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{ mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device), Device: filepath.Base(device),
@ -58,9 +59,16 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Mounting the root device...") ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
mountCommand := fmt.Sprintf("mount %s %s", device, mountPath) mountCommand, err := wrappedCommand(
wrappedCommand := state.Get("wrappedCommand").(Command) fmt.Sprintf("mount %s %s", device, mountPath))
cmd := wrappedCommand(mountCommand) if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
cmd.Stderr = stderr cmd.Stderr = stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
err := fmt.Errorf( err := fmt.Errorf(
@ -91,11 +99,15 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
} }
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting the root device...") wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
unmountCommand := fmt.Sprintf("umount %s", s.mountPath) ui.Say("Unmounting the root device...")
wrappedCommand := state.Get("wrappedCommand").(Command) unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
cmd := wrappedCommand(unmountCommand) if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
cmd := ShellCommand(unmountCommand)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Error unmounting root device: %s", err) return fmt.Errorf("Error unmounting root device: %s", err)
} }

View File

@ -20,7 +20,7 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string) mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(Command) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
s.mounts = make([]string, 0, len(config.ChrootMounts)) s.mounts = make([]string, 0, len(config.ChrootMounts))
@ -42,12 +42,19 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2])) ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
mountCommand := fmt.Sprintf( mountCommand, err := wrappedCommand(fmt.Sprintf(
"mount %s %s %s", "mount %s %s %s",
flags, flags,
mountInfo[1], mountInfo[1],
innerPath) innerPath))
cmd := wrappedCommand(mountCommand) if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
cmd.Stderr = stderr cmd.Stderr = stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
err := fmt.Errorf( err := fmt.Errorf(
@ -78,15 +85,18 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
return nil return nil
} }
wrappedCommand := state.Get("wrappedCommand").(Command) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
for len(s.mounts) > 0 { for len(s.mounts) > 0 {
var path string var path string
lastIndex := len(s.mounts) - 1 lastIndex := len(s.mounts) - 1
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex] path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
unmountCommand := fmt.Sprintf("umount %s", path) unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
cmd := wrappedCommand(unmountCommand) cmd := ShellCommand(unmountCommand)
cmd.Stderr = stderr cmd.Stderr = stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf( return fmt.Errorf(