m/c/metroctl: move installer dependency to runfiles

This is the first step in not requiring a full Metropolis build to build
metroctl.

Things are still actively coupled at build time, but the resulting
binary can now run without any embedded installer.

Change-Id: I55fc53c57ac6d1d3e75a225e7d7c79bae5759b67
Reviewed-on: https://review.monogon.dev/c/monogon/+/1945
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/metroctl/BUILD.bazel b/metropolis/cli/metroctl/BUILD.bazel
index 3b6dc25..960c220 100644
--- a/metropolis/cli/metroctl/BUILD.bazel
+++ b/metropolis/cli/metroctl/BUILD.bazel
@@ -18,10 +18,8 @@
         "table_node.go",
     ],
     data = [
-        "//metropolis/node:bundle",
-    ],
-    embedsrcs = [
         "//metropolis/installer:kernel",  #keep
+        "//metropolis/node:bundle",
     ],
     importpath = "source.monogon.dev/metropolis/cli/metroctl",
     visibility = ["//visibility:private"],
diff --git a/metropolis/cli/metroctl/cmd_install.go b/metropolis/cli/metroctl/cmd_install.go
index 986eeca..9ad7953 100644
--- a/metropolis/cli/metroctl/cmd_install.go
+++ b/metropolis/cli/metroctl/cmd_install.go
@@ -25,6 +25,7 @@
 }
 
 var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
+var installerPath = installCmd.PersistentFlags().StringP("installer", "i", "", "Path to the Metropolis installer to use when installing")
 
 var genusbCmd = &cobra.Command{
 	Use:     "genusb target",
@@ -43,8 +44,36 @@
 var bootstrapTPMMode string
 var bootstrapStorageSecurityPolicy string
 
-//go:embed metropolis/installer/kernel.efi
-var installer []byte
+type externalFile struct {
+	reader io.Reader
+	size   uint64
+}
+
+func external(name, datafilePath string, flag *string) *externalFile {
+	if flag == nil || *flag == "" {
+		df, err := datafile.Get(datafilePath)
+		if err != nil {
+			log.Fatalf("No %s specified", name)
+		}
+		return &externalFile{
+			reader: bytes.NewReader(df),
+			size:   uint64(len(df)),
+		}
+	}
+
+	f, err := os.Open(*bundlePath)
+	if err != nil {
+		log.Fatalf("Failed to open specified %s: %v", name, err)
+	}
+	st, err := f.Stat()
+	if err != nil {
+		log.Fatalf("Failed to stat specified %s: %v", name, err)
+	}
+	return &externalFile{
+		reader: f,
+		size:   uint64(st.Size()),
+	}
+}
 
 func doGenUSB(cmd *cobra.Command, args []string) {
 	var tpmMode cpb.ClusterConfiguration_TPMMode
@@ -74,30 +103,8 @@
 		log.Fatalf("Invalid --bootstrap-storage-security (must be one of: permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
 	}
 
-	var bundleReader io.Reader
-	var bundleSize uint64
-	if bundlePath == nil || *bundlePath == "" {
-		// Attempt Bazel runfile bundle if not explicitly set
-		bundle, err := datafile.Get("metropolis/node/bundle.zip")
-		if err != nil {
-			log.Fatalf("No bundle specified and fallback to runfiles failed: %v", err)
-		}
-		bundleReader = bytes.NewReader(bundle)
-		bundleSize = uint64(len(bundle))
-	} else {
-		// Load bundle from specified path
-		bundle, err := os.Open(*bundlePath)
-		if err != nil {
-			log.Fatalf("Failed to open specified bundle: %v", err)
-		}
-		bundleStat, err := bundle.Stat()
-		if err != nil {
-			log.Fatalf("Failed to stat specified bundle: %v", err)
-		}
-		bundleReader = bundle
-		bundleSize = uint64(bundleStat.Size())
-	}
-
+	bundle := external("bundle", "metropolis/node/bundle.zip", bundlePath)
+	installer := external("installer", "metropolis/installer/kernel.efi", installerPath)
 	ctx := clicontext.WithInterrupt(context.Background())
 
 	// TODO(lorenz): Have a key management story for this
@@ -148,11 +155,11 @@
 
 	installerImageArgs := core.MakeInstallerImageArgs{
 		TargetPath:    args[0],
-		Installer:     bytes.NewReader(installer),
-		InstallerSize: uint64(len(installer)),
+		Installer:     installer.reader,
+		InstallerSize: installer.size,
 		NodeParams:    params,
-		Bundle:        bundleReader,
-		BundleSize:    bundleSize,
+		Bundle:        bundle.reader,
+		BundleSize:    bundle.size,
 	}
 
 	log.Printf("Generating installer image (this can take a while, see issues/92).")