metropolis/cli/metroctl: refactor to use RunE instead of log.Fatal

Change-Id: Id5ca65980816e1715a8f08afcdf712292117012a
Reviewed-on: https://review.monogon.dev/c/monogon/+/3441
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/metroctl/cmd_takeownership.go b/metropolis/cli/metroctl/cmd_takeownership.go
index d421f71..a1a205a 100644
--- a/metropolis/cli/metroctl/cmd_takeownership.go
+++ b/metropolis/cli/metroctl/cmd_takeownership.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 	"log"
 	"os"
 	"os/exec"
@@ -25,78 +26,77 @@
 previous invocation of metroctl install on this machine. A single cluster
 endpoint must be provided with the --endpoints parameter.`,
 	Args: PrintUsageOnWrongArgs(cobra.ExactArgs(0)),
-	Run:  doTakeOwnership,
-}
-
-func doTakeOwnership(cmd *cobra.Command, _ []string) {
-	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
-	if len(flags.clusterEndpoints) != 1 {
-		log.Fatalf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter.")
-	}
-
-	contextName, err := cmd.Flags().GetString("context")
-	if err != nil || contextName == "" {
-		log.Fatalf("takeownership requires a valid context name to be provided with the --context parameter.")
-	}
-
-	ca, err := core.GetClusterCAWithTOFU(ctx, connectOptions())
-	if err != nil {
-		log.Fatalf("Could not retrieve cluster CA: %v", err)
-	}
-
-	// Retrieve the cluster owner's private key, and use it to construct
-	// ephemeral credentials. Then, dial the cluster.
-	opk, err := core.GetOwnerKey(flags.configPath)
-	if errors.Is(err, core.ErrNoCredentials) {
-		log.Fatalf("Owner key does not exist. takeownership needs to be executed on the same system that has previously installed the cluster using metroctl install.")
-	}
-	if err != nil {
-		log.Fatalf("Couldn't get owner's key: %v", err)
-	}
-	opts, err := core.DialOpts(ctx, connectOptions())
-	if err != nil {
-		log.Fatalf("While configuring cluster dial opts: %v", err)
-	}
-	creds, err := rpc.NewEphemeralCredentials(opk, rpc.WantRemoteCluster(ca))
-	if err != nil {
-		log.Fatalf("While generating ephemeral credentials: %v", err)
-	}
-	opts = append(opts, grpc.WithTransportCredentials(creds))
-
-	cc, err := grpc.Dial(resolver.MetropolisControlAddress, opts...)
-	if err != nil {
-		log.Fatalf("While dialing the cluster: %v", err)
-	}
-	aaa := apb.NewAAAClient(cc)
-
-	ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk)
-	if err != nil {
-		log.Fatalf("Failed to retrive owner certificate from cluster: %v", err)
-	}
-
-	if err := core.WriteOwnerCertificate(flags.configPath, ownerCert.Certificate[0]); err != nil {
-		log.Printf("Failed to store retrieved owner certificate: %v", err)
-		log.Fatalln("Sorry, the cluster has been lost as taking ownership cannot be repeated. Fix the reason the file couldn't be written and reinstall the node.")
-	}
-	log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
-
-	// If the user has metroctl in their path, use the metroctl from path as
-	// a credential plugin. Otherwise use the path to the currently-running
-	// metroctl.
-	metroctlPath := "metroctl"
-	if _, err := exec.LookPath("metroctl"); err != nil {
-		metroctlPath, err = os.Executable()
-		if err != nil {
-			log.Fatalf("Failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %v", err)
+	RunE: func(cmd *cobra.Command, _ []string) error {
+		ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
+		if len(flags.clusterEndpoints) != 1 {
+			return fmt.Errorf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter")
 		}
-	}
-	// TODO(q3k, issues/144): this only works as long as all nodes are kubernetes controller
-	// nodes. This won't be the case for too long. Figure this out.
-	configName := "metroctl"
-	if err := core.InstallKubeletConfig(ctx, metroctlPath, connectOptions(), configName, flags.clusterEndpoints[0]); err != nil {
-		log.Fatalf("Failed to install metroctl/k8s integration: %v", err)
-	}
-	log.Printf("Success! kubeconfig is set up. You can now run kubectl --context=%s ... to access the Kubernetes cluster.", configName)
+
+		contextName, err := cmd.Flags().GetString("context")
+		if err != nil || contextName == "" {
+			return fmt.Errorf("takeownership requires a valid context name to be provided with the --context parameter")
+		}
+
+		ca, err := core.GetClusterCAWithTOFU(ctx, connectOptions())
+		if err != nil {
+			return fmt.Errorf("could not retrieve cluster CA: %w", err)
+		}
+
+		// Retrieve the cluster owner's private key, and use it to construct
+		// ephemeral credentials. Then, dial the cluster.
+		opk, err := core.GetOwnerKey(flags.configPath)
+		if errors.Is(err, core.ErrNoCredentials) {
+			return fmt.Errorf("owner key does not exist. takeownership needs to be executed on the same system that has previously installed the cluster using metroctl install")
+		}
+		if err != nil {
+			return fmt.Errorf("couldn't get owner's key: %w", err)
+		}
+		opts, err := core.DialOpts(ctx, connectOptions())
+		if err != nil {
+			return fmt.Errorf("while configuring cluster dial opts: %w", err)
+		}
+		creds, err := rpc.NewEphemeralCredentials(opk, rpc.WantRemoteCluster(ca))
+		if err != nil {
+			return fmt.Errorf("while generating ephemeral credentials: %w", err)
+		}
+		opts = append(opts, grpc.WithTransportCredentials(creds))
+
+		cc, err := grpc.Dial(resolver.MetropolisControlAddress, opts...)
+		if err != nil {
+			return fmt.Errorf("while dialing the cluster: %w", err)
+		}
+		aaa := apb.NewAAAClient(cc)
+
+		ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk)
+		if err != nil {
+			return fmt.Errorf("failed to retrive owner certificate from cluster: %w", err)
+		}
+
+		if err := core.WriteOwnerCertificate(flags.configPath, ownerCert.Certificate[0]); err != nil {
+			log.Printf("Failed to store retrieved owner certificate: %v", err)
+			return fmt.Errorf("sorry, the cluster has been lost as taking ownership cannot be repeated. Fix the reason the file couldn't be written and reinstall the node")
+		}
+		log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
+
+		// If the user has metroctl in their path, use the metroctl from path as
+		// a credential plugin. Otherwise use the path to the currently-running
+		// metroctl.
+		metroctlPath := "metroctl"
+		if _, err := exec.LookPath("metroctl"); err != nil {
+			metroctlPath, err = os.Executable()
+			if err != nil {
+				return fmt.Errorf("failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %w", err)
+			}
+		}
+		// TODO(q3k, issues/144): this only works as long as all nodes are kubernetes controller
+		// nodes. This won't be the case for too long. Figure this out.
+		configName := "metroctl"
+		if err := core.InstallKubeletConfig(ctx, metroctlPath, connectOptions(), configName, flags.clusterEndpoints[0]); err != nil {
+			return fmt.Errorf("failed to install metroctl/k8s integration: %w", err)
+		}
+		log.Printf("Success! kubeconfig is set up. You can now run kubectl --context=%s ... to access the Kubernetes cluster.", configName)
+		return nil
+	},
 }
 
 func init() {