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) {
|
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||||
if runtime.GOOS != "linux" {
|
switch runtime.GOOS {
|
||||||
return nil, errors.New("the azure-chroot builder only works on Linux environments")
|
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()
|
err := b.config.ClientConfig.FillParameters()
|
||||||
|
|
|
@ -3,24 +3,18 @@ package chroot
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"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/services/compute/mgmt/2019-12-01/compute"
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
"github.com/Azure/go-autorest/autorest/azure"
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
|
"github.com/hashicorp/packer/builder/azure/common/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiskAttacher interface {
|
type DiskAttacher interface {
|
||||||
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
|
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
|
||||||
DiskPathForLun(lun int32) string
|
|
||||||
WaitForDevice(ctx context.Context, i int32) (device string, err error)
|
WaitForDevice(ctx context.Context, i int32) (device string, err error)
|
||||||
DetachDisk(ctx context.Context, disk string) (err error)
|
DetachDisk(ctx context.Context, disk string) (err error)
|
||||||
WaitForDetach(ctx context.Context, diskID string) 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
|
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 {
|
var DiskNotFoundError = errors.New("Disk not found")
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
|
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
|
||||||
log.Println("Fetching list of disks currently attached to VM")
|
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) {
|
func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) {
|
||||||
dataDisks, err := da.getDisks(ctx)
|
dataDisks, err := da.getDisks(ctx)
|
||||||
if err != nil {
|
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"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
|
@ -62,7 +63,13 @@ func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) mul
|
||||||
return multistep.ActionHalt
|
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)
|
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"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"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))
|
s.files = make([]string, 0, len(s.Files))
|
||||||
if len(s.Files) > 0 {
|
if len(s.Files) > 0 {
|
||||||
ui.Say("Copying files from host to chroot...")
|
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 {
|
for _, path := range s.Files {
|
||||||
ui.Message(path)
|
ui.Message(path)
|
||||||
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)
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error building copy command: %s", err)
|
err := fmt.Errorf("Error building copy command: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|
|
@ -1,6 +1,30 @@
|
||||||
package chroot
|
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) {
|
func TestCopyFilesCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||||
var raw interface{}
|
var raw interface{}
|
||||||
|
@ -9,3 +33,95 @@ func TestCopyFilesCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
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