[lxc-devel] [distrobuilder/master] Add VM support

monstermunchkin on Github lxc-bot at linuxcontainers.org
Mon Nov 25 16:37:50 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 310 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191125/ed7d67a9/attachment.bin>
-------------- next part --------------
From 42cc5df8b5b4f51559d236daa5e6a8caca82e646 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 29 Oct 2019 13:38:12 +0100
Subject: [PATCH 1/3] shared: Export ChrootMount struct

This exports the ChrootMount struct.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/chroot.go | 33 +++++++++++++++++----------------
 1 file changed, 17 insertions(+), 16 deletions(-)

diff --git a/shared/chroot.go b/shared/chroot.go
index eefdeab..a6ad591 100644
--- a/shared/chroot.go
+++ b/shared/chroot.go
@@ -12,19 +12,20 @@ import (
 	lxd "github.com/lxc/lxd/shared"
 )
 
-type chrootMount struct {
-	source string
-	target string
-	fstype string
-	flags  uintptr
-	data   string
-	isDir  bool
+// ChrootMount defines mount args.
+type ChrootMount struct {
+	Source string
+	Target string
+	FSType string
+	Flags  uintptr
+	Data   string
+	IsDir  bool
 }
 
 // ActiveChroots is a map of all active chroots and their exit functions
 var ActiveChroots = make(map[string]func() error)
 
-func setupMounts(rootfs string, mounts []chrootMount) error {
+func setupMounts(rootfs string, mounts []ChrootMount) error {
 	// Create a temporary mount path
 	err := os.MkdirAll(filepath.Join(rootfs, ".distrobuilder"), 0700)
 	if err != nil {
@@ -36,7 +37,7 @@ func setupMounts(rootfs string, mounts []chrootMount) error {
 		tmpTarget := filepath.Join(rootfs, ".distrobuilder", fmt.Sprintf("%d", i))
 
 		// Create the target mountpoint
-		if mount.isDir {
+		if mount.IsDir {
 			err := os.MkdirAll(tmpTarget, 0755)
 			if err != nil {
 				return err
@@ -49,22 +50,22 @@ func setupMounts(rootfs string, mounts []chrootMount) error {
 		}
 
 		// Mount to the temporary path
-		err := syscall.Mount(mount.source, tmpTarget, mount.fstype, mount.flags, mount.data)
+		err := syscall.Mount(mount.Source, tmpTarget, mount.FSType, mount.Flags, mount.Data)
 		if err != nil {
-			return fmt.Errorf("Failed to mount '%s': %s", mount.source, err)
+			return fmt.Errorf("Failed to mount '%s': %s", mount.Source, err)
 		}
 	}
 
 	return nil
 }
 
-func moveMounts(mounts []chrootMount) error {
+func moveMounts(mounts []ChrootMount) error {
 	for i, mount := range mounts {
 		// Source path
 		tmpSource := filepath.Join("/", ".distrobuilder", fmt.Sprintf("%d", i))
 
 		// Resolve symlinks
-		target := mount.target
+		target := mount.Target
 		for {
 			// Get information on current target
 			fi, err := os.Lstat(target)
@@ -93,7 +94,7 @@ func moveMounts(mounts []chrootMount) error {
 		}
 
 		// Create target path
-		if mount.isDir {
+		if mount.IsDir {
 			err = os.MkdirAll(target, 0755)
 			if err != nil {
 				return err
@@ -108,7 +109,7 @@ func moveMounts(mounts []chrootMount) error {
 		// Move the mount to its destination
 		err = syscall.Mount(tmpSource, target, "", syscall.MS_MOVE, "")
 		if err != nil {
-			return fmt.Errorf("Failed to mount '%s': %s", mount.source, err)
+			return fmt.Errorf("Failed to mount '%s': %s", mount.Source, err)
 		}
 	}
 
@@ -159,7 +160,7 @@ func SetupChroot(rootfs string, envs DefinitionEnv) (func() error, error) {
 	}
 
 	// Setup all other needed mounts
-	mounts := []chrootMount{
+	mounts := []ChrootMount{
 		{"none", "/proc", "proc", 0, "", true},
 		{"none", "/sys", "sysfs", 0, "", true},
 		{"/dev", "/dev", "", syscall.MS_BIND, "", true},

From 8378262caeaaaf8cd9dc285efda1fffab5fd2aac Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 29 Oct 2019 15:07:57 +0100
Subject: [PATCH 2/3] *: Pass custom mounts to SetupChroot()

This allows additional mounts to be passed to SetupChroot()

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 distrobuilder/main_lxc.go   |  2 +-
 distrobuilder/main_lxd.go   |  2 +-
 shared/chroot.go            | 10 +++++++---
 sources/alpine-http.go      |  2 +-
 sources/centos-http.go      |  4 ++--
 sources/oraclelinux-http.go |  2 +-
 sources/ubuntu-http.go      |  2 +-
 7 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/distrobuilder/main_lxc.go b/distrobuilder/main_lxc.go
index 0b8fb77..9ce050e 100644
--- a/distrobuilder/main_lxc.go
+++ b/distrobuilder/main_lxc.go
@@ -60,7 +60,7 @@ func (c *cmdLXC) run(cmd *cobra.Command, args []string) error {
 	}
 
 	exitChroot, err := shared.SetupChroot(c.global.sourceDir,
-		c.global.definition.Environment)
+		c.global.definition.Environment, nil)
 	if err != nil {
 		return err
 	}
diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go
index 9aea6dc..73596bb 100644
--- a/distrobuilder/main_lxd.go
+++ b/distrobuilder/main_lxd.go
@@ -85,7 +85,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string) error {
 	}
 
 	exitChroot, err := shared.SetupChroot(c.global.sourceDir,
-		c.global.definition.Environment)
+		c.global.definition.Environment, nil)
 	if err != nil {
 		return err
 	}
diff --git a/shared/chroot.go b/shared/chroot.go
index a6ad591..1c652b3 100644
--- a/shared/chroot.go
+++ b/shared/chroot.go
@@ -152,7 +152,7 @@ func killChrootProcesses(rootfs string) error {
 }
 
 // SetupChroot sets up mount and files, a reverter and then chroots for you
-func SetupChroot(rootfs string, envs DefinitionEnv) (func() error, error) {
+func SetupChroot(rootfs string, envs DefinitionEnv, m []ChrootMount) (func() error, error) {
 	// Mount the rootfs
 	err := syscall.Mount(rootfs, rootfs, "", syscall.MS_BIND, "")
 	if err != nil {
@@ -181,7 +181,11 @@ func SetupChroot(rootfs string, envs DefinitionEnv) (func() error, error) {
 	}
 
 	// Setup all needed mounts in a temporary location
-	err = setupMounts(rootfs, mounts)
+	if m != nil && len(m) > 0 {
+		err = setupMounts(rootfs, append(mounts, m...))
+	} else {
+		err = setupMounts(rootfs, mounts)
+	}
 	if err != nil {
 		return nil, fmt.Errorf("Failed to mount filesystems: %v", err)
 	}
@@ -199,7 +203,7 @@ func SetupChroot(rootfs string, envs DefinitionEnv) (func() error, error) {
 	}
 
 	// Move all the mounts into place
-	err = moveMounts(mounts)
+	err = moveMounts(append(mounts, m...))
 	if err != nil {
 		return nil, err
 	}
diff --git a/sources/alpine-http.go b/sources/alpine-http.go
index b32ea1d..1429459 100644
--- a/sources/alpine-http.go
+++ b/sources/alpine-http.go
@@ -98,7 +98,7 @@ func (s *AlpineLinuxHTTP) Run(definition shared.Definition, rootfsDir string) er
 	// Handle edge builds
 	if definition.Image.Release == "edge" {
 		// Upgrade to edge
-		exitChroot, err := shared.SetupChroot(rootfsDir, definition.Environment)
+		exitChroot, err := shared.SetupChroot(rootfsDir, definition.Environment, nil)
 		if err != nil {
 			return err
 		}
diff --git a/sources/centos-http.go b/sources/centos-http.go
index ea533b7..a16bb38 100644
--- a/sources/centos-http.go
+++ b/sources/centos-http.go
@@ -158,7 +158,7 @@ func (s CentOSHTTP) unpackRaw(filePath, rootfsDir string) error {
 	}
 
 	// Setup the mounts and chroot into the rootfs
-	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{})
+	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{}, nil)
 	if err != nil {
 		return fmt.Errorf("Failed to setup chroot: %s", err)
 	}
@@ -285,7 +285,7 @@ func (s CentOSHTTP) unpackISO(filePath, rootfsDir string) error {
 	}
 
 	// Setup the mounts and chroot into the rootfs
-	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{})
+	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{}, nil)
 	if err != nil {
 		return fmt.Errorf("Failed to setup chroot: %s", err)
 	}
diff --git a/sources/oraclelinux-http.go b/sources/oraclelinux-http.go
index 4906455..6d21904 100644
--- a/sources/oraclelinux-http.go
+++ b/sources/oraclelinux-http.go
@@ -169,7 +169,7 @@ func (s *OracleLinuxHTTP) unpackISO(latestUpdate, filePath, rootfsDir string) er
 	}
 
 	// Setup the mounts and chroot into the rootfs
-	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{})
+	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{}, nil)
 	if err != nil {
 		return fmt.Errorf("Failed to setup chroot: %s", err)
 	}
diff --git a/sources/ubuntu-http.go b/sources/ubuntu-http.go
index b82042a..c7ca68c 100644
--- a/sources/ubuntu-http.go
+++ b/sources/ubuntu-http.go
@@ -150,7 +150,7 @@ func (s *UbuntuHTTP) runCoreVariant(definition shared.Definition, rootfsDir stri
 		return err
 	}
 
-	exitChroot, err := shared.SetupChroot(baseImageDir, shared.DefinitionEnv{})
+	exitChroot, err := shared.SetupChroot(baseImageDir, shared.DefinitionEnv{}, nil)
 	if err != nil {
 		return err
 	}

From bdbdceea0963464e7d958a9389782ea01bb4af65 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 29 Oct 2019 09:12:53 +0100
Subject: [PATCH 3/3] distrobuilder: Add VM support

This adds VM support.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 distrobuilder/main.go     |  79 ++++++++++++++-
 distrobuilder/main_lxd.go |   4 +-
 distrobuilder/scripts.go  | 199 ++++++++++++++++++++++++++++++++++++++
 image/lxd.go              |  45 ++++++---
 image/lxd_test.go         |   6 +-
 5 files changed, 313 insertions(+), 20 deletions(-)
 create mode 100644 distrobuilder/scripts.go

diff --git a/distrobuilder/main.go b/distrobuilder/main.go
index 043d656..b1d0508 100644
--- a/distrobuilder/main.go
+++ b/distrobuilder/main.go
@@ -64,6 +64,7 @@ import (
 	"strings"
 	"time"
 
+	lxd "github.com/lxc/lxd/shared"
 	"github.com/spf13/cobra"
 	"gopkg.in/yaml.v2"
 
@@ -203,7 +204,6 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error {
 			return err
 		}
 	}
-
 	if cmd.CalledAs() == "build-dir" {
 		c.sourceDir = c.targetDir
 	} else {
@@ -230,6 +230,75 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error {
 		}
 	}
 
+	var mounts []shared.ChrootMount
+
+	isVM, err := cmd.Flags().GetBool("vm")
+	if err != nil {
+		return err
+	}
+
+	if isVM {
+		imgFile, err := shared.RenderTemplate(fmt.Sprintf("%s.img", c.definition.Image.Name), c.definition)
+		if err != nil {
+			return err
+		}
+
+		err = shared.RunScript(fmt.Sprintf(preVM, imgFile, c.sourceDir, c.flagCacheDir))
+		if err != nil {
+			return err
+		}
+		defer lxd.RunCommand("umount", "-R", c.sourceDir)
+
+	}
+
+	env := c.definition.Environment
+	envFile := filepath.Join(c.flagCacheDir, "env")
+	var loopDev string
+
+	if lxd.PathExists(envFile) {
+		f, err := os.Open(envFile)
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+
+		scanner := bufio.NewScanner(f)
+		for scanner.Scan() {
+			fields := strings.SplitN(scanner.Text(), "=", 2)
+
+			if len(fields) < 2 {
+				continue
+			}
+
+			// Remember this one as we need to clean up the loop device later.
+			if fields[0] == "DISTROBUILDER_LOOP_DEVICE" {
+				loopDev = fields[1]
+			}
+
+			env.EnvVariables = append(env.EnvVariables, shared.DefinitionEnvVars{
+				Key:   fields[0],
+				Value: fields[1],
+			})
+		}
+	}
+
+	// Clean up loop device.
+	if loopDev != "" {
+		defer shared.RunCommand("kpartx", "-d", loopDev)
+
+		uefiDev := fmt.Sprintf("/dev/mapper/%sp15", strings.TrimPrefix(loopDev, "/dev/"))
+		mounts = []shared.ChrootMount{
+			{
+				Source: uefiDev,
+				Target: "/boot/efi",
+				FSType: "vfat",
+				Flags:  0,
+				Data:   "",
+				IsDir:  true,
+			},
+		}
+	}
+
 	// Get the downloader to use for this image
 	downloader := sources.Get(c.definition.Source.Downloader)
 	if downloader == nil {
@@ -251,7 +320,7 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error {
 	}
 
 	// Setup the mounts and chroot into the rootfs
-	exitChroot, err := shared.SetupChroot(c.sourceDir, c.definition.Environment)
+	exitChroot, err := shared.SetupChroot(c.sourceDir, env, mounts)
 	if err != nil {
 		return fmt.Errorf("Failed to setup chroot: %s", err)
 	}
@@ -296,6 +365,12 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error {
 		}
 	}
 
+	// Run the post VM script which writes /etc/fstab, and runs grub-install.
+	err = shared.RunScript(postVM)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go
index 73596bb..8061a26 100644
--- a/distrobuilder/main_lxd.go
+++ b/distrobuilder/main_lxd.go
@@ -19,6 +19,7 @@ type cmdLXD struct {
 
 	flagType        string
 	flagCompression string
+	flagVM          bool
 }
 
 func (c *cmdLXD) commandBuild() *cobra.Command {
@@ -38,6 +39,7 @@ func (c *cmdLXD) commandBuild() *cobra.Command {
 
 	c.cmdBuild.Flags().StringVar(&c.flagType, "type", "split", "Type of tarball to create"+"``")
 	c.cmdBuild.Flags().StringVar(&c.flagCompression, "compression", "xz", "Type of compression to use"+"``")
+	c.cmdBuild.Flags().BoolVar(&c.flagVM, "vm", false, "Create a qcow2 image for VMs"+"``")
 
 	return c.cmdBuild
 }
@@ -101,7 +103,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string) error {
 
 	exitChroot()
 
-	err = img.Build(c.flagType == "unified", c.flagCompression)
+	err = img.Build(c.flagType == "unified", c.flagCompression, c.flagVM)
 	if err != nil {
 		return fmt.Errorf("Failed to create LXD image: %s", err)
 	}
diff --git a/distrobuilder/scripts.go b/distrobuilder/scripts.go
new file mode 100644
index 0000000..d50c325
--- /dev/null
+++ b/distrobuilder/scripts.go
@@ -0,0 +1,199 @@
+package main
+
+var preVM = `
+#!/bin/sh
+set -eux
+
+# default imagesize = 2252*1024**2 = 2.2G (the current size we ship)
+IMAGESIZE=2361393152
+DISK_IMAGE="%s"
+MOUNTPOINT="%s"
+CACHEDIR=%s
+FS_LABEL=rootfs
+ROOTFS_DEV_MAPPER=
+LOOP_DEVICE=
+
+create_empty_disk_image() {
+    # Prepare an empty disk image
+    dd if=/dev/zero of="$1" bs=1 count=0 seek="${IMAGESIZE}"
+}
+
+create_partitions() {
+    disk_image="$1"
+    sgdisk "${disk_image}" --zap-all
+	sgdisk "${disk_image}" \
+		--new=14::+4M \
+		--new=15::+106M \
+		--new=1::
+	sgdisk "${disk_image}" \
+		-t 14:ef02 \
+		-t 15:ef00
+    sgdisk "${disk_image}" \
+        --print
+}
+
+mount_image() {
+    #trap clean_loops EXIT
+    backing_img="$1"
+    local rootpart="$2"
+    kpartx_mapping="$(kpartx -s -v -a ${backing_img})"
+
+    # Find the loop device
+    loop_p1="$(echo ${kpartx_mapping} | awk '{print$3}')"
+    LOOP_DEVICE="/dev/${loop_p1%%p[0-9]*}"
+    if [ ! -b ${LOOP_DEVICE} ]; then
+        echo "unable to find loop device for ${backing_img}"
+        exit 1
+    fi
+
+    # Find the rootfs location
+    ROOTFS_DEV_MAPPER="/dev/mapper/${loop_p1%%%%[0-9]}${rootpart}"
+    if [ ! -b "${ROOTFS_DEV_MAPPER}" ]; then
+        echo "${ROOTFS_DEV_MAPPER} is not a block device";
+        exit 1
+    fi
+
+    # Add some information to the debug logs
+    echo "Mounted disk image ${backing_img} to ${ROOTFS_DEV_MAPPER}"
+    blkid ${ROOTFS_DEV_MAPPER}
+
+    return 0
+}
+
+make_ext4_partition() {
+    device="$1"
+    label=${FS_LABEL:+-L "${FS_LABEL}"}
+    mkfs.ext4 -F -b 4096 -i 8192 -m 0 ${label} -E resize=536870912 "$device"
+}
+
+create_and_mount_uefi_partition() {
+    uefi_dev="/dev/mapper/${LOOP_DEVICE#/dev/}p15"
+    mountpoint="$1"
+    mkfs.vfat -F 32 -n UEFI "${uefi_dev}"
+
+    mkdir -p "${mountpoint}"/boot/efi
+    mount "${uefi_dev}" "$mountpoint"/boot/efi
+}
+
+if [ -f "${DISK_IMAGE}" ]; then
+	rm "${DISK_IMAGE}"
+fi
+
+create_empty_disk_image "${DISK_IMAGE}"
+create_partitions "${DISK_IMAGE}"
+mount_image "${DISK_IMAGE}" 1
+
+PARTUUID=$(blkid -s PARTUUID -o value "$ROOTFS_DEV_MAPPER")
+
+make_ext4_partition "${ROOTFS_DEV_MAPPER}"
+mount "${ROOTFS_DEV_MAPPER}" "${MOUNTPOINT}"
+create_and_mount_uefi_partition ${MOUNTPOINT}
+
+# write useful env variables to cache file which will then be passed
+# to the actions
+echo DISTROBUILDER_FS_LABEL="${FS_LABEL}" >> "${CACHEDIR}"/env
+echo DISTROBUILDER_PARTUUID="${PARTUUID}" >> "${CACHEDIR}"/env
+echo DISTROBUILDER_LOOP_DEVICE="${LOOP_DEVICE}" >> "${CACHEDIR}"/env
+`
+
+var postVM = `
+#!/bin/sh
+set -eux
+
+fs_label="${DISTROBUILDER_FS_LABEL}"
+partuuid="${DISTROBUILDER_PARTUUID}"
+loop_device="${DISTROBUILDER_LOOP_DEVICE}"
+
+if [ -z "${fs_label}" ]; then
+  echo "Missing fs_label" >&2
+  exit 1
+elif [ -z "${partuuid}" ]; then
+  echo "Missing partuuid" >&2
+  exit 1
+elif [ -z "${loop_device}" ]; then
+  echo "Missing loop_device" >&2
+  exit 1
+fi
+
+divert_grub() {
+  # Don't divert all of grub-probe here; just the scripts we don't want
+  # running. Otherwise, you may be missing part-uuids for the search
+  # command, for example. ~cyphermox
+
+  dpkg-divert --local \
+	--divert /etc/grub.d/30_os-prober.dpkg-divert \
+	--rename /etc/grub.d/30_os-prober
+
+  # Divert systemd-detect-virt; /etc/kernel/postinst.d/zz-update-grub
+  # no-ops if we are in a container, and the launchpad farm runs builds
+  # in lxd.  We therefore pretend that we're never in a container (by
+  # exiting 1).
+  dpkg-divert --local \
+	--rename /usr/bin/systemd-detect-virt
+  echo "exit 1" > /usr/bin/systemd-detect-virt
+  chmod +x /usr/bin/systemd-detect-virt
+}
+
+undivert_grub() {
+  dpkg-divert --remove --local \
+	--divert /etc/grub.d/30_os-prober.dpkg-divert \
+	--rename /etc/grub.d/30_os-prober
+
+  rm /usr/bin/systemd-detect-virt
+  dpkg-divert --remove --local \
+	--rename /usr/bin/systemd-detect-virt
+}
+
+replace_grub_root_with_label() {
+	# When update-grub is run, it will detect the disks in the build system.
+	# Instead, we want grub to use the right labelled disk
+
+	# If boot by partuuid has been requested, don't override.
+	if [ -f /etc/default/grub.d/40-force-partuuid.cfg ] && \
+		  grep -q ^GRUB_FORCE_PARTUUID= /etc/default/grub.d/40-force-partuuid.cfg
+	then
+		return 0
+	fi
+	sed -i -e "s,root=[^ ]*,root=LABEL=${fs_label}," \
+		"/boot/grub/grub.cfg"
+}
+
+cat << EOF > /etc/fstab
+LABEL=rootfs  /         ext4  defaults  0 0
+LABEL=UEFI    /boot/efi vfat  defaults  0 0
+EOF
+
+if [ -n "$partuuid" ]; then
+	echo "GRUB_FORCE_PARTUUID=${partuuid}" >> /etc/default/grub.d/40-force-partuuid.cfg
+fi
+
+# Install bootloader
+efi_target=x86_64-efi
+
+grub-install "${loop_device}" \
+  --boot-directory=/boot \
+  --efi-directory=/boot/efi \
+  --target=${efi_target} \
+  --removable \
+  --uefi-secure-boot \
+  --no-nvram
+
+if [ -f /boot/efi/EFI/BOOT/grub.cfg ]; then
+	IMAGE_STR="# This file was created/modified by distrobuilder"
+	sed -i "s| root| root hd0,gpt1|" /boot/efi/EFI/BOOT/grub.cfg
+	sed -i "1i${IMAGE_STR}" /boot/efi/EFI/BOOT/grub.cfg
+	# For some reason the grub disk is looking for /boot/grub/grub.cfg on
+	# part 15....
+	mkdir -p /boot/efi/boot/grub
+	cp /boot/efi/EFI/BOOT/grub.cfg /boot/efi/boot/grub
+fi
+
+# Install the BIOS/GPT bits. Since GPT boots from the ESP partition,
+# it means that we just run this simple command and we're done
+grub-install --target=i386-pc "${loop_device}"
+
+divert_grub
+update-grub
+replace_grub_root_with_label
+undivert_grub
+`
diff --git a/image/lxd.go b/image/lxd.go
index 52400a5..2c65ddc 100644
--- a/image/lxd.go
+++ b/image/lxd.go
@@ -37,12 +37,17 @@ func NewLXDImage(sourceDir, targetDir, cacheDir string,
 }
 
 // Build creates a LXD image.
-func (l *LXDImage) Build(unified bool, compression string) error {
+func (l *LXDImage) Build(unified bool, compression string, vm bool) error {
 	err := l.createMetadata()
 	if err != nil {
 		return nil
 	}
 
+	if vm {
+		// If we're building a VM, we need the separate metadata tarball.
+		unified = false
+	}
+
 	file, err := os.Create(filepath.Join(l.cacheDir, "metadata.yaml"))
 	if err != nil {
 		return err
@@ -67,16 +72,16 @@ func (l *LXDImage) Build(unified bool, compression string) error {
 		paths = append(paths, "templates")
 	}
 
-	if unified {
-		var fname string
-		if l.definition.Image.Name != "" {
-			// Use a custom name for the unified tarball.
-			fname, _ = shared.RenderTemplate(l.definition.Image.Name, l.definition)
-		} else {
-			// Default name for the unified tarball.
-			fname = "lxd"
-		}
+	var fname string
+	if l.definition.Image.Name != "" {
+		// Use a custom name for the unified tarball.
+		fname, _ = shared.RenderTemplate(l.definition.Image.Name, l.definition)
+	} else {
+		// Default name for the unified tarball.
+		fname = "lxd"
+	}
 
+	if unified {
 		// Add the rootfs to the tarball, prefix all files with "rootfs"
 		err = shared.Pack(filepath.Join(l.targetDir, fmt.Sprintf("%s.tar", fname)),
 			"", l.sourceDir, "--transform", "s,^./,rootfs/,", ".")
@@ -91,13 +96,25 @@ func (l *LXDImage) Build(unified bool, compression string) error {
 			return err
 		}
 	} else {
-		// Create rootfs as squashfs.
-		err = shared.RunCommand("mksquashfs", l.sourceDir,
-			filepath.Join(l.targetDir, "rootfs.squashfs"), "-noappend", "-comp",
-			"xz", "-b", "1M", "-no-progress", "-no-recovery")
+		if vm {
+			// Create compressed qcow2 image.
+			err = shared.RunCommand("qemu-img", "convert", "-c", "-O", "qcow2", "-o", "compat=0.10",
+				filepath.Join(l.targetDir, fmt.Sprintf("%s.img", fname)),
+				filepath.Join(l.targetDir, fmt.Sprintf("%s.qcow2", fname)))
+		} else {
+			// Create rootfs as squashfs.
+			err = shared.RunCommand("mksquashfs", l.sourceDir,
+				filepath.Join(l.targetDir, "rootfs.squashfs"), "-noappend", "-comp",
+				"xz", "-b", "1M", "-no-progress", "-no-recovery")
+		}
 		if err != nil {
 			return err
 		}
+		defer func() {
+			if vm {
+				os.RemoveAll(filepath.Join(l.targetDir, fmt.Sprintf("%s.img", fname)))
+			}
+		}()
 
 		// Create metadata tarball.
 		err = shared.Pack(filepath.Join(l.targetDir, "lxd.tar"), compression,
diff --git a/image/lxd_test.go b/image/lxd_test.go
index d9efcd1..c5ce4db 100644
--- a/image/lxd_test.go
+++ b/image/lxd_test.go
@@ -76,7 +76,7 @@ func TestLXDBuild(t *testing.T) {
 
 func testLXDBuildSplitImage(t *testing.T, image *LXDImage) {
 	// Create split tarball and squashfs.
-	err := image.Build(false, "xz")
+	err := image.Build(false, "xz", false)
 	require.NoError(t, err)
 	defer func() {
 		os.Remove("lxd.tar.xz")
@@ -89,7 +89,7 @@ func testLXDBuildSplitImage(t *testing.T, image *LXDImage) {
 
 func testLXDBuildUnifiedImage(t *testing.T, image *LXDImage) {
 	// Create unified tarball with custom name.
-	err := image.Build(true, "xz")
+	err := image.Build(true, "xz", false)
 	require.NoError(t, err)
 	defer os.Remove("ubuntu-17.10-x86_64-testing.tar.xz")
 
@@ -97,7 +97,7 @@ func testLXDBuildUnifiedImage(t *testing.T, image *LXDImage) {
 
 	// Create unified tarball with default name.
 	image.definition.Image.Name = ""
-	err = image.Build(true, "xz")
+	err = image.Build(true, "xz", false)
 	require.NoError(t, err)
 	defer os.Remove("lxd.tar.xz")
 


More information about the lxc-devel mailing list