metropolis/node: generate OCI index

This adds a multi-architecture OCI index target for metropolis. mkoci is
extended to add the platform to the index.json. Product info is extended
to add the PlatformOS field, which mkoci puts into the platform.

The OCI spec requires the os field of the platform to be set. We don't
need this field so we could simply put "unknown" there, but I think
"uefi" is nicer. Registry UIs display the os and architecture fields.

Change-Id: I24acf3c01201b50238abd30cfd86da2eda43c1d9
Reviewed-on: https://review.monogon.dev/c/monogon/+/4477
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/osbase/build/mkoci/main.go b/osbase/build/mkoci/main.go
index 1fb2254..7077887 100644
--- a/osbase/build/mkoci/main.go
+++ b/osbase/build/mkoci/main.go
@@ -34,6 +34,11 @@
 	outPath          = flag.String("out", "", "Output OCI Image Layout directory path")
 )
 
+var architectureToOCI = map[string]string{
+	"x86_64":  "amd64",
+	"aarch64": "arm64",
+}
+
 type payload struct {
 	name string
 	path string
@@ -272,14 +277,30 @@
 	}
 
 	// Write the index.
+	platformArchitecture, ok := architectureToOCI[productInfo.Architecture()]
+	if !ok {
+		log.Fatalf("Missing architectureToOCI entry for %q", productInfo.Architecture())
+	}
+	platformOS := productInfo.PlatformOS
+	if platformOS == "" {
+		platformOS = "unknown"
+	}
 	imageIndex := ocispecv1.Index{
 		Versioned: ocispec.Versioned{SchemaVersion: 2},
 		MediaType: ocispecv1.MediaTypeImageIndex,
 		Manifests: []ocispecv1.Descriptor{{
 			MediaType:    ocispecv1.MediaTypeImageManifest,
 			ArtifactType: osimage.MediaTypeOSImageConfig,
-			Digest:       digest.NewDigestFromEncoded(digest.SHA256, imageManifestHash),
-			Size:         int64(len(imageManifestBytes)),
+			// The platform set here is used when building an OCI index with Bazel.
+			// Other consumers cannot rely on it being present because it is not
+			// preserved when pushing to a registry. We don't use the OS field, but
+			// it's required by the OCI spec.
+			Platform: &ocispecv1.Platform{
+				Architecture: platformArchitecture,
+				OS:           platformOS,
+			},
+			Digest: digest.NewDigestFromEncoded(digest.SHA256, imageManifestHash),
+			Size:   int64(len(imageManifestBytes)),
 		}},
 	}
 	imageIndexBytes, err := json.MarshalIndent(imageIndex, "", "\t")