Merge pull request #9697 from backerman/personal/backerman/9695
Add FreeBSD support to azure/chroot
This commit is contained in:
commit
f8bb9008c0
|
@ -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()
|
||||
|
|
|
@ -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/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,31 +32,7 @@ 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)
|
||||
}
|
||||
|
||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||
path := da.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
var DiskNotFoundError = errors.New("Disk not found")
|
||||
|
||||
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
|
||||
log.Println("Fetching list of disks currently attached to VM")
|
||||
|
@ -111,8 +81,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 {
|
||||
|
|
|
@ -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(1000 * 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build !linux,!freebsd
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
|
||||
panic("The azure-chroot builder does not work on this platform.")
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package chroot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func TestStepMountDevice_Run(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
break
|
||||
default:
|
||||
t.Skip("Unsupported operating system")
|
||||
}
|
||||
mountPath, err := ioutil.TempDir("", "stepmountdevicetest")
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create a temporary directory: %q", err)
|
||||
}
|
||||
step := &StepMountDevice{
|
||||
MountOptions: []string{"foo"},
|
||||
MountPartition: "42",
|
||||
MountPath: mountPath,
|
||||
}
|
||||
|
||||
var gotCommand string
|
||||
var wrapper common.CommandWrapper
|
||||
wrapper = func(ran string) (string, error) {
|
||||
gotCommand = ran
|
||||
return "", nil
|
||||
}
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("wrappedCommand", wrapper)
|
||||
state.Put("device", "/dev/quux")
|
||||
|
||||
ui, getErrs := testUI()
|
||||
state.Put("ui", ui)
|
||||
|
||||
var config Config
|
||||
state.Put("config", &config)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
got := step.Run(ctx, state)
|
||||
if got != multistep.ActionContinue {
|
||||
t.Errorf("Expected 'continue', but got '%v'", got)
|
||||
}
|
||||
|
||||
var expectedMountDevice string
|
||||
switch runtime.GOOS {
|
||||
case "freebsd":
|
||||
expectedMountDevice = "/dev/quuxp42"
|
||||
default: // currently just Linux
|
||||
expectedMountDevice = "/dev/quux42"
|
||||
}
|
||||
expectedCommand := fmt.Sprintf("mount -o foo %s %s", expectedMountDevice, mountPath)
|
||||
if gotCommand != expectedCommand {
|
||||
t.Errorf("Expected '%v', but got '%v'", expectedCommand, gotCommand)
|
||||
}
|
||||
|
||||
os.Remove(mountPath)
|
||||
_ = getErrs
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -1,6 +1,30 @@
|
|||
package chroot
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// testUI returns a test ui plus a function to retrieve the errors written to the ui
|
||||
func testUI() (packer.Ui, func() string) {
|
||||
errorBuffer := &strings.Builder{}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: strings.NewReader(""),
|
||||
Writer: ioutil.Discard,
|
||||
ErrorWriter: errorBuffer,
|
||||
}
|
||||
return ui, errorBuffer.String
|
||||
}
|
||||
|
||||
func TestCopyFilesCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
|
@ -9,3 +33,95 @@ func TestCopyFilesCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
|||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFiles_Run(t *testing.T) {
|
||||
mountPath := "/mnt/abcde"
|
||||
copySource := "/etc/resolv.conf"
|
||||
copyDestination := path.Join(mountPath, "etc", "resolv.conf")
|
||||
|
||||
step := &StepCopyFiles{
|
||||
Files: []string{
|
||||
copySource,
|
||||
},
|
||||
}
|
||||
|
||||
var gotCommand string
|
||||
commandRunCount := 0
|
||||
var wrapper common.CommandWrapper
|
||||
wrapper = func(ran string) (string, error) {
|
||||
gotCommand = ran
|
||||
commandRunCount++
|
||||
return "", nil
|
||||
}
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("mount_path", mountPath)
|
||||
state.Put("wrappedCommand", wrapper)
|
||||
|
||||
ui, getErrs := testUI()
|
||||
state.Put("ui", ui)
|
||||
|
||||
var expectedCopyTemplate string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
expectedCopyTemplate = "cp --remove-destination %s %s"
|
||||
case "freebsd":
|
||||
expectedCopyTemplate = "cp -f %s %s"
|
||||
default:
|
||||
t.Skip("Unsupported operating system")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
got := step.Run(ctx, state)
|
||||
if got != multistep.ActionContinue {
|
||||
t.Errorf("Expected 'continue', but got '%v'", got)
|
||||
}
|
||||
|
||||
if commandRunCount != 1 {
|
||||
t.Errorf("Copy command should run exactly once but ran %v times", commandRunCount)
|
||||
}
|
||||
|
||||
expectedCopyCommand := fmt.Sprintf(expectedCopyTemplate, copySource, copyDestination)
|
||||
if gotCommand != expectedCopyCommand {
|
||||
t.Errorf("Expected command was '%v' but actual was '%v'", expectedCopyCommand, gotCommand)
|
||||
}
|
||||
|
||||
_ = getErrs
|
||||
}
|
||||
|
||||
func TestCopyFiles_CopyNothing(t *testing.T) {
|
||||
step := &StepCopyFiles{
|
||||
Files: []string{},
|
||||
}
|
||||
|
||||
commandRunCount := 0
|
||||
var wrapper common.CommandWrapper
|
||||
wrapper = func(ran string) (string, error) {
|
||||
commandRunCount++
|
||||
return "", nil
|
||||
}
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("mount_path", "/mnt/something")
|
||||
state.Put("wrappedCommand", wrapper)
|
||||
|
||||
ui, getErrs := testUI()
|
||||
state.Put("ui", ui)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
got := step.Run(ctx, state)
|
||||
if got != multistep.ActionContinue {
|
||||
t.Errorf("Expected 'continue', but got '%v'", got)
|
||||
}
|
||||
|
||||
if commandRunCount != 0 {
|
||||
t.Errorf("Copy command should not run but ran %v times", commandRunCount)
|
||||
}
|
||||
|
||||
_ = getErrs
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"variables": {},
|
||||
"builders": [
|
||||
{
|
||||
"type": "azure-chroot",
|
||||
"source": "thefreebsdfoundation:freebsd-12_1:12_1-release:latest",
|
||||
"image_resource_id": "/subscriptions/{{vm `subscription_id`}}/resourceGroups/{{vm `resource_group`}}/providers/Microsoft.Compute/images/freebsd-{{timestamp}}",
|
||||
"os_disk_size_gb": 64,
|
||||
"os_disk_storage_account_type": "Premium_LRS",
|
||||
"mount_partition": 2,
|
||||
"chroot_mounts": [
|
||||
["devfs", "devfs", "/dev"],
|
||||
["procfs", "procfs", "/proc"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"inline": [
|
||||
"env ASSUME_ALWAYS_YES=YES pkg bootstrap"
|
||||
],
|
||||
"inline_shebang": "/bin/sh -x",
|
||||
"type": "shell"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue