add split boot partiton support

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
Adphi 2023-02-22 17:26:32 +01:00 committed by Adphi
parent 490f235c6d
commit 532ee3f1a3
11 changed files with 168 additions and 41 deletions

View File

@ -38,7 +38,9 @@ RUN apt-get update && \
mount \
tar \
extlinux \
qemu-utils
qemu-utils && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/

View File

@ -154,6 +154,7 @@ Usage:
Flags:
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-size uint Size of the boot partition in MB (default 100)
--force Override output qcow2 image
-h, --help help for convert
--network-manager string Network manager to use for the image: none, netplan, ifupdown
@ -163,6 +164,7 @@ Flags:
--push Push the container disk image to the registry
--raw Just convert the container to virtual machine image without installing anything more
-s, --size string The output image size (default "10G")
--split-boot Split the boot partition from the root partition
-t, --tag string Container disk Docker image tag
Global Flags:
@ -305,6 +307,7 @@ Usage:
Flags:
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-size uint Size of the boot partition in MB (default 100)
--build-arg stringArray Set build-time variables
-f, --file string Name of the Dockerfile
--force Override output qcow2 image
@ -315,6 +318,7 @@ Flags:
--push Push the container disk image to the registry
--raw Just convert the container to virtual machine image without installing anything more
-s, --size string The output image size (default "10G")
--split-boot Split the boot partition from the root partition
-t, --tag string Container disk Docker image tag
Global Flags:

View File

@ -121,18 +121,24 @@ type builder struct {
diskOut string
format string
size int64
size uint64
mntPoint string
splitBoot bool
bootSize uint64
mbrPath string
loDevice string
loPart string
diskUUD string
loDevice string
bootPart string
rootPart string
bootUUID string
rootUUID string
cmdLineExtra string
}
func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size int64, osRelease OSRelease, format string, cmdLineExtra string) (Builder, error) {
func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootSize uint64) (Builder, error) {
if err := checkDependencies(); err != nil {
return nil, err
}
@ -147,6 +153,14 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size int64, o
return nil, fmt.Errorf("invalid format: %s valid formats are: %s", f, strings.Join(formats, " "))
}
if splitBoot && bootSize < 50 {
return nil, fmt.Errorf("boot partition size must be at least 50MiB")
}
if splitBoot && bootSize >= size {
return nil, fmt.Errorf("boot partition size must be less than the disk size")
}
mbrBin := ""
for _, v := range mbrPaths {
if _, err := os.Stat(v); err == nil {
@ -158,7 +172,7 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size int64, o
return nil, fmt.Errorf("unable to find syslinux's mbr.bin path")
}
if size == 0 {
size = 10 * int64(datasize.GB)
size = 10 * uint64(datasize.GB)
}
if disk == "" {
disk = "disk0"
@ -186,6 +200,8 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size int64, o
mbrPath: mbrBin,
mntPoint: filepath.Join(workdir, "/mnt"),
cmdLineExtra: cmdLineExtra,
splitBoot: splitBoot,
bootSize: bootSize,
}
if err := os.MkdirAll(b.mntPoint, os.ModePerm); err != nil {
return nil, err
@ -252,9 +268,21 @@ func (b *builder) makeImg(ctx context.Context) error {
return err
}
if err := exec.Run(ctx, "parted", "-s", b.diskRaw, "mklabel", "msdos", "mkpart", "primary", "1Mib", "100%", "set", "1", "boot", "on"); err != nil {
var args []string
if b.splitBoot {
args = []string{"-s", b.diskRaw,
"mklabel", "msdos", "mkpart", "primary", "1Mib", fmt.Sprintf("%dMib", b.bootSize),
"mkpart", "primary", fmt.Sprintf("%dMib", b.bootSize), "100%",
"set", "1", "boot", "on",
}
} else {
args = []string{"-s", b.diskRaw, "mklabel", "msdos", "mkpart", "primary", "1Mib", "100%", "set", "1", "boot", "on"}
}
if err := exec.Run(ctx, "parted", args...); err != nil {
return err
}
return nil
}
@ -268,12 +296,29 @@ func (b *builder) mountImg(ctx context.Context) error {
if err := exec.Run(ctx, "kpartx", "-a", b.loDevice); err != nil {
return err
}
b.loPart = fmt.Sprintf("/dev/mapper/%sp1", filepath.Base(b.loDevice))
b.bootPart = fmt.Sprintf("/dev/mapper/%sp1", filepath.Base(b.loDevice))
if b.splitBoot {
b.rootPart = fmt.Sprintf("/dev/mapper/%sp2", filepath.Base(b.loDevice))
} else {
b.rootPart = b.bootPart
}
logrus.Infof("creating raw image file system")
if err := exec.Run(ctx, "mkfs.ext4", b.loPart); err != nil {
if err := exec.Run(ctx, "mkfs.ext4", b.rootPart); err != nil {
return err
}
if err := exec.Run(ctx, "mount", b.loPart, b.mntPoint); err != nil {
if err := exec.Run(ctx, "mount", b.rootPart, b.mntPoint); err != nil {
return err
}
if !b.splitBoot {
return nil
}
if err := os.MkdirAll(filepath.Join(b.mntPoint, "boot"), os.ModePerm); err != nil {
return err
}
if err := exec.Run(ctx, "mkfs.ext4", b.bootPart); err != nil {
return err
}
if err := exec.Run(ctx, "mount", b.bootPart, filepath.Join(b.mntPoint, "boot")); err != nil {
return err
}
return nil
@ -282,6 +327,11 @@ func (b *builder) mountImg(ctx context.Context) error {
func (b *builder) unmountImg(ctx context.Context) error {
logrus.Infof("unmounting raw image")
var merr error
if b.splitBoot {
if err := exec.Run(ctx, "umount", filepath.Join(b.mntPoint, "boot")); err != nil {
merr = multierr.Append(merr, err)
}
}
if err := exec.Run(ctx, "umount", b.mntPoint); err != nil {
merr = multierr.Append(merr, err)
}
@ -302,14 +352,28 @@ func (b *builder) copyRootFS(ctx context.Context) error {
return nil
}
func (b *builder) setupRootFS(ctx context.Context) error {
logrus.Infof("setting up rootfs")
o, _, err := exec.RunOut(ctx, "blkid", "-s", "UUID", "-o", "value", b.loPart)
func diskUUID(ctx context.Context, disk string) (string, error) {
o, _, err := exec.RunOut(ctx, "blkid", "-s", "UUID", "-o", "value", disk)
if err != nil {
return err
return "", err
}
return strings.TrimSuffix(o, "\n"), nil
}
func (b *builder) setupRootFS(ctx context.Context) (err error) {
logrus.Infof("setting up rootfs")
b.rootUUID, err = diskUUID(ctx, b.rootPart)
var fstab string
if b.splitBoot {
b.bootUUID, err = diskUUID(ctx, b.bootPart)
if err != nil {
return err
}
fstab = fmt.Sprintf("UUID=%s / ext4 errors=remount-ro 0 1\nUUID=%s /boot ext4 errors=remount-ro 0 2\n", b.rootUUID, b.bootUUID)
} else {
b.bootUUID = b.rootUUID
fstab = fmt.Sprintf("UUID=%s / ext4 errors=remount-ro 0 1\n", b.bootUUID)
}
b.diskUUD = strings.TrimSuffix(o, "\n")
fstab := fmt.Sprintf("UUID=%s / ext4 errors=remount-ro 0 1\n", b.diskUUD)
if err := b.chWriteFile("/etc/fstab", fstab, perm); err != nil {
return err
}
@ -329,21 +393,51 @@ func (b *builder) setupRootFS(ctx context.Context) error {
if err := os.RemoveAll(b.chPath("/.dockerenv")); err != nil {
return err
}
if b.osRelease.ID != ReleaseAlpine {
// create a symlink to /boot for non-alpine images in order to have a consistent path
// even if the image is not split
if _, err := os.Stat(b.chPath("/boot/boot")); os.IsNotExist(err) {
if err := os.Symlink(".", b.chPath("/boot/boot")); err != nil {
return err
}
}
switch b.osRelease.ID {
case ReleaseAlpine:
by, err := os.ReadFile(b.chPath("/etc/inittab"))
if err != nil {
return err
}
by = append(by, []byte("\n"+"ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100\n")...)
if err := b.chWriteFile("/etc/inittab", string(by), perm); err != nil {
return err
}
if err := b.chWriteFileIfNotExist("/etc/network/interfaces", "", perm); err != nil {
return err
}
return nil
case ReleaseUbuntu:
if b.osRelease.VersionID >= "20.04" {
return nil
}
fallthrough
case ReleaseDebian, ReleaseKali:
t, err := os.Readlink(b.chPath("/vmlinuz"))
if err != nil {
return err
}
if err := os.Symlink(t, b.chPath("/boot/vmlinuz")); err != nil {
return err
}
t, err = os.Readlink(b.chPath("/initrd.img"))
if err != nil {
return err
}
if err := os.Symlink(t, b.chPath("/boot/initrd.img")); err != nil {
return err
}
return nil
default:
return nil
}
by, err := os.ReadFile(b.chPath("/etc/inittab"))
if err != nil {
return err
}
by = append(by, []byte("\n"+"ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100\n")...)
if err := b.chWriteFile("/etc/inittab", string(by), perm); err != nil {
return err
}
if err := b.chWriteFileIfNotExist("/etc/network/interfaces", "", perm); err != nil {
return err
}
return nil
}
func (b *builder) installKernel(ctx context.Context) error {
@ -355,7 +449,7 @@ func (b *builder) installKernel(ctx context.Context) error {
if err != nil {
return err
}
if err := b.chWriteFile("/boot/syslinux.cfg", fmt.Sprintf(sysconfig, b.diskUUD, b.cmdLineExtra), perm); err != nil {
if err := b.chWriteFile("/boot/syslinux.cfg", fmt.Sprintf(sysconfig, b.rootUUID, b.cmdLineExtra), perm); err != nil {
return err
}
return nil
@ -393,13 +487,13 @@ func (b *builder) Close() error {
return b.img.Close()
}
func block(path string, size int64) error {
func block(path string, size uint64) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return f.Truncate(size)
return f.Truncate(int64(size))
}
func checkDependencies() error {

View File

@ -107,6 +107,8 @@ var (
d2vm.WithCmdLineExtra(cmdLineExtra),
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
d2vm.WithRaw(raw),
d2vm.WithSplitBoot(splitBoot),
d2vm.WithBootSize(bootSize),
); err != nil {
return err
}

View File

@ -95,6 +95,8 @@ var (
d2vm.WithCmdLineExtra(cmdLineExtra),
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
d2vm.WithRaw(raw),
d2vm.WithSplitBoot(splitBoot),
d2vm.WithBootSize(bootSize),
); err != nil {
return err
}
@ -109,12 +111,12 @@ var (
}
)
func parseSize(s string) (int64, error) {
func parseSize(s string) (uint64, error) {
var v datasize.ByteSize
if err := v.UnmarshalText([]byte(s)); err != nil {
return 0, err
}
return int64(v), nil
return uint64(v), nil
}
func init() {

View File

@ -33,6 +33,8 @@ var (
containerDiskTag = ""
push bool
networkManager string
splitBoot bool
bootSize uint64
)
func buildFlags() *pflag.FlagSet {
@ -46,5 +48,7 @@ func buildFlags() *pflag.FlagSet {
flags.BoolVar(&raw, "raw", false, "Just convert the container to virtual machine image without installing anything more")
flags.StringVarP(&containerDiskTag, "tag", "t", "", "Container disk Docker image tag")
flags.BoolVar(&push, "push", false, "Push the container disk image to the registry")
flags.BoolVar(&splitBoot, "split-boot", false, "Split the boot partition from the root partition")
flags.Uint64Var(&bootSize, "boot-size", 100, "Size of the boot partition in MB")
return flags
}

View File

@ -79,7 +79,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error {
if format == "" {
format = "raw"
}
b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra)
b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootSize)
if err != nil {
return err
}

View File

@ -17,15 +17,18 @@ package d2vm
type ConvertOption func(o *convertOptions)
type convertOptions struct {
size int64
size uint64
password string
output string
cmdLineExtra string
networkManager NetworkManager
raw bool
splitBoot bool
bootSize uint64
}
func WithSize(size int64) ConvertOption {
func WithSize(size uint64) ConvertOption {
return func(o *convertOptions) {
o.size = size
}
@ -60,3 +63,15 @@ func WithRaw(raw bool) ConvertOption {
o.raw = raw
}
}
func WithSplitBoot(b bool) ConvertOption {
return func(o *convertOptions) {
o.splitBoot = b
}
}
func WithBootSize(bootSize uint64) ConvertOption {
return func(o *convertOptions) {
o.bootSize = bootSize
}
}

View File

@ -10,6 +10,7 @@ d2vm build [context directory] [flags]
```
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-size uint Size of the boot partition in MB (default 100)
--build-arg stringArray Set build-time variables
-f, --file string Name of the Dockerfile
--force Override output qcow2 image
@ -20,6 +21,7 @@ d2vm build [context directory] [flags]
--push Push the container disk image to the registry
--raw Just convert the container to virtual machine image without installing anything more
-s, --size string The output image size (default "10G")
--split-boot Split the boot partition from the root partition
-t, --tag string Container disk Docker image tag
```

View File

@ -10,6 +10,7 @@ d2vm convert [docker image] [flags]
```
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-size uint Size of the boot partition in MB (default 100)
--force Override output qcow2 image
-h, --help help for convert
--network-manager string Network manager to use for the image: none, netplan, ifupdown
@ -19,6 +20,7 @@ d2vm convert [docker image] [flags]
--push Push the container disk image to the registry
--raw Just convert the container to virtual machine image without installing anything more
-s, --size string The output image size (default "10G")
--split-boot Split the boot partition from the root partition
-t, --tag string Container disk Docker image tag
```

View File

@ -6,14 +6,14 @@ RUN apk update --no-cache && \
apk add \
util-linux \
linux-virt \
{{ if ge .Release.VersionID "3.17" }} \
{{- if ge .Release.VersionID "3.17" }}
busybox-openrc \
busybox-mdev-openrc \
busybox-extras-openrc \
busybox-mdev-openrc \
{{ else }}
{{- else }}
busybox-initscripts \
{{ end }}
{{- end }}
openrc
RUN for s in bootmisc hostname hwclock modules networking swap sysctl urandom syslog; do rc-update add $s boot; done