Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

arm64 support with grub-efi #44

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ images
docs/build
docs-src
/completions
/cmd/d2vm/run/sparsecat-linux-amd64
/cmd/d2vm/run/sparsecat-linux-*
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ RUN apt-get update && \
dosfstools \
mount \
tar \
extlinux \
"$([ "$(uname -m)" = "x86_64" ] && echo extlinux)" \
cryptsetup-bin \
qemu-utils && \
apt-get clean && \
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,15 @@ Flags:
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-fs string Filesystem to use for the boot partition, ext4 or fat32
--boot-size uint Size of the boot partition in MB (default 100)
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi, defaults to syslinux on amd64 and grub-efi on arm64
--force Override output qcow2 image
-h, --help help for convert
--keep-cache Keep the images after the build
--luks-password string Password to use for the LUKS encrypted root partition. If not set, the root partition will not be encrypted
--network-manager string Network manager to use for the image: none, netplan, ifupdown
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
-p, --password string Optional root user password
--platform string Platform to use for the container disk image, linux/arm64 and linux/arm64 are supported (default "linux/amd64")
--pull Always pull docker image
--push Push the container disk image to the registry
--raw Just convert the container to virtual machine image without installing anything more
Expand Down Expand Up @@ -317,7 +318,7 @@ Flags:
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-fs string Filesystem to use for the boot partition, ext4 or fat32
--boot-size uint Size of the boot partition in MB (default 100)
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi, defaults to syslinux on amd64 and grub-efi on arm64
--build-arg stringArray Set build-time variables
-f, --file string Name of the Dockerfile
--force Override output qcow2 image
Expand All @@ -327,6 +328,8 @@ Flags:
--network-manager string Network manager to use for the image: none, netplan, ifupdown
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
-p, --password string Optional root user password
--platform string Platform to use for the container disk image, linux/arm64 and linux/arm64 are supported (default "linux/amd64")
--pull Always pull docker image
--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")
Expand Down
2 changes: 1 addition & 1 deletion bootloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func BootloaderByName(name string) (BootloaderProvider, error) {
}

type BootloaderProvider interface {
New(c Config, r OSRelease) (Bootloader, error)
New(c Config, r OSRelease, arch string) (Bootloader, error)
Name() string
}

Expand Down
27 changes: 21 additions & 6 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,18 @@ type builder struct {
luksPassword string

cmdLineExtra string
arch string
}

func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootFS BootFS, bootSize uint64, luksPassword string, bootLoader string) (Builder, error) {
if err := checkDependencies(); err != nil {
return nil, err
func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootFS BootFS, bootSize uint64, luksPassword string, bootLoader string, platform string) (Builder, error) {
var arch string
switch platform {
case "linux/amd64":
arch = "x86_64"
case "linux/arm64", "linux/aarch64":
arch = "arm64"
default:
return nil, fmt.Errorf("unexpected platform: %s, supported platforms: linux/amd64, linux/arm64", platform)
}
if luksPassword != "" {
if !splitBoot {
Expand Down Expand Up @@ -141,7 +148,7 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64,
if err != nil {
return nil, err
}
bl, err := blp.New(config, osRelease)
bl, err := blp.New(config, osRelease, arch)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -184,6 +191,10 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64,
bootSize: bootSize,
bootFS: bootFS,
luksPassword: luksPassword,
arch: arch,
}
if err := b.checkDependencies(); err != nil {
return nil, err
}
if err := os.MkdirAll(b.mntPoint, os.ModePerm); err != nil {
return nil, err
Expand Down Expand Up @@ -490,9 +501,13 @@ func block(path string, size uint64) error {
return f.Truncate(int64(size))
}

func checkDependencies() error {
func (b *builder) checkDependencies() error {
var merr error
for _, v := range []string{"mount", "blkid", "tar", "losetup", "parted", "kpartx", "qemu-img", "extlinux", "dd", "mkfs.ext4", "cryptsetup"} {
deps := []string{"mount", "blkid", "tar", "losetup", "parted", "kpartx", "qemu-img", "dd", "mkfs.ext4", "cryptsetup"}
if _, ok := b.bootloader.(*syslinux); ok {
deps = append(deps, "extlinux")
}
for _, v := range deps {
if _, err := exec2.LookPath(v); err != nil {
merr = multierr.Append(merr, err)
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/d2vm/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var (
file = filepath.Join(args[0], "Dockerfile")
}
logrus.Infof("building docker image from %s", file)
if err := docker.Build(cmd.Context(), tag, file, args[0], buildArgs...); err != nil {
if err := docker.Build(cmd.Context(), pull, tag, file, args[0], platform, buildArgs...); err != nil {
return err
}
if err := d2vm.Convert(
Expand All @@ -108,6 +108,8 @@ var (
d2vm.WithBootFS(d2vm.BootFS(bootFS)),
d2vm.WithLuksPassword(luksPassword),
d2vm.WithKeepCache(keepCache),
d2vm.WithPlatform(platform),
d2vm.WithPull(false),
); err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/d2vm/container_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func maybeMakeContainerDisk(ctx context.Context) error {
return nil
}
logrus.Infof("creating container disk image %s", containerDiskTag)
if err := d2vm.MakeContainerDisk(ctx, output, containerDiskTag); err != nil {
if err := d2vm.MakeContainerDisk(ctx, output, containerDiskTag, platform); err != nil {
return err
}
if !push {
Expand Down
5 changes: 3 additions & 2 deletions cmd/d2vm/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var (
}
if pull || !found {
logrus.Infof("pulling image %s", img)
if err := docker.Pull(cmd.Context(), img); err != nil {
if err := docker.Pull(cmd.Context(), platform, img); err != nil {
return err
}
}
Expand All @@ -89,6 +89,8 @@ var (
d2vm.WithBootFS(d2vm.BootFS(bootFS)),
d2vm.WithLuksPassword(luksPassword),
d2vm.WithKeepCache(keepCache),
d2vm.WithPlatform(platform),
d2vm.WithPull(pull),
); err != nil {
return err
}
Expand All @@ -112,7 +114,6 @@ func parseSize(s string) (uint64, error) {
}

func init() {
convertCmd.Flags().BoolVar(&pull, "pull", false, "Always pull docker image")
convertCmd.Flags().AddFlagSet(buildFlags())
rootCmd.AddCommand(convertCmd)
}
21 changes: 20 additions & 1 deletion cmd/d2vm/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,26 @@ var (
luksPassword string

keepCache bool
platform string
)

func validateFlags() error {
switch platform {
case "linux/amd64":
if bootloader == "" {
bootloader = "syslinux"
}
case "linux/arm64", "linux/aarch64":
platform = "linux/arm64"
if bootloader == "" {
bootloader = "grub-efi"
}
if bootloader != "grub-efi" {
return fmt.Errorf("unsupported bootloader for platform %s: %s, only grub-efi is supported", platform, bootloader)
}
default:
return fmt.Errorf("unexpected platform: %s, supported platforms: linux/amd64, linux/arm64", platform)
}
if luksPassword != "" && !splitBoot {
logrus.Warnf("luks password is set: enabling split boot")
splitBoot = true
Expand Down Expand Up @@ -94,8 +111,10 @@ func buildFlags() *pflag.FlagSet {
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")
flags.StringVar(&bootFS, "boot-fs", "", "Filesystem to use for the boot partition, ext4 or fat32")
flags.StringVar(&bootloader, "bootloader", "syslinux", "Bootloader to use: syslinux, grub, grub-bios, grub-efi")
flags.StringVar(&bootloader, "bootloader", "", "Bootloader to use: syslinux, grub, grub-bios, grub-efi, defaults to syslinux on amd64 and grub-efi on arm64")
flags.StringVar(&luksPassword, "luks-password", "", "Password to use for the LUKS encrypted root partition. If not set, the root partition will not be encrypted")
flags.BoolVar(&keepCache, "keep-cache", false, "Keep the images after the build")
flags.StringVar(&platform, "platform", d2vm.Arch, "Platform to use for the container disk image, linux/arm64 and linux/arm64 are supported")
flags.BoolVar(&pull, "pull", false, "Always pull docker image")
return flags
}
23 changes: 19 additions & 4 deletions cmd/d2vm/run/hetzner.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ func init() {
HetznerCmd.Flags().StringVarP(&hetznerSSHKeyPath, "ssh-key", "i", "", "d2vm image identity key")
HetznerCmd.Flags().BoolVar(&hetznerRemove, "rm", false, "remove server when done")
HetznerCmd.Flags().StringVarP(&hetznerServerName, "name", "n", "d2vm", "d2vm server name")
HetznerCmd.Flags().StringVarP(&hetznerVMType, "type", "t", hetznerVMType, "d2vm server type")
HetznerCmd.Flags().StringVarP(&hetznerDatacenter, "location", "l", hetznerDatacenter, "d2vm server location")
}

func Hetzner(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -113,10 +115,23 @@ func runHetzner(ctx context.Context, imgPath string, stdin io.Reader, stderr io.
if err != nil {
return err
}
img, _, err := c.Image.GetByName(ctx, serverImg)
arch := "amd64"
harch := hcloud.ArchitectureX86
if strings.HasPrefix(strings.ToLower(hetznerVMType), "cax") {
harch = hcloud.ArchitectureARM
arch = "arm64"
}
sparsecatBin, err := Sparsecat(arch)
if err != nil {
return err
}
imgs, _, err := c.Image.List(ctx, hcloud.ImageListOpts{Name: serverImg, Architecture: []hcloud.Architecture{harch}})
if err != nil {
return err
}
if len(imgs) == 0 {
return fmt.Errorf("no image found with name %s", serverImg)
}
l, _, err := c.Location.Get(ctx, hetznerDatacenter)
if err != nil {
return err
Expand All @@ -125,9 +140,9 @@ func runHetzner(ctx context.Context, imgPath string, stdin io.Reader, stderr io.
sres, _, err := c.Server.Create(ctx, hcloud.ServerCreateOpts{
Name: hetznerServerName,
ServerType: st,
Image: img,
Image: imgs[0],
Location: l,
StartAfterCreate: hcloud.Bool(false),
StartAfterCreate: hcloud.Ptr(false),
})
if err != nil {
return err
Expand Down Expand Up @@ -186,7 +201,7 @@ func runHetzner(ctx context.Context, imgPath string, stdin io.Reader, stderr io.
return err
}
defer f.Close()
if _, err := io.Copy(f, bytes.NewReader(sparsecatBinary)); err != nil {
if _, err := io.Copy(f, bytes.NewReader(sparsecatBin)); err != nil {
return err
}
if err := f.Close(); err != nil {
Expand Down
17 changes: 16 additions & 1 deletion cmd/d2vm/run/util.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//go:generate env GOOS=linux GOARCH=amd64 go build -o sparsecat-linux-amd64 github.com/svenwiltink/sparsecat/cmd/sparsecat
//go:generate env GOOS=linux GOARCH=arm64 go build -o sparsecat-linux-arm64 github.com/svenwiltink/sparsecat/cmd/sparsecat

// Copyright 2022 Linka Cloud All rights reserved.
//
Expand Down Expand Up @@ -33,7 +34,21 @@ import (
)

//go:embed sparsecat-linux-amd64
var sparsecatBinary []byte
var sparsecatAmdBinary []byte

//go:embed sparsecat-linux-arm64
var sparsecatArmBinary []byte

func Sparsecat(arch string) ([]byte, error) {
switch arch {
case "amd64":
return sparsecatAmdBinary, nil
case "arm64":
return sparsecatArmBinary, nil
default:
return nil, fmt.Errorf("unsupported architecture: %s", arch)
}
}

// Handle flags with multiple occurrences
type MultipleFlag []string
Expand Down
4 changes: 2 additions & 2 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

func testConfig(t *testing.T, ctx context.Context, name, img string, config Config, luks, grubBIOS, grubEFI bool) {
require.NoError(t, docker.Pull(ctx, img))
require.NoError(t, docker.Pull(ctx, Arch, img))
tmpPath := filepath.Join(os.TempDir(), "d2vm-tests", strings.NewReplacer(":", "-", ".", "-").Replace(name))
require.NoError(t, os.MkdirAll(tmpPath, 0755))
defer os.RemoveAll(tmpPath)
Expand All @@ -52,7 +52,7 @@ func testConfig(t *testing.T, ctx context.Context, name, img string, config Conf
require.NoError(t, d.Render(f))
imgUUID := uuid.New().String()
logrus.Infof("building kernel enabled image")
require.NoError(t, docker.Build(ctx, imgUUID, p, dir))
require.NoError(t, docker.Build(ctx, false, imgUUID, p, dir, Arch))
defer docker.Remove(ctx, imgUUID)
// we don't need to test the kernel location if grub is enabled
if grubBIOS || grubEFI {
Expand Down
4 changes: 2 additions & 2 deletions container_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ ADD --chown=%[1]d:%[1]d %[2]s /disk/
`
)

func MakeContainerDisk(ctx context.Context, path string, tag string) error {
func MakeContainerDisk(ctx context.Context, path string, tag string, platform string) error {
tmpPath := filepath.Join(os.TempDir(), "d2vm", uuid.New().String())
if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil {
return err
Expand All @@ -60,7 +60,7 @@ func MakeContainerDisk(ctx context.Context, path string, tag string) error {
if err := os.WriteFile(dockerfile, []byte(dockerfileContent), os.ModePerm); err != nil {
return fmt.Errorf("failed to write dockerfile: %w", err)
}
if err := docker.Build(ctx, tag, dockerfile, tmpPath); err != nil {
if err := docker.Build(ctx, false, tag, dockerfile, tmpPath, platform); err != nil {
return fmt.Errorf("failed to build container disk: %w", err)
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error {
return err
}
logrus.Infof("building kernel enabled image")
if err := docker.Build(ctx, imgUUID, p, dir); err != nil {
if err := docker.Build(ctx, o.pull, imgUUID, p, dir, o.platform); err != nil {
return err
}
if !o.keepCache {
Expand All @@ -88,7 +88,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, o.splitBoot, o.bootFS, o.bootSize, o.luksPassword, o.bootLoader)
b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootFS, o.bootSize, o.luksPassword, o.bootLoader, o.platform)
if err != nil {
return err
}
Expand Down
14 changes: 14 additions & 0 deletions convert_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type convertOptions struct {
luksPassword string

keepCache bool
platform string
pull bool
}

func (o *convertOptions) hasGrubBIOS() bool {
Expand Down Expand Up @@ -113,3 +115,15 @@ func WithKeepCache(b bool) ConvertOption {
o.keepCache = b
}
}

func WithPlatform(platform string) ConvertOption {
return func(o *convertOptions) {
o.platform = platform
}
}

func WithPull(b bool) ConvertOption {
return func(o *convertOptions) {
o.pull = b
}
}
2 changes: 1 addition & 1 deletion docker_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ RUN rm -rf /etc/apk
require.NoError(t, os.WriteFile(filepath.Join(tmp, "hostname"), []byte("d2vm-flatten-test"), perm))
require.NoError(t, os.WriteFile(filepath.Join(tmp, "resolv.conf"), []byte("nameserver 8.8.8.8"), perm))
require.NoError(t, os.WriteFile(filepath.Join(tmp, "Dockerfile"), []byte(dockerfile), perm))
require.NoError(t, docker.Build(ctx, img, "", tmp))
require.NoError(t, docker.Build(ctx, false, img, "", tmp, "linux/amd64"))
defer docker.Remove(ctx, img)

imgTmp := filepath.Join(tmp, "image")
Expand Down
4 changes: 3 additions & 1 deletion docs/content/reference/d2vm_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ d2vm build [context directory] [flags]
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
--boot-fs string Filesystem to use for the boot partition, ext4 or fat32
--boot-size uint Size of the boot partition in MB (default 100)
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi, defaults to syslinux on amd64 and grub-efi on arm64
--build-arg stringArray Set build-time variables
-f, --file string Name of the Dockerfile
--force Override output qcow2 image
Expand All @@ -22,6 +22,8 @@ d2vm build [context directory] [flags]
--network-manager string Network manager to use for the image: none, netplan, ifupdown
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
-p, --password string Optional root user password
--platform string Platform to use for the container disk image, linux/arm64 and linux/arm64 are supported (default "linux/amd64")
--pull Always pull docker image
--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")
Expand Down
Loading
Loading