From 364c415294e6841ff0d3c00f3fcf734bfd3f05ff Mon Sep 17 00:00:00 2001 From: Brad Ackerman Date: Sat, 1 Aug 2020 21:09:43 -0700 Subject: [PATCH] Add FreeBSD support. --- builder/azure/chroot/builder.go | 7 +- builder/azure/chroot/diskattacher.go | 118 +++++++------------ builder/azure/chroot/diskattacher_freebsd.go | 54 +++++++++ builder/azure/chroot/diskattacher_linux.go | 36 ++++++ builder/azure/chroot/step_mount_device.go | 9 +- common/chroot/step_copy_files.go | 13 +- 6 files changed, 158 insertions(+), 79 deletions(-) create mode 100644 builder/azure/chroot/diskattacher_freebsd.go create mode 100644 builder/azure/chroot/diskattacher_linux.go diff --git a/builder/azure/chroot/builder.go b/builder/azure/chroot/builder.go index ec4181fb8..4617ebe2f 100644 --- a/builder/azure/chroot/builder.go +++ b/builder/azure/chroot/builder.go @@ -391,8 +391,11 @@ func checkHyperVGeneration(s string) interface{} { } func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { - if runtime.GOOS != "linux" { - return nil, errors.New("the azure-chroot builder only works on Linux environments") + switch runtime.GOOS { + case "linux", "freebsd": + break + default: + return nil, errors.New("the azure-chroot builder only works on Linux and FreeBSD environments") } err := b.config.ClientConfig.FillParameters() diff --git a/builder/azure/chroot/diskattacher.go b/builder/azure/chroot/diskattacher.go index 6440491b7..619b9ea27 100644 --- a/builder/azure/chroot/diskattacher.go +++ b/builder/azure/chroot/diskattacher.go @@ -3,24 +3,18 @@ package chroot import ( "context" "errors" - "fmt" "log" - "os" - "path/filepath" "strings" - "syscall" "time" - "github.com/hashicorp/packer/builder/azure/common/client" - - "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute" + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" + "github.com/hashicorp/packer/builder/azure/common/client" ) type DiskAttacher interface { AttachDisk(ctx context.Context, disk string) (lun int32, err error) - DiskPathForLun(lun int32) string WaitForDevice(ctx context.Context, i int32) (device string, err error) DetachDisk(ctx context.Context, disk string) (err error) WaitForDetach(ctx context.Context, diskID string) error @@ -38,30 +32,53 @@ type diskAttacher struct { vm *client.ComputeInfo // store info about this VM so that we don't have to ask metadata service on every call } -func (diskAttacher) DiskPathForLun(lun int32) string { - return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun) -} +var DiskNotFoundError = errors.New("Disk not found") -func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) { - path := da.DiskPathForLun(lun) +func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) { + dataDisks, err := da.getDisks(ctx) + if err != nil { + return -1, err + } - for { - link, err := os.Readlink(path) - if err == nil { - return filepath.Abs("/dev/disk/azure/scsi1/" + link) - } else if err != os.ErrNotExist { - if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT { - return "", err - } + // check to see if disk is already attached, remember lun if found + if disk := findDiskInList(dataDisks, diskID); disk != nil { + // disk is already attached, just take this lun + if disk.Lun == nil { + return -1, errors.New("disk is attached, but lun was not set in VM model (possibly an error in the Azure APIs)") } + return to.Int32(disk.Lun), nil + } - select { - case <-time.After(100 * time.Millisecond): - // continue - case <-ctx.Done(): - return "", ctx.Err() + // disk was not found on VM, go and actually attach it + + var lun int32 = -1 +findFreeLun: + for lun = 0; lun < 64; lun++ { + for _, v := range dataDisks { + if to.Int32(v.Lun) == lun { + continue findFreeLun + } } + // no datadisk is using this lun + break + } + + // append new data disk to collection + dataDisks = append(dataDisks, compute.DataDisk{ + CreateOption: compute.DiskCreateOptionTypesAttach, + ManagedDisk: &compute.ManagedDiskParameters{ + ID: to.StringPtr(diskID), + }, + Lun: to.Int32Ptr(lun), + }) + + // prepare resource object for update operation + err = da.setDisks(ctx, dataDisks) + if err != nil { + return -1, err } + + return lun, nil } func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error { @@ -111,55 +128,6 @@ func (da *diskAttacher) WaitForDetach(ctx context.Context, diskID string) error } } -var DiskNotFoundError = errors.New("Disk not found") - -func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) { - dataDisks, err := da.getDisks(ctx) - if err != nil { - return -1, err - } - - // check to see if disk is already attached, remember lun if found - if disk := findDiskInList(dataDisks, diskID); disk != nil { - // disk is already attached, just take this lun - if disk.Lun == nil { - return -1, errors.New("disk is attached, but lun was not set in VM model (possibly an error in the Azure APIs)") - } - return to.Int32(disk.Lun), nil - } - - // disk was not found on VM, go and actually attach it - - var lun int32 = -1 -findFreeLun: - for lun = 0; lun < 64; lun++ { - for _, v := range dataDisks { - if to.Int32(v.Lun) == lun { - continue findFreeLun - } - } - // no datadisk is using this lun - break - } - - // append new data disk to collection - dataDisks = append(dataDisks, compute.DataDisk{ - CreateOption: compute.DiskCreateOptionTypesAttach, - ManagedDisk: &compute.ManagedDiskParameters{ - ID: to.StringPtr(diskID), - }, - Lun: to.Int32Ptr(lun), - }) - - // prepare resource object for update operation - err = da.setDisks(ctx, dataDisks) - if err != nil { - return -1, err - } - - return lun, nil -} - func (da *diskAttacher) getThisVM(ctx context.Context) (compute.VirtualMachine, error) { // getting resource info for this VM if da.vm == nil { diff --git a/builder/azure/chroot/diskattacher_freebsd.go b/builder/azure/chroot/diskattacher_freebsd.go new file mode 100644 index 000000000..bc9ffa20e --- /dev/null +++ b/builder/azure/chroot/diskattacher_freebsd.go @@ -0,0 +1,54 @@ +package chroot + +import ( + "bufio" + "bytes" + "context" + "fmt" + "os/exec" + "regexp" + "strings" + "time" +) + +func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) { + // This builder will always be running in Azure, where data disks show up + // on scbus5 target 0. The camcontrol command always outputs LUNs in + // unpadded hexadecimal format. + regexStr := fmt.Sprintf(`at scbus5 target 0 lun %x \(.*?da([\d]+)`, lun) + devRegex, err := regexp.Compile(regexStr) + if err != nil { + return "", err + } + for { + cmd := exec.Command("camcontrol", "devlist") + var out bytes.Buffer + cmd.Stdout = &out + err = cmd.Run() + if err != nil { + return "", err + } + outString := out.String() + scanner := bufio.NewScanner(strings.NewReader(outString)) + for scanner.Scan() { + line := scanner.Text() + // Check if this is the correct bus, target, and LUN. + if matches := devRegex.FindStringSubmatch(line); matches != nil { + // If this function immediately returns, devfs won't have + // created the device yet. + time.Sleep(100 * time.Millisecond) + return fmt.Sprintf("/dev/da%s", matches[1]), nil + } + } + if err = scanner.Err(); err != nil { + return "", err + } + + select { + case <-time.After(100 * time.Millisecond): + // continue + case <-ctx.Done(): + return "", ctx.Err() + } + } +} diff --git a/builder/azure/chroot/diskattacher_linux.go b/builder/azure/chroot/diskattacher_linux.go new file mode 100644 index 000000000..3d1403691 --- /dev/null +++ b/builder/azure/chroot/diskattacher_linux.go @@ -0,0 +1,36 @@ +package chroot + +import ( + "context" + "fmt" + "os" + "path/filepath" + "syscall" + "time" +) + +func diskPathForLun(lun int32) string { + return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun) +} + +func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) { + path := diskPathForLun(lun) + + for { + link, err := os.Readlink(path) + if err == nil { + return filepath.Abs("/dev/disk/azure/scsi1/" + link) + } else if err != os.ErrNotExist { + if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT { + return "", err + } + } + + select { + case <-time.After(100 * time.Millisecond): + // continue + case <-ctx.Done(): + return "", ctx.Err() + } + } +} diff --git a/builder/azure/chroot/step_mount_device.go b/builder/azure/chroot/step_mount_device.go index e4fc02595..99fbb9d90 100644 --- a/builder/azure/chroot/step_mount_device.go +++ b/builder/azure/chroot/step_mount_device.go @@ -9,6 +9,7 @@ import ( "log" "os" "path/filepath" + "runtime" "strings" "github.com/hashicorp/packer/common" @@ -62,7 +63,13 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul return multistep.ActionHalt } - deviceMount := fmt.Sprintf("%s%s", device, s.MountPartition) + var deviceMount string + switch runtime.GOOS { + case "freebsd": + deviceMount = fmt.Sprintf("%sp%s", device, s.MountPartition) + default: + deviceMount = fmt.Sprintf("%s%s", device, s.MountPartition) + } state.Put("deviceMount", deviceMount) diff --git a/common/chroot/step_copy_files.go b/common/chroot/step_copy_files.go index 496578bfc..ecf011b38 100644 --- a/common/chroot/step_copy_files.go +++ b/common/chroot/step_copy_files.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "path/filepath" + "runtime" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/multistep" @@ -31,12 +32,22 @@ func (s *StepCopyFiles) Run(ctx context.Context, state multistep.StateBag) multi s.files = make([]string, 0, len(s.Files)) if len(s.Files) > 0 { ui.Say("Copying files from host to chroot...") + var removeDestinationOption string + switch runtime.GOOS { + case "freebsd": + // The -f option here is closer to GNU --remove-destination than + // what POSIX says -f should do. + removeDestinationOption = "-f" + default: + // This is the GNU binutils version. + removeDestinationOption = "--remove-destination" + } for _, path := range s.Files { ui.Message(path) chrootPath := filepath.Join(mountPath, path) log.Printf("Copying '%s' to '%s'", path, chrootPath) - cmdText, err := wrappedCommand(fmt.Sprintf("cp --remove-destination %s %s", path, chrootPath)) + cmdText, err := wrappedCommand(fmt.Sprintf("cp %s %s %s", removeDestinationOption, path, chrootPath)) if err != nil { err := fmt.Errorf("Error building copy command: %s", err) state.Put("error", err)