From 997b81da21a293fc56eee8ae40538b360b7cc3c9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 30 Jul 2013 21:19:57 -0700 Subject: [PATCH] builder/amazon/chroot: find available device --- builder/amazon/chroot/builder.go | 20 +++---- builder/amazon/chroot/device.go | 61 ++++++++++++++++++++ builder/amazon/chroot/step_attach_volume.go | 11 ++-- builder/amazon/chroot/step_prepare_device.go | 45 +++++++++++++++ 4 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 builder/amazon/chroot/device.go create mode 100644 builder/amazon/chroot/step_prepare_device.go diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index aa598a5e1..1641cde57 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -24,14 +24,13 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` awscommon.AccessConfig `mapstructure:",squash"` - ChrootMounts [][]string `mapstructure:"chroot_mounts"` - CopyFiles []string `mapstructure:"copy_files"` - DevicePath string `mapstructure:"device_path"` - DevicePrefix string `mapstructure:"device_prefix"` - MountCommand string `mapstructure:"mount_command"` - MountPath string `mapstructure:"mount_path"` - SourceAmi string `mapstructure:"source_ami"` - UnmountCommand string `mapstructure:"unmount_command"` + ChrootMounts [][]string `mapstructure:"chroot_mounts"` + CopyFiles []string `mapstructure:"copy_files"` + DevicePath string `mapstructure:"device_path"` + MountCommand string `mapstructure:"mount_command"` + MountPath string `mapstructure:"mount_path"` + SourceAmi string `mapstructure:"source_ami"` + UnmountCommand string `mapstructure:"unmount_command"` } type Builder struct { @@ -68,10 +67,6 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.CopyFiles = []string{"/etc/resolv.conf"} } - if b.config.DevicePath == "" { - b.config.DevicePath = "/dev/sdh" - } - if b.config.MountCommand == "" { b.config.MountCommand = "mount" } @@ -128,6 +123,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps := []multistep.Step{ &StepInstanceInfo{}, &StepSourceAMIInfo{}, + &StepPrepareDevice{}, &StepCreateVolume{}, &StepAttachVolume{}, &StepMountDevice{}, diff --git a/builder/amazon/chroot/device.go b/builder/amazon/chroot/device.go new file mode 100644 index 000000000..ed5be6194 --- /dev/null +++ b/builder/amazon/chroot/device.go @@ -0,0 +1,61 @@ +package chroot + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" +) + +// AvailableDevice finds an available device and returns it. Note that +// you should externally hold a flock or something in order to guarantee +// that this device is available across processes. +func AvailableDevice() (string, error) { + prefix, err := devicePrefix() + if err != nil { + return "", err + } + + letters := "fghijklmnop" + for _, letter := range letters { + for i := 1; i < 16; i++ { + device := fmt.Sprintf("/dev/%s%c%d", prefix, letter, i) + if _, err := os.Stat(device); err != nil { + return device, nil + } + } + } + + return "", errors.New("available device could not be found") +} + +// devicePrefix returns the prefix ("sd" or "xvd" or so on) of the devices +// on the system. +func devicePrefix() (string, error) { + available := []string{"sd", "xvd"} + + f, err := os.Open("/sys/block") + if err != nil { + return "", err + } + defer f.Close() + + dirs, err := f.Readdirnames(-1) + if dirs != nil && len(dirs) > 0 { + for _, dir := range dirs { + dirBase := filepath.Base(dir) + for _, prefix := range available { + if strings.HasPrefix(dirBase, prefix) { + return prefix, nil + } + } + } + } + + if err != nil { + return "", err + } + + return "", errors.New("device prefix could not be detected") +} diff --git a/builder/amazon/chroot/step_attach_volume.go b/builder/amazon/chroot/step_attach_volume.go index 5b9b2a5ba..051c0704c 100644 --- a/builder/amazon/chroot/step_attach_volume.go +++ b/builder/amazon/chroot/step_attach_volume.go @@ -7,6 +7,7 @@ import ( "github.com/mitchellh/multistep" awscommon "github.com/mitchellh/packer/builder/amazon/common" "github.com/mitchellh/packer/packer" + "strings" ) // StepAttachVolume attaches the previously created volume to an @@ -21,16 +22,17 @@ type StepAttachVolume struct { } func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepAction { - config := state["config"].(*Config) ec2conn := state["ec2"].(*ec2.EC2) + device := state["device"].(string) instance := state["instance"].(*ec2.Instance) ui := state["ui"].(packer.Ui) volumeId := state["volume_id"].(string) - device := config.DevicePath + // For the API call, it expects "sd" prefixed devices. + attachVolume := strings.Replace(device, "/xvd", "/sd", 1) - ui.Say("Attaching the root volume...") - _, err := ec2conn.AttachVolume(volumeId, instance.InstanceId, device) + ui.Say(fmt.Sprintf("Attaching the root volume to %s", attachVolume)) + _, err := ec2conn.AttachVolume(volumeId, instance.InstanceId, attachVolume) if err != nil { err := fmt.Errorf("Error attaching volume: %s", err) state["error"] = err @@ -70,7 +72,6 @@ func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepActio return multistep.ActionHalt } - state["device"] = device state["attach_cleanup"] = s return multistep.ActionContinue } diff --git a/builder/amazon/chroot/step_prepare_device.go b/builder/amazon/chroot/step_prepare_device.go new file mode 100644 index 000000000..114c0ce9d --- /dev/null +++ b/builder/amazon/chroot/step_prepare_device.go @@ -0,0 +1,45 @@ +package chroot + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "os" +) + +// StepPrepareDevice finds an available device and sets it. +type StepPrepareDevice struct { + mounts []string +} + +func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*Config) + ui := state["ui"].(packer.Ui) + + device := config.DevicePath + if device == "" { + var err error + log.Println("Device path not specified, searching for available device...") + device, err = AvailableDevice() + if err != nil { + err := fmt.Errorf("Error finding available device: %s", err) + state["error"] = err + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if _, err := os.Stat(device); err == nil { + err := fmt.Errorf("Device is in use: %s", device) + state["error"] = err + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Printf("Device: %s", device) + state["device"] = device + return multistep.ActionContinue +} + +func (s *StepPrepareDevice) Cleanup(state map[string]interface{}) {}