metropolis: use new OS image format for install
This switches the USB and SSH installation methods to the new OS image
format based on OCI artifacts.
When stored on disk, the new format consists of a directory containing
an OCI layout, instead of a single file. This means that all steps which
copy or upload an image now need to handle a tree of files.
Change-Id: I526d32f5c50bd74f513f785118768a56b2655fa0
Reviewed-on: https://review.monogon.dev/c/monogon/+/4090
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/takeover/BUILD.bazel b/metropolis/cli/takeover/BUILD.bazel
index c262164..c0b16f4 100644
--- a/metropolis/cli/takeover/BUILD.bazel
+++ b/metropolis/cli/takeover/BUILD.bazel
@@ -40,6 +40,8 @@
"//osbase/kexec",
"//osbase/net/dump",
"//osbase/net/proto",
+ "//osbase/oci",
+ "//osbase/oci/osimage",
"//osbase/structfs",
"//osbase/supervisor",
"@com_github_cavaliergopher_cpio//:cpio",
diff --git a/metropolis/cli/takeover/e2e/BUILD.bazel b/metropolis/cli/takeover/e2e/BUILD.bazel
index ae4810b..d951ad8 100644
--- a/metropolis/cli/takeover/e2e/BUILD.bazel
+++ b/metropolis/cli/takeover/e2e/BUILD.bazel
@@ -6,13 +6,13 @@
data = [
"//metropolis/cli/metroctl:metroctl_lite",
"//metropolis/cli/takeover",
- "//metropolis/installer/test/testos:testos_bundle",
+ "//metropolis/installer/test/testos:testos_image",
"//third_party/edk2:OVMF_CODE.fd",
"//third_party/edk2:OVMF_VARS.fd",
"@debian_11_cloudimage//file",
],
x_defs = {
- "xBundleFilePath": "$(rlocationpath //metropolis/installer/test/testos:testos_bundle )",
+ "xImagePath": "$(rlocationpath //metropolis/installer/test/testos:testos_image )",
"xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
"xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
"xCloudImagePath": "$(rlocationpath @debian_11_cloudimage//file )",
diff --git a/metropolis/cli/takeover/e2e/main_test.go b/metropolis/cli/takeover/e2e/main_test.go
index afce515..d357d8a 100644
--- a/metropolis/cli/takeover/e2e/main_test.go
+++ b/metropolis/cli/takeover/e2e/main_test.go
@@ -32,7 +32,7 @@
// These are filled by bazel at linking time with the canonical path of
// their corresponding file. Inside the init function we resolve it
// with the rules_go runfiles package to the real path.
- xBundleFilePath string
+ xImagePath string
xOvmfVarsPath string
xOvmfCodePath string
xCloudImagePath string
@@ -44,7 +44,7 @@
var err error
for _, path := range []*string{
&xCloudImagePath, &xOvmfVarsPath, &xOvmfCodePath,
- &xTakeoverPath, &xBundleFilePath, &xMetroctlPath,
+ &xTakeoverPath, &xImagePath, &xMetroctlPath,
} {
*path, err = runfiles.Rlocation(*path)
if err != nil {
@@ -203,7 +203,7 @@
"--bootstrap",
"--cluster", "cluster.internal",
"--takeover", xTakeoverPath,
- "--bundle", xBundleFilePath,
+ "--image", xImagePath,
}
installCmd := exec.Command(xMetroctlPath, installArgs...)
installCmd.Env = append(installCmd.Environ(), fmt.Sprintf("SSH_AUTH_SOCK=%s", sshAuthSock))
diff --git a/metropolis/cli/takeover/install.go b/metropolis/cli/takeover/install.go
index 2b76095..5bea9e9 100644
--- a/metropolis/cli/takeover/install.go
+++ b/metropolis/cli/takeover/install.go
@@ -4,7 +4,6 @@
package main
import (
- "archive/zip"
_ "embed"
"fmt"
"os"
@@ -14,30 +13,14 @@
"source.monogon.dev/osbase/blockdev"
"source.monogon.dev/osbase/build/mkimage/osimage"
"source.monogon.dev/osbase/efivarfs"
+ "source.monogon.dev/osbase/oci"
+ ociosimage "source.monogon.dev/osbase/oci/osimage"
"source.monogon.dev/osbase/structfs"
)
//go:embed metropolis/node/core/abloader/abloader.efi
var abloader []byte
-// zipBlob looks up a file in a [zip.Reader] and adapts it to [structfs.Blob].
-func zipBlob(reader *zip.Reader, name string) (zipFileBlob, error) {
- for _, file := range reader.File {
- if file.Name == name {
- return zipFileBlob{file}, nil
- }
- }
- return zipFileBlob{}, fmt.Errorf("file %q not found", name)
-}
-
-type zipFileBlob struct {
- *zip.File
-}
-
-func (f zipFileBlob) Size() int64 {
- return int64(f.File.UncompressedSize64)
-}
-
// EnvInstallTarget environment variable which tells the takeover binary where
// to install to
const EnvInstallTarget = "TAKEOVER_INSTALL_TARGET"
@@ -54,22 +37,12 @@
return err
}
- bundleRaw, err := os.Open("/bundle.zip")
+ image, err := oci.ReadLayout("/osimage")
if err != nil {
- return err
+ return fmt.Errorf("failed to read OS image: %w", err)
}
- bundleStat, err := bundleRaw.Stat()
- if err != nil {
- return err
- }
-
- bundle, err := zip.NewReader(bundleRaw, bundleStat.Size())
- if err != nil {
- return fmt.Errorf("failed to open node bundle: %w", err)
- }
-
- installParams, err := setupOSImageParams(bundle, metropolisSpecRaw, os.Getenv(EnvInstallTarget))
+ installParams, err := setupOSImageParams(image, metropolisSpecRaw, os.Getenv(EnvInstallTarget))
if err != nil {
return err
}
@@ -89,20 +62,24 @@
return nil
}
-func setupOSImageParams(bundle *zip.Reader, metropolisSpecRaw []byte, installTarget string) (*osimage.Params, error) {
+func setupOSImageParams(image *oci.Image, metropolisSpecRaw []byte, installTarget string) (*osimage.Params, error) {
rootDev, err := blockdev.Open(filepath.Join("/dev", installTarget))
if err != nil {
return nil, fmt.Errorf("failed to open root device: %w", err)
}
- efiPayload, err := zipBlob(bundle, "kernel_efi.efi")
+ osImage, err := ociosimage.Read(image)
if err != nil {
- return nil, fmt.Errorf("invalid bundle: %w", err)
+ return nil, fmt.Errorf("failed to read OS image: %w", err)
}
- systemImage, err := zipBlob(bundle, "verity_rootfs.img")
+ efiPayload, err := osImage.Payload("kernel.efi")
if err != nil {
- return nil, fmt.Errorf("invalid bundle: %w", err)
+ return nil, fmt.Errorf("cannot open EFI payload in OS image: %w", err)
+ }
+ systemImage, err := osImage.Payload("system")
+ if err != nil {
+ return nil, fmt.Errorf("cannot open system image in OS image: %w", err)
}
return &osimage.Params{
diff --git a/metropolis/cli/takeover/takeover.go b/metropolis/cli/takeover/takeover.go
index 07c44de..b8ace66 100644
--- a/metropolis/cli/takeover/takeover.go
+++ b/metropolis/cli/takeover/takeover.go
@@ -4,7 +4,6 @@
package main
import (
- "archive/zip"
_ "embed"
"fmt"
"io"
@@ -23,6 +22,7 @@
"source.monogon.dev/osbase/build/mkimage/osimage"
"source.monogon.dev/osbase/kexec"
netdump "source.monogon.dev/osbase/net/dump"
+ "source.monogon.dev/osbase/oci"
"source.monogon.dev/osbase/structfs"
)
@@ -94,19 +94,9 @@
return nil, err
}
- bundleBlob, err := structfs.OSPathBlob(filepath.Join(filepath.Dir(currPath), "bundle.zip"))
+ image, err := oci.ReadLayout(filepath.Join(filepath.Dir(currPath), "osimage"))
if err != nil {
- return nil, err
- }
-
- bundleRaw, err := bundleBlob.Open()
- if err != nil {
- return nil, err
- }
- defer bundleRaw.Close()
- bundle, err := zip.NewReader(bundleRaw.(io.ReaderAt), bundleBlob.Size())
- if err != nil {
- return nil, fmt.Errorf("failed to open node bundle: %w", err)
+ return nil, fmt.Errorf("failed to read OS image: %w", err)
}
// Dump the current network configuration
@@ -140,7 +130,7 @@
return nil, fmt.Errorf("failed marshaling: %w", err)
}
- oParams, err := setupOSImageParams(bundle, nodeParamsRaw, target)
+ oParams, err := setupOSImageParams(image, nodeParamsRaw, target)
if err != nil {
return nil, err
}
@@ -170,15 +160,19 @@
return nil, fmt.Errorf("failed to write initramfs into memory-backed file: %w", err)
}
- // Append this executable, the bundle and node params to initramfs
+ // Append this executable, node params and OS image to initramfs.
self, err := structfs.OSPathBlob("/proc/self/exe")
if err != nil {
return nil, err
}
+ imageLayout, err := oci.CreateLayout(image)
+ if err != nil {
+ return nil, err
+ }
root := structfs.Tree{
structfs.File("init", self, structfs.WithPerm(0o755)),
structfs.File("params.pb", structfs.Bytes(nodeParamsRaw)),
- structfs.File("bundle.zip", bundleBlob),
+ structfs.Dir("osimage", imageLayout),
}
compressedW, err := zstd.NewWriter(initramfsFile, zstd.WithEncoderLevel(1))
if err != nil {