[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