mirror of
https://github.com/linka-cloud/d2vm.git
synced 2024-11-21 23:36:25 +00:00
add grub-efi support
* tests: increase timeout * ci: split e2e tests Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
parent
d4c3476031
commit
a41bbdb745
62
.github/workflows/ci.yaml
vendored
62
.github/workflows/ci.yaml
vendored
@ -45,9 +45,17 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: git --no-pager diff --exit-code HEAD~1 HEAD **/**.go templates/ || make tests
|
run: git --no-pager diff --exit-code HEAD~1 HEAD **/**.go templates/ || make tests
|
||||||
|
|
||||||
e2e-tests:
|
templates-tests:
|
||||||
name: End to end Tests
|
name: Test Templates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
image:
|
||||||
|
- ubuntu
|
||||||
|
- debian
|
||||||
|
- kalilinux
|
||||||
|
- alpine
|
||||||
|
- centos
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -70,6 +78,53 @@ jobs:
|
|||||||
- name: Setup dependencies
|
- name: Setup dependencies
|
||||||
run: sudo apt update && sudo apt install -y util-linux udev parted e2fsprogs mount tar extlinux qemu-utils qemu-system
|
run: sudo apt update && sudo apt install -y util-linux udev parted e2fsprogs mount tar extlinux qemu-utils qemu-system
|
||||||
|
|
||||||
|
- name: Share cache with other actions
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
/tmp/.buildx-cache
|
||||||
|
key: ${{ runner.os }}-tests-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-tests-
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: git --no-pager diff --exit-code HEAD~1 HEAD **/**.go templates/ || IMAGE=${{ matrix.image }} make test-templates
|
||||||
|
|
||||||
|
|
||||||
|
e2e-tests:
|
||||||
|
name: End to end Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
image:
|
||||||
|
- alpine:3.17
|
||||||
|
- ubuntu:20.04
|
||||||
|
- ubuntu:22.04
|
||||||
|
- debian:10
|
||||||
|
- debian:11
|
||||||
|
- centos:8
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
# fetching all tags is required for the Makefile to compute the right version
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: "1.20"
|
||||||
|
|
||||||
|
- name: Set up QEMU dependency
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Setup dependencies
|
||||||
|
run: sudo apt update && sudo apt install -y util-linux udev parted e2fsprogs mount tar extlinux qemu-utils qemu-system ovmf
|
||||||
|
|
||||||
- name: Share cache with other actions
|
- name: Share cache with other actions
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
@ -81,7 +136,7 @@ jobs:
|
|||||||
${{ runner.os }}-tests-
|
${{ runner.os }}-tests-
|
||||||
|
|
||||||
- name: Run end-to-end tests
|
- name: Run end-to-end tests
|
||||||
run: make e2e
|
run: E2E_IMAGES=${{ matrix.image }} make e2e
|
||||||
|
|
||||||
docs-up-to-date:
|
docs-up-to-date:
|
||||||
name: Docs up to date
|
name: Docs up to date
|
||||||
@ -224,6 +279,7 @@ jobs:
|
|||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||||
needs:
|
needs:
|
||||||
- tests
|
- tests
|
||||||
|
- templates-tests
|
||||||
- docs-up-to-date
|
- docs-up-to-date
|
||||||
- build
|
- build
|
||||||
- e2e-tests
|
- e2e-tests
|
||||||
|
@ -39,7 +39,6 @@ RUN apt-get update && \
|
|||||||
mount \
|
mount \
|
||||||
tar \
|
tar \
|
||||||
extlinux \
|
extlinux \
|
||||||
grub2 \
|
|
||||||
cryptsetup-bin \
|
cryptsetup-bin \
|
||||||
qemu-utils && \
|
qemu-utils && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
|
9
Makefile
9
Makefile
@ -64,10 +64,15 @@ docker-run:
|
|||||||
.PHONY: tests
|
.PHONY: tests
|
||||||
tests:
|
tests:
|
||||||
@go generate ./...
|
@go generate ./...
|
||||||
@go list .| xargs go test -exec sudo -count=1 -timeout 20m -v
|
@go list .| xargs go test -exec sudo -count=1 -timeout 60m -v -skip TestConfig
|
||||||
|
|
||||||
|
.PHONY: test-templates
|
||||||
|
test-templates:
|
||||||
|
@go generate ./...
|
||||||
|
@go test -exec sudo -count=1 -timeout 60m -v -run TestConfig/$(IMAGE)
|
||||||
|
|
||||||
e2e: docker-build .build
|
e2e: docker-build .build
|
||||||
@go test -v -exec sudo -count=1 -timeout 60m -ldflags "-X '$(MODULE).Version=$(VERSION)' -X '$(MODULE).BuildDate=$(shell date)'" ./e2e
|
@go test -v -exec sudo -count=1 -timeout 60m -ldflags "-X '$(MODULE).Version=$(VERSION)' -X '$(MODULE).BuildDate=$(shell date)'" ./e2e -args -images $(E2E_IMAGES)
|
||||||
|
|
||||||
docs-up-to-date:
|
docs-up-to-date:
|
||||||
@$(MAKE) cli-docs
|
@$(MAKE) cli-docs
|
||||||
|
@ -61,7 +61,6 @@ Obviously, **Distroless** images are not supported.
|
|||||||
- mount
|
- mount
|
||||||
- tar
|
- tar
|
||||||
- extlinux (when using syslinux)
|
- extlinux (when using syslinux)
|
||||||
- grub2 (when using grub)
|
|
||||||
- qemu-utils
|
- qemu-utils
|
||||||
- cryptsetup (when using LUKS)
|
- cryptsetup (when using LUKS)
|
||||||
- [QEMU](https://www.qemu.org/download/#linux) (optional)
|
- [QEMU](https://www.qemu.org/download/#linux) (optional)
|
||||||
@ -161,7 +160,7 @@ Flags:
|
|||||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
--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-fs string Filesystem to use for the boot partition, ext4 or fat32
|
||||||
--boot-size uint Size of the boot partition in MB (default 100)
|
--boot-size uint Size of the boot partition in MB (default 100)
|
||||||
--bootloader string Bootloader to use: syslinux, grub (default "syslinux")
|
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
|
||||||
--force Override output qcow2 image
|
--force Override output qcow2 image
|
||||||
-h, --help help for convert
|
-h, --help help for convert
|
||||||
--keep-cache Keep the images after the build
|
--keep-cache Keep the images after the build
|
||||||
@ -318,7 +317,7 @@ Flags:
|
|||||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
--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-fs string Filesystem to use for the boot partition, ext4 or fat32
|
||||||
--boot-size uint Size of the boot partition in MB (default 100)
|
--boot-size uint Size of the boot partition in MB (default 100)
|
||||||
--bootloader string Bootloader to use: syslinux, grub (default "syslinux")
|
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
|
||||||
--build-arg stringArray Set build-time variables
|
--build-arg stringArray Set build-time variables
|
||||||
-f, --file string Name of the Dockerfile
|
-f, --file string Name of the Dockerfile
|
||||||
--force Override output qcow2 image
|
--force Override output qcow2 image
|
||||||
|
@ -38,5 +38,6 @@ type BootloaderProvider interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Bootloader interface {
|
type Bootloader interface {
|
||||||
|
Validate(fs BootFS) error
|
||||||
Setup(ctx context.Context, dev, root, cmdline string) error
|
Setup(ctx context.Context, dev, root, cmdline string) error
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bootFS == "" {
|
if bootFS == "" {
|
||||||
bootFS = FSExt4
|
bootFS = BootFSExt4
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bootFS.Validate(); err != nil {
|
if err := bootFS.Validate(); err != nil {
|
||||||
@ -146,6 +146,10 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := bl.Validate(bootFS); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = 10 * uint64(datasize.GB)
|
size = 10 * uint64(datasize.GB)
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,18 @@ func validateFlags() error {
|
|||||||
logrus.Warnf("boot filesystem is set: enabling split boot")
|
logrus.Warnf("boot filesystem is set: enabling split boot")
|
||||||
splitBoot = true
|
splitBoot = true
|
||||||
}
|
}
|
||||||
|
efi := bootloader == "grub-efi" || bootloader == "grub"
|
||||||
|
if efi && !splitBoot {
|
||||||
|
logrus.Warnf("grub-efi bootloader is set: enabling split boot")
|
||||||
|
splitBoot = true
|
||||||
|
}
|
||||||
|
if efi && bootFS != "" && bootFS != "fat32" {
|
||||||
|
return fmt.Errorf("grub-efi bootloader only supports fat32 boot filesystem")
|
||||||
|
}
|
||||||
|
if efi && bootFS == "" {
|
||||||
|
logrus.Warnf("grub-efi bootloader is set: enabling fat32 boot filesystem")
|
||||||
|
bootFS = "fat32"
|
||||||
|
}
|
||||||
if push && tag == "" {
|
if push && tag == "" {
|
||||||
return fmt.Errorf("tag is required when pushing container disk image")
|
return fmt.Errorf("tag is required when pushing container disk image")
|
||||||
}
|
}
|
||||||
@ -82,7 +94,7 @@ func buildFlags() *pflag.FlagSet {
|
|||||||
flags.BoolVar(&splitBoot, "split-boot", false, "Split the boot partition from the root partition")
|
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.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(&bootFS, "boot-fs", "", "Filesystem to use for the boot partition, ext4 or fat32")
|
||||||
flags.StringVar(&bootloader, "bootloader", "syslinux", "Bootloader to use: syslinux, grub")
|
flags.StringVar(&bootloader, "bootloader", "syslinux", "Bootloader to use: syslinux, grub, grub-bios, grub-efi")
|
||||||
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.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.BoolVar(&keepCache, "keep-cache", false, "Keep the images after the build")
|
||||||
return flags
|
return flags
|
||||||
|
@ -28,6 +28,7 @@ var (
|
|||||||
arch string
|
arch string
|
||||||
cpus uint
|
cpus uint
|
||||||
mem uint
|
mem uint
|
||||||
|
bios string
|
||||||
qemuCmd string
|
qemuCmd string
|
||||||
qemuDetached bool
|
qemuDetached bool
|
||||||
networking string
|
networking string
|
||||||
@ -71,6 +72,8 @@ func init() {
|
|||||||
flags.UintVar(&cpus, "cpus", 1, "Number of CPUs")
|
flags.UintVar(&cpus, "cpus", 1, "Number of CPUs")
|
||||||
flags.UintVar(&mem, "mem", 1024, "Amount of memory in MB")
|
flags.UintVar(&mem, "mem", 1024, "Amount of memory in MB")
|
||||||
|
|
||||||
|
flags.StringVar(&bios, "bios", "", "Path to the optional bios binary")
|
||||||
|
|
||||||
// Backend configuration
|
// Backend configuration
|
||||||
flags.StringVar(&qemuCmd, "qemu", "", "Path to the qemu binary (otherwise look in $PATH)")
|
flags.StringVar(&qemuCmd, "qemu", "", "Path to the qemu binary (otherwise look in $PATH)")
|
||||||
flags.BoolVar(&qemuDetached, "detached", false, "Set qemu container to run in the background")
|
flags.BoolVar(&qemuDetached, "detached", false, "Set qemu container to run in the background")
|
||||||
@ -105,6 +108,7 @@ func Qemu(cmd *cobra.Command, args []string) {
|
|||||||
qemu.WithStdin(os.Stdin),
|
qemu.WithStdin(os.Stdin),
|
||||||
qemu.WithStdout(os.Stdout),
|
qemu.WithStdout(os.Stdout),
|
||||||
qemu.WithStderr(os.Stderr),
|
qemu.WithStderr(os.Stderr),
|
||||||
|
qemu.WithBios(bios),
|
||||||
}
|
}
|
||||||
if enableGUI {
|
if enableGUI {
|
||||||
opts = append(opts, qemu.WithGUI())
|
opts = append(opts, qemu.WithGUI())
|
||||||
|
@ -29,19 +29,22 @@ import (
|
|||||||
"go.linka.cloud/d2vm/pkg/exec"
|
"go.linka.cloud/d2vm/pkg/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig(t *testing.T, ctx context.Context, img string, config Config) {
|
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, img))
|
||||||
tmpPath := filepath.Join(os.TempDir(), "d2vm-tests", strings.NewReplacer(":", "-", ".", "-").Replace(img))
|
tmpPath := filepath.Join(os.TempDir(), "d2vm-tests", strings.NewReplacer(":", "-", ".", "-").Replace(name))
|
||||||
require.NoError(t, os.MkdirAll(tmpPath, 0755))
|
require.NoError(t, os.MkdirAll(tmpPath, 0755))
|
||||||
defer os.RemoveAll(tmpPath)
|
defer os.RemoveAll(tmpPath)
|
||||||
logrus.Infof("inspecting image %s", img)
|
logrus.Infof("inspecting image %s", img)
|
||||||
r, err := FetchDockerImageOSRelease(ctx, img, tmpPath)
|
r, err := FetchDockerImageOSRelease(ctx, img)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer docker.Remove(ctx, img)
|
defer docker.Remove(ctx, img)
|
||||||
d, err := NewDockerfile(r, img, "root", "", false, false)
|
if !r.SupportsLUKS() && luks {
|
||||||
|
t.Skipf("LUKS not supported for %s", r.Version)
|
||||||
|
}
|
||||||
|
d, err := NewDockerfile(r, img, "root", "", luks, grubBIOS, grubEFI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
logrus.Infof("docker image based on %s", d.Release.Name)
|
logrus.Infof("docker image based on %s", d.Release.Name)
|
||||||
p := filepath.Join(tmpPath, docker.FormatImgName(img))
|
p := filepath.Join(tmpPath, docker.FormatImgName(name))
|
||||||
dir := filepath.Dir(p)
|
dir := filepath.Dir(p)
|
||||||
f, err := os.Create(p)
|
f, err := os.Create(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -51,6 +54,10 @@ func testConfig(t *testing.T, ctx context.Context, img string, config Config) {
|
|||||||
logrus.Infof("building kernel enabled image")
|
logrus.Infof("building kernel enabled image")
|
||||||
require.NoError(t, docker.Build(ctx, imgUUID, p, dir))
|
require.NoError(t, docker.Build(ctx, imgUUID, p, dir))
|
||||||
defer docker.Remove(ctx, imgUUID)
|
defer docker.Remove(ctx, imgUUID)
|
||||||
|
// we don't need to test the kernel location if grub is enabled
|
||||||
|
if grubBIOS || grubEFI {
|
||||||
|
return
|
||||||
|
}
|
||||||
require.NoError(t, docker.RunAndRemove(ctx, imgUUID, "test", "-f", config.Kernel))
|
require.NoError(t, docker.RunAndRemove(ctx, imgUUID, "test", "-f", config.Kernel))
|
||||||
require.NoError(t, docker.RunAndRemove(ctx, imgUUID, "test", "-f", config.Initrd))
|
require.NoError(t, docker.RunAndRemove(ctx, imgUUID, "test", "-f", config.Initrd))
|
||||||
}
|
}
|
||||||
@ -116,13 +123,35 @@ func TestConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
exec.SetDebug(true)
|
exec.SetDebug(true)
|
||||||
|
|
||||||
|
names := []string{"luks", "grub-bios", "grub-efi"}
|
||||||
|
bools := []bool{false, true}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.image, func(t *testing.T) {
|
t.Run(test.image, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
for _, luks := range bools {
|
||||||
defer cancel()
|
for _, grubBIOS := range bools {
|
||||||
testConfig(t, ctx, test.image, test.config)
|
for _, grubEFI := range bools {
|
||||||
|
luks := luks
|
||||||
|
grubBIOS := grubBIOS
|
||||||
|
grubEFI := grubEFI
|
||||||
|
n := []string{test.image}
|
||||||
|
for i, v := range []bool{luks, grubBIOS, grubEFI} {
|
||||||
|
if v {
|
||||||
|
n = append(n, names[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name := strings.Join(n, "-")
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
testConfig(t, ctx, name, test.image, test.config, luks, grubBIOS, grubEFI)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error {
|
|||||||
defer os.RemoveAll(tmpPath)
|
defer os.RemoveAll(tmpPath)
|
||||||
|
|
||||||
logrus.Infof("inspecting image %s", img)
|
logrus.Infof("inspecting image %s", img)
|
||||||
r, err := FetchDockerImageOSRelease(ctx, img, tmpPath)
|
r, err := FetchDockerImageOSRelease(ctx, img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !o.raw {
|
if !o.raw {
|
||||||
d, err := NewDockerfile(r, img, o.password, o.networkManager, o.luksPassword != "", o.bootLoader == "grub")
|
d, err := NewDockerfile(r, img, o.password, o.networkManager, o.luksPassword != "", o.hasGrubBIOS(), o.hasGrubEFI())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,14 @@ type convertOptions struct {
|
|||||||
keepCache bool
|
keepCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *convertOptions) hasGrubBIOS() bool {
|
||||||
|
return o.bootLoader == "grub" || o.bootLoader == "grub-bios"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *convertOptions) hasGrubEFI() bool {
|
||||||
|
return o.bootLoader == "grub" || o.bootLoader == "grub-efi"
|
||||||
|
}
|
||||||
|
|
||||||
func WithSize(size uint64) ConvertOption {
|
func WithSize(size uint64) ConvertOption {
|
||||||
return func(o *convertOptions) {
|
return func(o *convertOptions) {
|
||||||
o.size = size
|
o.size = size
|
||||||
|
@ -65,16 +65,21 @@ type Dockerfile struct {
|
|||||||
Release OSRelease
|
Release OSRelease
|
||||||
NetworkManager NetworkManager
|
NetworkManager NetworkManager
|
||||||
Luks bool
|
Luks bool
|
||||||
Grub bool
|
GrubBIOS bool
|
||||||
|
GrubEFI bool
|
||||||
tmpl *template.Template
|
tmpl *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d Dockerfile) Grub() bool {
|
||||||
|
return d.GrubBIOS || d.GrubEFI
|
||||||
|
}
|
||||||
|
|
||||||
func (d Dockerfile) Render(w io.Writer) error {
|
func (d Dockerfile) Render(w io.Writer) error {
|
||||||
return d.tmpl.Execute(w, d)
|
return d.tmpl.Execute(w, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerfile(release OSRelease, img, password string, networkManager NetworkManager, luks, grub bool) (Dockerfile, error) {
|
func NewDockerfile(release OSRelease, img, password string, networkManager NetworkManager, luks, grubBIOS, grubEFI bool) (Dockerfile, error) {
|
||||||
d := Dockerfile{Release: release, Image: img, Password: password, NetworkManager: networkManager, Luks: luks, Grub: grub}
|
d := Dockerfile{Release: release, Image: img, Password: password, NetworkManager: networkManager, Luks: luks, GrubBIOS: grubBIOS, GrubEFI: grubEFI}
|
||||||
var net NetworkManager
|
var net NetworkManager
|
||||||
switch release.ID {
|
switch release.ID {
|
||||||
case ReleaseDebian:
|
case ReleaseDebian:
|
||||||
|
@ -12,7 +12,7 @@ d2vm build [context directory] [flags]
|
|||||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
--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-fs string Filesystem to use for the boot partition, ext4 or fat32
|
||||||
--boot-size uint Size of the boot partition in MB (default 100)
|
--boot-size uint Size of the boot partition in MB (default 100)
|
||||||
--bootloader string Bootloader to use: syslinux, grub (default "syslinux")
|
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
|
||||||
--build-arg stringArray Set build-time variables
|
--build-arg stringArray Set build-time variables
|
||||||
-f, --file string Name of the Dockerfile
|
-f, --file string Name of the Dockerfile
|
||||||
--force Override output qcow2 image
|
--force Override output qcow2 image
|
||||||
|
@ -12,7 +12,7 @@ d2vm convert [docker image] [flags]
|
|||||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
--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-fs string Filesystem to use for the boot partition, ext4 or fat32
|
||||||
--boot-size uint Size of the boot partition in MB (default 100)
|
--boot-size uint Size of the boot partition in MB (default 100)
|
||||||
--bootloader string Bootloader to use: syslinux, grub (default "syslinux")
|
--bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi (default "syslinux")
|
||||||
--force Override output qcow2 image
|
--force Override output qcow2 image
|
||||||
-h, --help help for convert
|
-h, --help help for convert
|
||||||
--keep-cache Keep the images after the build
|
--keep-cache Keep the images after the build
|
||||||
|
@ -11,6 +11,7 @@ d2vm run qemu [options] [image-path] [flags]
|
|||||||
```
|
```
|
||||||
--accel string Choose acceleration mode. Use 'tcg' to disable it. (default "kvm:tcg")
|
--accel string Choose acceleration mode. Use 'tcg' to disable it. (default "kvm:tcg")
|
||||||
--arch string Type of architecture to use, e.g. x86_64, aarch64, s390x (default "x86_64")
|
--arch string Type of architecture to use, e.g. x86_64, aarch64, s390x (default "x86_64")
|
||||||
|
--bios string Path to the optional bios binary
|
||||||
--cpus uint Number of CPUs (default 1)
|
--cpus uint Number of CPUs (default 1)
|
||||||
--detached Set qemu container to run in the background
|
--detached Set qemu container to run in the background
|
||||||
--disk disk Disk config, may be repeated. [file=]path[,size=1G][,format=qcow2] (default [])
|
--disk disk Disk config, may be repeated. [file=]path[,size=1G][,format=qcow2] (default [])
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -36,6 +37,7 @@ import (
|
|||||||
type test struct {
|
type test struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
|
efi bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type img struct {
|
type img struct {
|
||||||
@ -43,14 +45,24 @@ type img struct {
|
|||||||
luks string
|
luks string
|
||||||
}
|
}
|
||||||
|
|
||||||
var images = []img{
|
var (
|
||||||
{name: "alpine:3.17", luks: "Enter passphrase for /dev/sda2:"},
|
images = []img{
|
||||||
{name: "ubuntu:20.04", luks: "Please unlock disk root:"},
|
{name: "alpine:3.17", luks: "Enter passphrase for /dev/sda2:"},
|
||||||
{name: "ubuntu:22.04", luks: "Please unlock disk root:"},
|
{name: "ubuntu:20.04", luks: "Please unlock disk root:"},
|
||||||
{name: "debian:10", luks: "Please unlock disk root:"},
|
{name: "ubuntu:22.04", luks: "Please unlock disk root:"},
|
||||||
{name: "debian:11", luks: "Please unlock disk root:"},
|
{name: "debian:10", luks: "Please unlock disk root:"},
|
||||||
{name: "centos:8", luks: "Please enter passphrase for disk"},
|
{name: "debian:11", luks: "Please unlock disk root:"},
|
||||||
}
|
{name: "centos:8", luks: "Please enter passphrase for disk"},
|
||||||
|
}
|
||||||
|
imgNames = func() []string {
|
||||||
|
var imgs []string
|
||||||
|
for _, img := range images {
|
||||||
|
imgs = append(imgs, img.name)
|
||||||
|
}
|
||||||
|
return imgs
|
||||||
|
}()
|
||||||
|
imgs = flag.String("images", "", "comma separated list of images to test, must be one of: "+strings.Join(imgNames, ","))
|
||||||
|
)
|
||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
func TestConvert(t *testing.T) {
|
||||||
require := require2.New(t)
|
require := require2.New(t)
|
||||||
@ -62,6 +74,10 @@ func TestConvert(t *testing.T) {
|
|||||||
name: "split-boot",
|
name: "split-boot",
|
||||||
args: []string{"--split-boot"},
|
args: []string{"--split-boot"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fat32",
|
||||||
|
args: []string{"--split-boot", "--boot-fs=fat32"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "luks",
|
name: "luks",
|
||||||
args: []string{"--luks-password=root"},
|
args: []string{"--luks-password=root"},
|
||||||
@ -69,13 +85,30 @@ func TestConvert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "grub",
|
name: "grub",
|
||||||
args: []string{"--bootloader=grub"},
|
args: []string{"--bootloader=grub"},
|
||||||
|
efi: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "grub-luks",
|
name: "grub-luks",
|
||||||
args: []string{"--bootloader=grub", "--luks-password=root"},
|
args: []string{"--bootloader=grub", "--luks-password=root"},
|
||||||
|
efi: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var testImgs []img
|
||||||
|
imgs:
|
||||||
|
for _, v := range strings.Split(*imgs, ",") {
|
||||||
|
for _, img := range images {
|
||||||
|
if img.name == v {
|
||||||
|
testImgs = append(testImgs, img)
|
||||||
|
continue imgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("invalid image: %q, valid images: %s", v, strings.Join(imgNames, ","))
|
||||||
|
}
|
||||||
|
if len(testImgs) == 0 {
|
||||||
|
testImgs = images
|
||||||
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
@ -83,7 +116,7 @@ func TestConvert(t *testing.T) {
|
|||||||
require.NoError(os.MkdirAll(dir, os.ModePerm))
|
require.NoError(os.MkdirAll(dir, os.ModePerm))
|
||||||
|
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
for _, img := range images {
|
for _, img := range testImgs {
|
||||||
t.Run(img.name, func(t *testing.T) {
|
t.Run(img.name, func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -161,7 +194,11 @@ func TestConvert(t *testing.T) {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if err := qemu.Run(ctx, out, qemu.WithStdin(inr), qemu.WithStdout(io.MultiWriter(outw, os.Stdout)), qemu.WithStderr(io.Discard), qemu.WithMemory(2048)); err != nil && !success.Load() {
|
opts := []qemu.Option{qemu.WithStdin(inr), qemu.WithStdout(io.MultiWriter(outw, os.Stdout)), qemu.WithStderr(io.Discard), qemu.WithMemory(2048), qemu.WithCPUs(2)}
|
||||||
|
if tt.efi {
|
||||||
|
opts = append(opts, qemu.WithBios("/usr/share/ovmf/OVMF.fd"))
|
||||||
|
}
|
||||||
|
if err := qemu.Run(ctx, out, opts...); err != nil && !success.Load() {
|
||||||
t.Fatalf("failed to run qemu: %v", err)
|
t.Fatalf("failed to run qemu: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
10
fs.go
10
fs.go
@ -21,8 +21,8 @@ import (
|
|||||||
type BootFS string
|
type BootFS string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FSExt4 BootFS = "ext4"
|
BootFSExt4 BootFS = "ext4"
|
||||||
FSFat32 BootFS = "fat32"
|
BootFSFat32 BootFS = "fat32"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f BootFS) String() string {
|
func (f BootFS) String() string {
|
||||||
@ -30,11 +30,11 @@ func (f BootFS) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f BootFS) IsExt() bool {
|
func (f BootFS) IsExt() bool {
|
||||||
return f == FSExt4
|
return f == BootFSExt4
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f BootFS) IsFat() bool {
|
func (f BootFS) IsFat() bool {
|
||||||
return f == FSFat32
|
return f == BootFSFat32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f BootFS) IsSupported() bool {
|
func (f BootFS) IsSupported() bool {
|
||||||
@ -50,7 +50,7 @@ func (f BootFS) Validate() error {
|
|||||||
|
|
||||||
func (f BootFS) linux() string {
|
func (f BootFS) linux() string {
|
||||||
switch f {
|
switch f {
|
||||||
case FSFat32:
|
case BootFSFat32:
|
||||||
return "vfat"
|
return "vfat"
|
||||||
default:
|
default:
|
||||||
return "ext4"
|
return "ext4"
|
||||||
|
77
grub.go
77
grub.go
@ -17,89 +17,54 @@ package d2vm
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
exec2 "os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"go.linka.cloud/d2vm/pkg/exec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const grubCfg = `GRUB_DEFAULT=0
|
|
||||||
GRUB_HIDDEN_TIMEOUT=0
|
|
||||||
GRUB_HIDDEN_TIMEOUT_QUIET=true
|
|
||||||
GRUB_TIMEOUT=0
|
|
||||||
GRUB_CMDLINE_LINUX_DEFAULT="%s"
|
|
||||||
GRUB_CMDLINE_LINUX=""
|
|
||||||
GRUB_TERMINAL=console
|
|
||||||
`
|
|
||||||
|
|
||||||
type grub struct {
|
type grub struct {
|
||||||
name string
|
*grubCommon
|
||||||
c Config
|
}
|
||||||
r OSRelease
|
|
||||||
|
func (g grub) Validate(fs BootFS) error {
|
||||||
|
switch fs {
|
||||||
|
case BootFSFat32:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("grub only supports fat32 boot filesystem due to grub-efi")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g grub) Setup(ctx context.Context, dev, root string, cmdline string) error {
|
func (g grub) Setup(ctx context.Context, dev, root string, cmdline string) error {
|
||||||
logrus.Infof("setting up grub bootloader")
|
logrus.Infof("setting up grub bootloader")
|
||||||
if err := os.WriteFile(filepath.Join(root, "etc", "default", "grub"), []byte(fmt.Sprintf(grubCfg, cmdline)), perm); err != nil {
|
clean, err := g.prepare(ctx, dev, root, cmdline)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(filepath.Join(root, "boot", g.name), os.ModePerm); err != nil {
|
defer clean()
|
||||||
|
if err := g.install(ctx, "--target=x86_64-efi", "--efi-directory=/boot", "--no-nvram", "--removable", "--no-floppy"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mounts := []string{"dev", "proc", "sys"}
|
if err := g.install(ctx, "--target=i386-pc", "--boot-directory=/boot", dev); err != nil {
|
||||||
var unmounts []string
|
|
||||||
defer func() {
|
|
||||||
for _, v := range unmounts {
|
|
||||||
if err := exec.Run(ctx, "umount", filepath.Join(root, v)); err != nil {
|
|
||||||
logrus.Errorf("failed to unmount /%s: %s", v, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for _, v := range mounts {
|
|
||||||
if err := exec.Run(ctx, "mount", "-o", "bind", "/"+v, filepath.Join(root, v)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
unmounts = append(unmounts, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := exec.Run(ctx, "chroot", root, g.name+"-install", "--target=i386-pc", "--boot-directory", "/boot", dev); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := exec.Run(ctx, "chroot", root, g.name+"-mkconfig", "-o", "/boot/"+g.name+"/grub.cfg"); err != nil {
|
if err := g.mkconfig(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type grubBootloaderProvider struct {
|
type grubProvider struct {
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g grubBootloaderProvider) New(c Config, r OSRelease) (Bootloader, error) {
|
func (g grubProvider) New(c Config, r OSRelease) (Bootloader, error) {
|
||||||
name := "grub"
|
return grub{grubCommon: newGrubCommon(c, r)}, nil
|
||||||
if r.ID == "centos" {
|
|
||||||
name = "grub2"
|
|
||||||
}
|
|
||||||
if _, err := exec2.LookPath("grub-install"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := exec2.LookPath("grub-mkconfig"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return grub{
|
|
||||||
name: name,
|
|
||||||
c: c,
|
|
||||||
r: r,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g grubBootloaderProvider) Name() string {
|
func (g grubProvider) Name() string {
|
||||||
return "grub"
|
return "grub"
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterBootloaderProvider(grubBootloaderProvider{})
|
RegisterBootloaderProvider(grubProvider{})
|
||||||
}
|
}
|
||||||
|
61
grub_bios.go
Normal file
61
grub_bios.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2023 Linka Cloud All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package d2vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grubBios struct {
|
||||||
|
*grubCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubBios) Validate(_ BootFS) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubBios) Setup(ctx context.Context, dev, root string, cmdline string) error {
|
||||||
|
logrus.Infof("setting up grub bootloader")
|
||||||
|
clean, err := g.prepare(ctx, dev, root, cmdline)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer clean()
|
||||||
|
if err := g.install(ctx, "--target=i386-pc", "--boot-directory=/boot", dev); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := g.mkconfig(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type grubBiosProvider struct {
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubBiosProvider) New(c Config, r OSRelease) (Bootloader, error) {
|
||||||
|
return grubBios{grubCommon: newGrubCommon(c, r)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubBiosProvider) Name() string {
|
||||||
|
return "grub-bios"
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBootloaderProvider(grubBiosProvider{})
|
||||||
|
}
|
102
grub_common.go
Normal file
102
grub_common.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2023 Linka Cloud All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package d2vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"go.linka.cloud/d2vm/pkg/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const grubCfg = `GRUB_DEFAULT=0
|
||||||
|
GRUB_HIDDEN_TIMEOUT=0
|
||||||
|
GRUB_HIDDEN_TIMEOUT_QUIET=true
|
||||||
|
GRUB_TIMEOUT=0
|
||||||
|
GRUB_CMDLINE_LINUX_DEFAULT="%s"
|
||||||
|
GRUB_CMDLINE_LINUX=""
|
||||||
|
GRUB_TERMINAL=console
|
||||||
|
`
|
||||||
|
|
||||||
|
type grubCommon struct {
|
||||||
|
name string
|
||||||
|
c Config
|
||||||
|
r OSRelease
|
||||||
|
root string
|
||||||
|
dev string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGrubCommon(c Config, r OSRelease) *grubCommon {
|
||||||
|
name := "grub"
|
||||||
|
if r.ID == "centos" {
|
||||||
|
name = "grub2"
|
||||||
|
}
|
||||||
|
return &grubCommon{
|
||||||
|
name: name,
|
||||||
|
c: c,
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *grubCommon) prepare(ctx context.Context, dev, root, cmdline string) (clean func(), err error) {
|
||||||
|
g.dev = dev
|
||||||
|
g.root = root
|
||||||
|
if err = os.WriteFile(filepath.Join(root, "etc", "default", "grub"), []byte(fmt.Sprintf(grubCfg, cmdline)), perm); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.MkdirAll(filepath.Join(root, "boot", g.name), os.ModePerm); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mounts := []string{"dev", "proc", "sys"}
|
||||||
|
var unmounts []string
|
||||||
|
clean = func() {
|
||||||
|
for _, v := range unmounts {
|
||||||
|
if err := exec.Run(ctx, "umount", filepath.Join(root, v)); err != nil {
|
||||||
|
logrus.Errorf("failed to unmount /%s: %s", v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
clean()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, v := range mounts {
|
||||||
|
if err = exec.Run(ctx, "mount", "-o", "bind", "/"+v, filepath.Join(root, v)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unmounts = append(unmounts, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *grubCommon) install(ctx context.Context, args ...string) error {
|
||||||
|
if g.dev == "" || g.root == "" {
|
||||||
|
return fmt.Errorf("grubCommon not prepared")
|
||||||
|
}
|
||||||
|
args = append([]string{g.root, g.name + "-install"}, args...)
|
||||||
|
return exec.Run(ctx, "chroot", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *grubCommon) mkconfig(ctx context.Context) error {
|
||||||
|
if g.dev == "" || g.root == "" {
|
||||||
|
return fmt.Errorf("grubCommon not prepared")
|
||||||
|
}
|
||||||
|
return exec.Run(ctx, "chroot", g.root, g.name+"-mkconfig", "-o", "/boot/"+g.name+"/grub.cfg")
|
||||||
|
}
|
67
grub_efi.go
Normal file
67
grub_efi.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2023 Linka Cloud All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package d2vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grubEFI struct {
|
||||||
|
*grubCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubEFI) Validate(fs BootFS) error {
|
||||||
|
switch fs {
|
||||||
|
case BootFSFat32:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("grub-efi only supports fat32 boot filesystem")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubEFI) Setup(ctx context.Context, dev, root string, cmdline string) error {
|
||||||
|
logrus.Infof("setting up grub-efi bootloader")
|
||||||
|
clean, err := g.prepare(ctx, dev, root, cmdline)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer clean()
|
||||||
|
if err := g.install(ctx, "--target=x86_64-efi", "--efi-directory=/boot", "--no-nvram", "--removable", "--no-floppy"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := g.mkconfig(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type grubEFIProvider struct {
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubEFIProvider) New(c Config, r OSRelease) (Bootloader, error) {
|
||||||
|
return grubEFI{grubCommon: newGrubCommon(c, r)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g grubEFIProvider) Name() string {
|
||||||
|
return "grub-efi"
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBootloaderProvider(grubEFIProvider{})
|
||||||
|
}
|
@ -16,12 +16,8 @@ package d2vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -107,40 +103,8 @@ func ParseOSRelease(s string) (OSRelease, error) {
|
|||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func FetchDockerImageOSRelease(ctx context.Context, img string) (OSRelease, error) {
|
||||||
osReleaseDockerfile = `
|
o, _, err := docker.CmdOut(ctx, "run", "--rm", "-i", "--entrypoint", "cat", img, "/etc/os-release")
|
||||||
FROM {{ . }}
|
|
||||||
|
|
||||||
ENTRYPOINT [""]
|
|
||||||
|
|
||||||
CMD ["/bin/cat", "/etc/os-release"]
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
osReleaseDockerfileTemplate = template.Must(template.New("osrelease.Dockerfile").Parse(osReleaseDockerfile))
|
|
||||||
)
|
|
||||||
|
|
||||||
func FetchDockerImageOSRelease(ctx context.Context, img string, tmpPath string) (OSRelease, error) {
|
|
||||||
d := filepath.Join(tmpPath, "osrelease.Dockerfile")
|
|
||||||
f, err := os.Create(d)
|
|
||||||
if err != nil {
|
|
||||||
return OSRelease{}, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := osReleaseDockerfileTemplate.Execute(f, img); err != nil {
|
|
||||||
return OSRelease{}, err
|
|
||||||
}
|
|
||||||
imgTag := fmt.Sprintf("os-release-%s", img)
|
|
||||||
if err := docker.Cmd(ctx, "image", "build", "-t", imgTag, "-f", d, tmpPath); err != nil {
|
|
||||||
return OSRelease{}, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := docker.Cmd(ctx, "image", "rm", imgTag); err != nil {
|
|
||||||
logrus.WithError(err).Error("failed to cleanup OSRelease Docker Image")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
o, _, err := docker.CmdOut(ctx, "run", "--rm", "-i", imgTag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OSRelease{}, err
|
return OSRelease{}, err
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ type config struct {
|
|||||||
arch string
|
arch string
|
||||||
cpus uint
|
cpus uint
|
||||||
memory uint
|
memory uint
|
||||||
|
bios string
|
||||||
accel string
|
accel string
|
||||||
detached bool
|
detached bool
|
||||||
qemuBinPath string
|
qemuBinPath string
|
||||||
@ -92,6 +93,12 @@ func WithMemory(memory uint) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithBios(bios string) Option {
|
||||||
|
return func(c *config) {
|
||||||
|
c.bios = bios
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithAccel(accel string) Option {
|
func WithAccel(accel string) Option {
|
||||||
return func(c *config) {
|
return func(c *config) {
|
||||||
c.accel = accel
|
c.accel = accel
|
||||||
|
@ -197,6 +197,10 @@ func (c *config) buildQemuCmdline() ([]string, error) {
|
|||||||
qemuArgs = append(qemuArgs, "-m", fmt.Sprintf("%d", c.memory))
|
qemuArgs = append(qemuArgs, "-m", fmt.Sprintf("%d", c.memory))
|
||||||
qemuArgs = append(qemuArgs, "-uuid", c.uuid.String())
|
qemuArgs = append(qemuArgs, "-uuid", c.uuid.String())
|
||||||
|
|
||||||
|
if c.bios != "" {
|
||||||
|
qemuArgs = append(qemuArgs, "-bios", c.bios)
|
||||||
|
}
|
||||||
|
|
||||||
// Need to specify the vcpu type when running qemu on arm64 platform, for security reason,
|
// Need to specify the vcpu type when running qemu on arm64 platform, for security reason,
|
||||||
// the vcpu should be "host" instead of other names such as "cortex-a53"...
|
// the vcpu should be "host" instead of other names such as "cortex-a53"...
|
||||||
if c.arch == "aarch64" {
|
if c.arch == "aarch64" {
|
||||||
|
@ -50,6 +50,10 @@ type syslinux struct {
|
|||||||
mbrBin string
|
mbrBin string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s syslinux) Validate(_ BootFS) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s syslinux) Setup(ctx context.Context, dev, root string, cmdline string) error {
|
func (s syslinux) Setup(ctx context.Context, dev, root string, cmdline string) error {
|
||||||
logrus.Infof("setting up syslinux bootloader")
|
logrus.Infof("setting up syslinux bootloader")
|
||||||
if err := exec.Run(ctx, "extlinux", "--install", filepath.Join(root, "boot")); err != nil {
|
if err := exec.Run(ctx, "extlinux", "--install", filepath.Join(root, "boot")); err != nil {
|
||||||
|
@ -2,8 +2,7 @@ FROM {{ .Image }}
|
|||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apk update --no-cache && \
|
RUN apk add --no-cache \
|
||||||
apk add \
|
|
||||||
util-linux \
|
util-linux \
|
||||||
linux-virt \
|
linux-virt \
|
||||||
{{- if ge .Release.VersionID "3.17" }}
|
{{- if ge .Release.VersionID "3.17" }}
|
||||||
@ -31,13 +30,21 @@ iface eth0 inet dhcp\n\
|
|||||||
' > /etc/network/interfaces
|
' > /etc/network/interfaces
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{- if .Luks }}
|
{{ if .Luks }}
|
||||||
RUN apk add --no-cache cryptsetup && \
|
RUN apk add --no-cache cryptsetup && \
|
||||||
source /etc/mkinitfs/mkinitfs.conf && \
|
source /etc/mkinitfs/mkinitfs.conf && \
|
||||||
echo "features=\"${features} cryptsetup\"" > /etc/mkinitfs/mkinitfs.conf && \
|
echo "features=\"${features} cryptsetup\"" > /etc/mkinitfs/mkinitfs.conf && \
|
||||||
mkinitfs $(ls /lib/modules)
|
mkinitfs $(ls /lib/modules)
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- if .Grub }} \
|
# we need to keep that at the end, because after it, we can't install packages without error anymore due to grub hooks
|
||||||
RUN apk add --no-cache grub grub-bios
|
{{- if .Grub }}
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
{{- if .GrubBIOS }}
|
||||||
|
grub-bios \
|
||||||
|
{{- end }}
|
||||||
|
{{- if .GrubEFI }}
|
||||||
|
grub-efi \
|
||||||
|
{{- end }}
|
||||||
|
grub
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -7,24 +7,23 @@ RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \
|
|||||||
|
|
||||||
RUN yum update -y
|
RUN yum update -y
|
||||||
|
|
||||||
|
# See https://bugzilla.redhat.com/show_bug.cgi?id=1917213
|
||||||
RUN yum install -y \
|
RUN yum install -y \
|
||||||
kernel \
|
kernel \
|
||||||
systemd \
|
systemd \
|
||||||
NetworkManager \
|
NetworkManager \
|
||||||
{{- if .Grub }}
|
{{- if .GrubBIOS }}
|
||||||
grub2 \
|
grub2 \
|
||||||
|
{{- end }}
|
||||||
|
{{- if .GrubEFI }}
|
||||||
|
grub2 grub2-efi-x64 grub2-efi-x64-modules \
|
||||||
{{- end }}
|
{{- end }}
|
||||||
e2fsprogs \
|
e2fsprogs \
|
||||||
sudo && \
|
sudo && \
|
||||||
systemctl enable NetworkManager && \
|
systemctl enable NetworkManager && \
|
||||||
systemctl unmask systemd-remount-fs.service && \
|
systemctl unmask systemd-remount-fs.service && \
|
||||||
systemctl unmask getty.target
|
systemctl unmask getty.target && \
|
||||||
|
find /boot -type l -exec rm {} \;
|
||||||
{{- if not .Grub }}
|
|
||||||
RUN cd /boot && \
|
|
||||||
mv $(find . -name 'vmlinuz-*') /boot/vmlinuz && \
|
|
||||||
mv $(find . -name 'initramfs-*.img') /boot/initrd.img
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{ if .Luks }}
|
{{ if .Luks }}
|
||||||
RUN yum install -y cryptsetup && \
|
RUN yum install -y cryptsetup && \
|
||||||
@ -34,3 +33,9 @@ RUN dracut --no-hostonly --regenerate-all --force
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
|
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
|
||||||
|
|
||||||
|
{{- if not .Grub }}
|
||||||
|
RUN cd /boot && \
|
||||||
|
mv $(find . -name 'vmlinuz-*') /boot/vmlinuz && \
|
||||||
|
mv $(find . -name 'initramfs-*.img') /boot/initrd.img
|
||||||
|
{{- end }}
|
||||||
|
@ -10,21 +10,23 @@ RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.
|
|||||||
echo "deb-src http://archive.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list
|
echo "deb-src http://archive.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
RUN apt-get -y update && \
|
RUN apt-get update && \
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
||||||
linux-image-amd64 && \
|
linux-image-amd64 && \
|
||||||
find /boot -type l -exec rm {} \;
|
find /boot -type l -exec rm {} \;
|
||||||
|
|
||||||
{{- if not .Grub }}
|
|
||||||
RUN mv $(find /boot -name 'vmlinuz-*') /boot/vmlinuz && \
|
|
||||||
mv $(find /boot -name 'initrd.img-*') /boot/initrd.img
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
systemd-sysv \
|
systemd-sysv \
|
||||||
systemd \
|
systemd \
|
||||||
{{- if .Grub }}
|
{{- if .Grub }}
|
||||||
grub2 \
|
grub-common \
|
||||||
|
grub2-common \
|
||||||
|
{{- end }}
|
||||||
|
{{- if .GrubBIOS }}
|
||||||
|
grub-pc-bin \
|
||||||
|
{{- end }}
|
||||||
|
{{- if .GrubEFI }}
|
||||||
|
grub-efi-amd64-bin \
|
||||||
{{- end }}
|
{{- end }}
|
||||||
dbus \
|
dbus \
|
||||||
iproute2 \
|
iproute2 \
|
||||||
@ -65,3 +67,9 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cr
|
|||||||
echo "CRYPTSETUP=y" >> /etc/cryptsetup-initramfs/conf-hook && \
|
echo "CRYPTSETUP=y" >> /etc/cryptsetup-initramfs/conf-hook && \
|
||||||
update-initramfs -u -v
|
update-initramfs -u -v
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
# needs to be after update-initramfs
|
||||||
|
{{- if not .Grub }}
|
||||||
|
RUN mv $(find /boot -name 'vmlinuz-*') /boot/vmlinuz && \
|
||||||
|
mv $(find /boot -name 'initrd.img-*') /boot/initrd.img
|
||||||
|
{{- end }}
|
||||||
|
@ -2,14 +2,21 @@ FROM {{ .Image }}
|
|||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apt-get update -y && \
|
RUN apt-get update && \
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
||||||
linux-image-virtual \
|
linux-image-virtual \
|
||||||
initramfs-tools \
|
initramfs-tools \
|
||||||
systemd-sysv \
|
systemd-sysv \
|
||||||
systemd \
|
systemd \
|
||||||
{{- if .Grub }}
|
{{- if .Grub }}
|
||||||
grub2 \
|
grub-common \
|
||||||
|
grub2-common \
|
||||||
|
{{- end }}
|
||||||
|
{{- if .GrubBIOS }}
|
||||||
|
grub-pc-bin \
|
||||||
|
{{- end }}
|
||||||
|
{{- if .GrubEFI }}
|
||||||
|
grub-efi-amd64-bin \
|
||||||
{{- end }}
|
{{- end }}
|
||||||
dbus \
|
dbus \
|
||||||
isc-dhcp-client \
|
isc-dhcp-client \
|
||||||
@ -17,11 +24,6 @@ RUN apt-get update -y && \
|
|||||||
iputils-ping && \
|
iputils-ping && \
|
||||||
find /boot -type l -exec rm {} \;
|
find /boot -type l -exec rm {} \;
|
||||||
|
|
||||||
{{- if not .Grub }}
|
|
||||||
RUN mv $(find /boot -name 'vmlinuz-*') /boot/vmlinuz && \
|
|
||||||
mv $(find /boot -name 'initrd.img-*') /boot/initrd.img
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
RUN systemctl preset-all
|
RUN systemctl preset-all
|
||||||
|
|
||||||
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
|
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
|
||||||
@ -54,3 +56,9 @@ iface eth0 inet dhcp\n\
|
|||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cryptsetup-initramfs && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cryptsetup-initramfs && \
|
||||||
update-initramfs -u -v
|
update-initramfs -u -v
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
# needs to be after update-initramfs
|
||||||
|
{{- if not .Grub }}
|
||||||
|
RUN mv $(find /boot -name 'vmlinuz-*') /boot/vmlinuz && \
|
||||||
|
mv $(find /boot -name 'initrd.img-*') /boot/initrd.img
|
||||||
|
{{- end }}
|
||||||
|
Loading…
Reference in New Issue
Block a user