m/c/metroctl: automatically set up kubeconfig

When running takeownership this automatically sets up the necessary
kubeconfig configuration to make sure kubectl can access the cluster.

Change-Id: I135bfda2a13ea169a41506d3615d4e8a0bb0ea21
Reviewed-on: https://review.monogon.dev/c/monogon/+/818
Tested-by: Jenkins CI
Reviewed-by: Mateusz Zalega <mateusz@monogon.tech>
diff --git a/metropolis/cli/metroctl/takeownership.go b/metropolis/cli/metroctl/takeownership.go
index 17ed121..d6d9df6 100644
--- a/metropolis/cli/metroctl/takeownership.go
+++ b/metropolis/cli/metroctl/takeownership.go
@@ -7,11 +7,15 @@
 	"log"
 	"net"
 	"os"
+	"os/exec"
 	"path/filepath"
 
 	"github.com/adrg/xdg"
 	"github.com/spf13/cobra"
 	"google.golang.org/grpc"
+	clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication/v1"
+	"k8s.io/client-go/tools/clientcmd"
+	clientapi "k8s.io/client-go/tools/clientcmd/api"
 
 	clicontext "source.monogon.dev/metropolis/cli/pkg/context"
 	"source.monogon.dev/metropolis/node"
@@ -72,7 +76,61 @@
 		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.")
+	log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
+
+	ca := clientcmd.NewDefaultPathOptions()
+	config, err := ca.GetStartingConfig()
+	if err != nil {
+		log.Fatalf("Failed to get initial kubeconfig to add Metropolis cluster: %v", err)
+	}
+	// 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)
+		}
+	}
+
+	config.AuthInfos["metropolis"] = &clientapi.AuthInfo{
+		Exec: &clientapi.ExecConfig{
+			APIVersion: clientauthentication.SchemeGroupVersion.String(),
+			Command:    metroctlPath,
+			Args:       []string{k8scredpluginCmd.Use},
+			InstallHint: `Authenticating to Metropolis clusters requires metroctl to be present.
+Running metroctl takeownership creates this entry and either points to metroctl as a command in
+PATH if metroctl is in PATH at that time or to the absolute path to metroctl at that time.
+If you moved metroctl afterwards or want to switch to PATH resolution, edit $HOME/.kube/config and
+change users.metropolis.exec.command to the required path (or just metroctl if using PATH resolution).`,
+			InteractiveMode: clientapi.NeverExecInteractiveMode,
+		},
+	}
+
+	config.Clusters["metropolis"] = &clientapi.Cluster{
+		// MVP: This is insecure, but making this work would be wasted effort
+		// as all of it will be replaced by the identity system.
+		InsecureSkipTLSVerify: true,
+		Server:                "https://" + net.JoinHostPort(args[0], node.KubernetesAPIWrappedPort.PortString()),
+	}
+
+	config.Contexts["metropolis"] = &clientapi.Context{
+		AuthInfo:  "metropolis",
+		Cluster:   "metropolis",
+		Namespace: "default",
+	}
+
+	// Only set us as the current context if no other exists. Changing that
+	// unprompted would be kind of rude.
+	if config.CurrentContext == "" {
+		config.CurrentContext = "metropolis"
+	}
+
+	if err := clientcmd.ModifyConfig(ca, *config, true); err != nil {
+		log.Fatalf("Failed to modify kubeconfig to add Metropolis cluster: %v", err)
+	}
+	log.Println("Success! kubeconfig is set up. You can now run kubectl --context=metropolis ... to access the Kubernetes cluster.")
 }
 
 func init() {