m/installer/install: take OCI image in Params

install.Params now takes an OCI image. This avoids some duplicate code
to extract info from the OCI image in all places which create
install.Params.

Change-Id: I2430ce1101befaa1b1a4781f592d06ee015a0f99
Reviewed-on: https://review.monogon.dev/c/monogon/+/4297
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/takeover/install.go b/metropolis/cli/takeover/install.go
index 4d42269..d58d89c 100644
--- a/metropolis/cli/takeover/install.go
+++ b/metropolis/cli/takeover/install.go
@@ -73,24 +73,13 @@
 		return nil, fmt.Errorf("failed to read OS image: %w", err)
 	}
 
-	efiPayload, err := osImage.Payload("kernel.efi")
-	if err != nil {
-		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 &install.Params{
 		PartitionSize: install.PartitionSizeInfo{
 			ESP:    384,
 			System: 4096,
 			Data:   128,
 		},
-		Architecture:   osImage.Config.ProductInfo.Architecture(),
-		SystemImage:    systemImage,
-		EFIPayload:     efiPayload,
+		OSImage:        osImage,
 		ABLoader:       structfs.Bytes(abloader),
 		NodeParameters: structfs.Bytes(metropolisSpecRaw),
 		Output:         rootDev,
diff --git a/metropolis/installer/install/BUILD.bazel b/metropolis/installer/install/BUILD.bazel
index 1532198..1a0fed3 100644
--- a/metropolis/installer/install/BUILD.bazel
+++ b/metropolis/installer/install/BUILD.bazel
@@ -10,6 +10,7 @@
         "//osbase/efivarfs",
         "//osbase/fat32",
         "//osbase/gpt",
+        "//osbase/oci/osimage",
         "//osbase/structfs",
         "@com_github_google_uuid//:uuid",
     ],
diff --git a/metropolis/installer/install/install.go b/metropolis/installer/install/install.go
index a2b9ff8..dda3b7a 100644
--- a/metropolis/installer/install/install.go
+++ b/metropolis/installer/install/install.go
@@ -15,6 +15,7 @@
 	"source.monogon.dev/osbase/efivarfs"
 	"source.monogon.dev/osbase/fat32"
 	"source.monogon.dev/osbase/gpt"
+	"source.monogon.dev/osbase/oci/osimage"
 	"source.monogon.dev/osbase/structfs"
 )
 
@@ -72,17 +73,14 @@
 type Params struct {
 	// Output is the block device to which the OS is installed.
 	Output blockdev.BlockDev
-	// Architecture is the CPU architecture of the OS image.
-	Architecture string
+	// OSImage is the image from which the OS is installed.
+	OSImage *osimage.Image
+	// UnverifiedPayloads disables verification of payloads if set.
+	// This only works with uncompressed OS images.
+	UnverifiedPayloads bool
 	// ABLoader provides the A/B loader which then loads the EFI loader for the
 	// correct slot.
 	ABLoader structfs.Blob
-	// EFIPayload provides contents of the EFI payload file. It must not be
-	// nil. This gets put into boot slot A.
-	EFIPayload structfs.Blob
-	// SystemImage provides contents of the Metropolis system partition.
-	// If nil, no contents will be copied into the partition.
-	SystemImage structfs.Blob
 	// NodeParameters provides contents of the node parameters file. If nil,
 	// the node parameters file won't be created in the target ESP
 	// filesystem.
@@ -102,6 +100,7 @@
 
 type plan struct {
 	*Params
+	systemImage      structfs.Blob
 	efiBootPath      string
 	efiRoot          structfs.Tree
 	tbl              *gpt.Table
@@ -125,11 +124,11 @@
 		return nil, fmt.Errorf("failed to write FAT32: %w", err)
 	}
 
-	systemImage, err := i.SystemImage.Open()
+	systemImage, err := i.systemImage.Open()
 	if err != nil {
 		return nil, fmt.Errorf("failed to open system image: %w", err)
 	}
-	if _, err := io.CopyN(blockdev.NewRWS(i.systemPartitionA), systemImage, i.SystemImage.Size()); err != nil {
+	if _, err := io.CopyN(blockdev.NewRWS(i.systemPartitionA), systemImage, i.systemImage.Size()); err != nil {
 		systemImage.Close()
 		return nil, fmt.Errorf("failed to write system partition A: %w", err)
 	}
@@ -161,7 +160,19 @@
 func Plan(p *Params) (*plan, error) {
 	params := &plan{Params: p}
 
-	var err error
+	payload := p.OSImage.Payload
+	if p.UnverifiedPayloads {
+		payload = p.OSImage.PayloadUnverified
+	}
+	efiPayload, err := payload("kernel.efi")
+	if err != nil {
+		return nil, fmt.Errorf("cannot open EFI payload in OS image: %w", err)
+	}
+	params.systemImage, err = payload("system")
+	if err != nil {
+		return nil, fmt.Errorf("cannot open system image in OS image: %w", err)
+	}
+
 	params.tbl, err = gpt.New(params.Output)
 	if err != nil {
 		return nil, fmt.Errorf("invalid block device: %w", err)
@@ -178,11 +189,11 @@
 		return nil, fmt.Errorf("failed to allocate ESP: %w", err)
 	}
 
-	if err := params.efiRoot.PlaceFile(EFIBootAPath, params.EFIPayload); err != nil {
+	if err := params.efiRoot.PlaceFile(EFIBootAPath, efiPayload); err != nil {
 		return nil, err
 	}
 	// Place the A/B loader at the EFI bootloader autodiscovery path.
-	params.efiBootPath, err = EFIBootPath(p.Architecture)
+	params.efiBootPath, err = EFIBootPath(p.OSImage.Config.ProductInfo.Architecture())
 	if err != nil {
 		return nil, err
 	}
@@ -205,26 +216,22 @@
 		return nil, fmt.Errorf("failed to calculate size of FAT32: %w", err)
 	}
 
-	// Create the system partition only if its size is specified.
-	if params.PartitionSize.System != 0 && params.SystemImage != nil {
-		params.systemPartitionA = &gpt.Partition{
-			Type: SystemAType,
-			Name: SystemALabel,
-		}
-		if err := params.tbl.AddPartition(params.systemPartitionA, params.PartitionSize.System*Mi); err != nil {
-			return nil, fmt.Errorf("failed to allocate system partition A: %w", err)
-		}
-		params.systemPartitionB = &gpt.Partition{
-			Type: SystemBType,
-			Name: SystemBLabel,
-		}
-		if err := params.tbl.AddPartition(params.systemPartitionB, params.PartitionSize.System*Mi); err != nil {
-			return nil, fmt.Errorf("failed to allocate system partition B: %w", err)
-		}
-	} else if params.PartitionSize.System == 0 && params.SystemImage != nil {
-		// Safeguard against contradicting parameters.
-		return nil, fmt.Errorf("the system image parameter was passed while the associated partition size is zero")
+	// Create the system partition.
+	params.systemPartitionA = &gpt.Partition{
+		Type: SystemAType,
+		Name: SystemALabel,
 	}
+	if err := params.tbl.AddPartition(params.systemPartitionA, params.PartitionSize.System*Mi); err != nil {
+		return nil, fmt.Errorf("failed to allocate system partition A: %w", err)
+	}
+	params.systemPartitionB = &gpt.Partition{
+		Type: SystemBType,
+		Name: SystemBLabel,
+	}
+	if err := params.tbl.AddPartition(params.systemPartitionB, params.PartitionSize.System*Mi); err != nil {
+		return nil, fmt.Errorf("failed to allocate system partition B: %w", err)
+	}
+
 	// Create the data partition only if its size is specified.
 	if params.PartitionSize.Data != 0 {
 		params.dataPartition = &gpt.Partition{
diff --git a/metropolis/installer/main.go b/metropolis/installer/main.go
index ac6152f..6ecc26e 100644
--- a/metropolis/installer/main.go
+++ b/metropolis/installer/main.go
@@ -164,15 +164,6 @@
 		return fmt.Errorf("failed to read OS image from ESP: %w", err)
 	}
 
-	efiPayload, err := osImage.Payload("kernel.efi")
-	if err != nil {
-		return fmt.Errorf("cannot open EFI payload in OS image: %w", err)
-	}
-	systemImage, err := osImage.Payload("system")
-	if err != nil {
-		return fmt.Errorf("cannot open system image in OS image: %w", err)
-	}
-
 	// Build the install parameters.
 	installParams := install.Params{
 		PartitionSize: install.PartitionSizeInfo{
@@ -186,9 +177,7 @@
 			// whenever it's writing to block devices, such as now.
 			Data: 128,
 		},
-		Architecture:   osImage.Config.ProductInfo.Architecture(),
-		SystemImage:    systemImage,
-		EFIPayload:     efiPayload,
+		OSImage:        osImage,
 		ABLoader:       structfs.Bytes(abloader),
 		NodeParameters: nodeParameters,
 	}
diff --git a/metropolis/node/core/update/e2e/BUILD.bazel b/metropolis/node/core/update/e2e/BUILD.bazel
index 5960e66..32f0f52 100644
--- a/metropolis/node/core/update/e2e/BUILD.bazel
+++ b/metropolis/node/core/update/e2e/BUILD.bazel
@@ -8,20 +8,18 @@
         "//third_party/edk2:OVMF_CODE.fd",
         "//third_party/edk2:OVMF_VARS.fd",
         # For the initial image creation
-        "//metropolis/node/core/update/e2e/testos:verity_rootfs_x",
-        "//metropolis/node/core/update/e2e/testos:kernel_efi_x",
+        "//metropolis/node/core/update/e2e/testos:testos_image_x",
         "//metropolis/node/abloader",
         # For the two update tests
         "//metropolis/node/core/update/e2e/testos:testos_image_y",
         "//metropolis/node/core/update/e2e/testos:testos_image_z",
     ],
     x_defs = {
+        "xImageXPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:testos_image_x )",
         "xImageYPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:testos_image_y )",
         "xImageZPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:testos_image_z )",
         "xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
         "xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
-        "xBootPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:kernel_efi_x )",
-        "xSystemXPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:verity_rootfs_x )",
         "xAbloaderPath": "$(rlocationpath //metropolis/node/abloader )",
     },
     deps = [
diff --git a/metropolis/node/core/update/e2e/e2e_test.go b/metropolis/node/core/update/e2e/e2e_test.go
index 124883e..ba41f70 100644
--- a/metropolis/node/core/update/e2e/e2e_test.go
+++ b/metropolis/node/core/update/e2e/e2e_test.go
@@ -32,20 +32,19 @@
 	// 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.
+	xImageXPath   string
 	xImageYPath   string
 	xImageZPath   string
 	xOvmfVarsPath string
 	xOvmfCodePath string
-	xBootPath     string
-	xSystemXPath  string
 	xAbloaderPath string
 )
 
 func init() {
 	var err error
 	for _, path := range []*string{
-		&xImageYPath, &xImageZPath, &xOvmfVarsPath,
-		&xOvmfCodePath, &xBootPath, &xSystemXPath,
+		&xImageXPath, &xImageYPath, &xImageZPath,
+		&xOvmfVarsPath, &xOvmfCodePath,
 		&xAbloaderPath,
 	} {
 		*path, err = runfiles.Rlocation(*path)
@@ -141,6 +140,10 @@
 		Port: 80,
 	}
 
+	imageX, err := oci.ReadLayout(xImageXPath)
+	if err != nil {
+		t.Fatal(err)
+	}
 	imageY, err := oci.ReadLayout(xImageYPath)
 	if err != nil {
 		t.Fatal(err)
@@ -150,7 +153,7 @@
 		t.Fatal(err)
 	}
 
-	osImageY, err := osimage.Read(imageY)
+	osImageX, err := osimage.Read(imageX)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -175,26 +178,15 @@
 	t.Cleanup(func() { os.Remove(rootDevPath) })
 	defer rootDisk.Close()
 
-	boot, err := structfs.OSPathBlob(xBootPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-	system, err := structfs.OSPathBlob(xSystemXPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
 	loader, err := structfs.OSPathBlob(xAbloaderPath)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	if _, err := install.Write(&install.Params{
-		Output:       rootDisk,
-		Architecture: osImageY.Config.ProductInfo.Architecture(),
-		ABLoader:     loader,
-		EFIPayload:   boot,
-		SystemImage:  system,
+		Output:   rootDisk,
+		OSImage:  osImageX,
+		ABLoader: loader,
 		PartitionSize: install.PartitionSizeInfo{
 			ESP:    128,
 			System: 256,
diff --git a/metropolis/node/core/update/e2e/testos/testos.bzl b/metropolis/node/core/update/e2e/testos/testos.bzl
index b0665c0..ab7d2f0 100644
--- a/metropolis/node/core/update/e2e/testos/testos.bzl
+++ b/metropolis/node/core/update/e2e/testos/testos.bzl
@@ -24,7 +24,6 @@
     verity_image(
         name = "verity_rootfs_" + variant,
         source = ":rootfs_" + variant,
-        visibility = ["//metropolis/node/core/update/e2e:__pkg__"],
     )
 
     efi_unified_kernel_image(
@@ -32,7 +31,6 @@
         cmdline = "console=ttyS0 quiet rootfstype=erofs init=/init loadpin.exclude=kexec-image,kexec-initramfs",
         kernel = "//third_party/linux",
         verity = ":verity_rootfs_" + variant,
-        visibility = ["//metropolis/node/core/update/e2e:__pkg__"],
     )
 
     test_product_info(
diff --git a/metropolis/test/launch/cluster.go b/metropolis/test/launch/cluster.go
index 744c0cc..118b82d 100644
--- a/metropolis/test/launch/cluster.go
+++ b/metropolis/test/launch/cluster.go
@@ -175,15 +175,6 @@
 		return nil, fmt.Errorf("failed to read OS image: %w", err)
 	}
 
-	efiPayload, err := osImage.PayloadUnverified("kernel.efi")
-	if err != nil {
-		return nil, fmt.Errorf("cannot open EFI payload in OS image: %w", err)
-	}
-	systemImage, err := osImage.PayloadUnverified("system")
-	if err != nil {
-		return nil, fmt.Errorf("cannot open system image in OS image: %w", err)
-	}
-
 	abloader, err := structfs.OSPathBlob(xAbloaderPath)
 	if err != nil {
 		return nil, fmt.Errorf("cannot open abloader: %w", err)
@@ -204,11 +195,10 @@
 			System: 1024,
 			Data:   128,
 		},
-		Architecture: osImage.Config.ProductInfo.Architecture(),
-		SystemImage:  systemImage,
-		EFIPayload:   efiPayload,
-		ABLoader:     abloader,
-		Output:       df,
+		OSImage:            osImage,
+		UnverifiedPayloads: true,
+		ABLoader:           abloader,
+		Output:             df,
 	}
 
 	if _, err := install.Write(installParams); err != nil {