m/c/metroctl: split cmd_install for future changes

Change-Id: I99119cc8f5e3728cf832427d1cad79f69fbd48bc
Reviewed-on: https://review.monogon.dev/c/monogon/+/2793
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/cli/metroctl/BUILD.bazel b/metropolis/cli/metroctl/BUILD.bazel
index 60c59b6..a84ae03 100644
--- a/metropolis/cli/metroctl/BUILD.bazel
+++ b/metropolis/cli/metroctl/BUILD.bazel
@@ -19,6 +19,7 @@
     srcs = [
         "cmd_certs.go",
         "cmd_install.go",
+        "cmd_install_usb.go",
         "cmd_k8s_configure.go",
         "cmd_k8scredplugin.go",
         "cmd_node.go",
diff --git a/metropolis/cli/metroctl/cmd_install.go b/metropolis/cli/metroctl/cmd_install.go
index 64cdb26..f729d34 100644
--- a/metropolis/cli/metroctl/cmd_install.go
+++ b/metropolis/cli/metroctl/cmd_install.go
@@ -5,7 +5,6 @@
 	"context"
 	"crypto/ed25519"
 	_ "embed"
-	"io"
 	"log"
 	"os"
 	"strings"
@@ -27,55 +26,18 @@
 	Use:   "install",
 }
 
-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",
-	Short:   "Generates a Metropolis installer disk or image.",
-	Example: "metroctl install --bundle=metropolis-v0.1.zip genusb /dev/sdx",
-	Args:    cobra.ExactArgs(1), // One positional argument: the target
-	Run:     doGenUSB,
-}
-
 // bootstrap is a flag controlling node parameters included in the installer
 // image. If set, the installed node will bootstrap a new cluster. Otherwise,
 // it will try to connect to the cluster which endpoints were provided with
 // the --endpoints flag.
-var bootstrap bool
+var bootstrap = installCmd.PersistentFlags().Bool("bootstrap", false, "Create a bootstrap installer image.")
+var bootstrapTPMMode = installCmd.PersistentFlags().String("bootstrap-tpm-mode", "required", "TPM mode to set on cluster (required, best-effort, disabled)")
+var bootstrapStorageSecurityPolicy = installCmd.PersistentFlags().String("bootstrap-storage-security", "needs-encryption-and-authentication", "Storage security policy to set on cluster (permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
+var bundlePath = installCmd.PersistentFlags().StringP("bundle", "b", "", "Path to the Metropolis bundle to be installed")
 
-var bootstrapTPMMode string
-var bootstrapStorageSecurityPolicy string
-
-type externalFile struct {
-	reader io.Reader
-	size   uint64
-}
-
-func external(name, datafilePath string, flag *string) fat32.SizedReader {
-	if flag == nil || *flag == "" {
-		rPath, err := runfiles.Rlocation(datafilePath)
-		if err != nil {
-			log.Fatalf("No %s specified", name)
-		}
-		df, err := os.ReadFile(rPath)
-		if err != nil {
-			log.Fatalf("Cant read file: %v", err)
-		}
-		return bytes.NewReader(df)
-	}
-
-	f, err := blkio.NewFileReader(*bundlePath)
-	if err != nil {
-		log.Fatalf("Failed to open specified %s: %v", name, err)
-	}
-
-	return f
-}
-
-func doGenUSB(cmd *cobra.Command, args []string) {
+func makeNodeParams() *api.NodeParameters {
 	var tpmMode cpb.ClusterConfiguration_TPMMode
-	switch strings.ToLower(bootstrapTPMMode) {
+	switch strings.ToLower(*bootstrapTPMMode) {
 	case "required", "require":
 		tpmMode = cpb.ClusterConfiguration_TPM_MODE_REQUIRED
 	case "best-effort", "besteffort":
@@ -87,7 +49,7 @@
 	}
 
 	var bootstrapStorageSecurity cpb.ClusterConfiguration_StorageSecurityPolicy
-	switch strings.ToLower(bootstrapStorageSecurityPolicy) {
+	switch strings.ToLower(*bootstrapStorageSecurityPolicy) {
 	case "permissive":
 		bootstrapStorageSecurity = cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_PERMISSIVE
 	case "needs-encryption":
@@ -101,17 +63,15 @@
 		log.Fatalf("Invalid --bootstrap-storage-security (must be one of: permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
 	}
 
-	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
 	if err := os.MkdirAll(flags.configPath, 0700); err != nil && !os.IsExist(err) {
 		log.Fatalf("Failed to create config directory: %v", err)
 	}
 
 	var params *api.NodeParameters
-	if bootstrap {
+	if *bootstrap {
+		// TODO(lorenz): Have a key management story for this
 		priv, err := core.GetOrMakeOwnerKey(flags.configPath)
 		if err != nil {
 			log.Fatalf("Failed to generate or get owner key: %v", err)
@@ -150,25 +110,30 @@
 			},
 		}
 	}
+	return params
+}
 
-	installerImageArgs := core.MakeInstallerImageArgs{
-		TargetPath: args[0],
-		Installer:  installer,
-		NodeParams: params,
-		Bundle:     bundle,
+func external(name, datafilePath string, flag *string) fat32.SizedReader {
+	if flag == nil || *flag == "" {
+		rPath, err := runfiles.Rlocation(datafilePath)
+		if err != nil {
+			log.Fatalf("No %s specified", name)
+		}
+		df, err := os.ReadFile(rPath)
+		if err != nil {
+			log.Fatalf("Cant read file: %v", err)
+		}
+		return bytes.NewReader(df)
 	}
 
-	log.Printf("Generating installer image (this can take a while, see issues/92).")
-	if err := core.MakeInstallerImage(installerImageArgs); err != nil {
-		log.Fatalf("Failed to create installer: %v", err)
+	f, err := blkio.NewFileReader(*bundlePath)
+	if err != nil {
+		log.Fatalf("Failed to open specified %s: %v", name, err)
 	}
+
+	return f
 }
 
 func init() {
 	rootCmd.AddCommand(installCmd)
-
-	genusbCmd.Flags().BoolVar(&bootstrap, "bootstrap", false, "Create a bootstrap installer image.")
-	genusbCmd.Flags().StringVar(&bootstrapTPMMode, "bootstrap-tpm-mode", "required", "TPM mode to set on cluster (required, best-effort, disabled)")
-	genusbCmd.Flags().StringVar(&bootstrapStorageSecurityPolicy, "bootstrap-storage-security", "needs-encryption-and-authentication", "Storage security policy to set on cluster (permissive, needs-encryption, needs-encryption-and-authentication, needs-insecure)")
-	installCmd.AddCommand(genusbCmd)
 }
diff --git a/metropolis/cli/metroctl/cmd_install_usb.go b/metropolis/cli/metroctl/cmd_install_usb.go
new file mode 100644
index 0000000..a9873b6
--- /dev/null
+++ b/metropolis/cli/metroctl/cmd_install_usb.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+	_ "embed"
+	"log"
+
+	"github.com/spf13/cobra"
+
+	"source.monogon.dev/metropolis/cli/metroctl/core"
+)
+
+var genusbCmd = &cobra.Command{
+	Use:     "genusb target",
+	Short:   "Generates a Metropolis installer disk or image.",
+	Example: "metroctl install --bundle=metropolis-v0.1.zip genusb /dev/sdx",
+	Args:    cobra.ExactArgs(1), // One positional argument: the target
+	Run:     doGenUSB,
+}
+
+func doGenUSB(cmd *cobra.Command, args []string) {
+	params := makeNodeParams()
+
+	installerPath, err := cmd.Flags().GetString("installer")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	installer := external("installer", "_main/metropolis/installer/kernel.efi", &installerPath)
+	bundle := external("bundle", "_main/metropolis/node/bundle.zip", bundlePath)
+
+	installerImageArgs := core.MakeInstallerImageArgs{
+		TargetPath: args[0],
+		Installer:  installer,
+		NodeParams: params,
+		Bundle:     bundle,
+	}
+
+	log.Printf("Generating installer image (this can take a while, see issues/92).")
+	if err := core.MakeInstallerImage(installerImageArgs); err != nil {
+		log.Fatalf("Failed to create installer: %v", err)
+	}
+}
+
+func init() {
+	genusbCmd.Flags().StringP("installer", "i", "", "Path to the Metropolis installer to use when installing")
+	installCmd.AddCommand(genusbCmd)
+}