From 462433b28c7fb31f3b53b3dc06a25bc3b031dbb1 Mon Sep 17 00:00:00 2001 From: Enrico Candino Date: Mon, 15 Jun 2026 14:46:23 +0200 Subject: [PATCH] added HCP print command --- cli/cmds/cluster_create.go | 40 +++++++++++++++++++++++++++----------- cli/cmds/kubeconfig.go | 32 +++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/cli/cmds/cluster_create.go b/cli/cmds/cluster_create.go index cbfcd654..9978ce2f 100644 --- a/cli/cmds/cluster_create.go +++ b/cli/cmds/cluster_create.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "net/url" "os" "strings" "text/template" @@ -89,7 +88,7 @@ func createAction(appCtx *AppContext, config *CreateConfig) func(cmd *cobra.Comm } if config.mode == string(v1beta1.HCPClusterMode) { - logrus.Warn("Mode 'hcp' is experimental") + logrus.Warn("HCP (Hosted Control Plane) mode is experimental.") } namespace := appCtx.Namespace(name) @@ -132,17 +131,12 @@ func createAction(appCtx *AppContext, config *CreateConfig) func(cmd *cobra.Comm } // add Host IP address as an extra TLS-SAN to expose the k3k cluster - url, err := url.Parse(appCtx.RestConfig.Host) + host, err := resolveServerHost(appCtx.RestConfig.Host, config.kubeconfigServerHost) if err != nil { return err } - host := strings.Split(url.Host, ":") - if config.kubeconfigServerHost != "" { - host = []string{config.kubeconfigServerHost} - } - - cluster.Spec.TLSSANs = []string{host[0]} + cluster.Spec.TLSSANs = []string{host} if err := client.Create(ctx, cluster); err != nil { if apierrors.IsAlreadyExists(err) { @@ -183,16 +177,40 @@ func createAction(appCtx *AppContext, config *CreateConfig) func(cmd *cobra.Comm var kubeconfig *clientcmdapi.Config if err := retry.OnError(availableBackoff, apierrors.IsNotFound, func() error { - kubeconfig, err = cfg.Generate(ctx, client, cluster, host[0], 0) + kubeconfig, err = cfg.Generate(ctx, client, cluster, host, 0) return err }); err != nil { return err } - return writeKubeconfigFile(cluster, kubeconfig, "") + if err := writeKubeconfigFile(cluster, kubeconfig, ""); err != nil { + return err + } + + if cluster.Spec.Mode == v1beta1.HCPClusterMode { + printHCPJoinInstructions(cluster, kubeconfig) + } + + return nil } } +func printHCPJoinInstructions(cluster *v1beta1.Cluster, kc *clientcmdapi.Config) { + tokenSecretName := k3kcluster.TokenSecretName(cluster.Name) + serverURL := kc.Clusters["default"].Server + + logrus.Infof(`To join an external worker node to this HCP cluster: + + 1. On this machine, fetch the cluster token: + + kubectl get secret -n %s %s -o jsonpath='{.data.token}' | base64 -d + + 2. On the worker node, run (replace with the value from step 1): + + curl -sfL https://get.k3s.io | K3S_URL=%s K3S_TOKEN= sh - +`, cluster.Namespace, tokenSecretName, serverURL) +} + func newCluster(name, namespace string, config *CreateConfig) (*v1beta1.Cluster, error) { var storageRequestSize *resource.Quantity if config.storageRequestSize != "" { diff --git a/cli/cmds/kubeconfig.go b/cli/cmds/kubeconfig.go index 39763fd4..1b3ad1b5 100644 --- a/cli/cmds/kubeconfig.go +++ b/cli/cmds/kubeconfig.go @@ -89,14 +89,12 @@ func generate(appCtx *AppContext, cfg *GenerateKubeconfigConfig) func(cmd *cobra return err } - url, err := url.Parse(appCtx.RestConfig.Host) + host, err := resolveServerHost(appCtx.RestConfig.Host, cfg.kubeconfigServerHost) if err != nil { return err } - host := strings.Split(url.Host, ":") if cfg.kubeconfigServerHost != "" { - host = []string{cfg.kubeconfigServerHost} cfg.altNames = append(cfg.altNames, cfg.kubeconfigServerHost) } @@ -118,16 +116,40 @@ func generate(appCtx *AppContext, cfg *GenerateKubeconfigConfig) func(cmd *cobra var kubeconfig *clientcmdapi.Config if err := retry.OnError(controller.Backoff, apierrors.IsNotFound, func() error { - kubeconfig, err = kubeCfg.Generate(ctx, client, &cluster, host[0], 0) + kubeconfig, err = kubeCfg.Generate(ctx, client, &cluster, host, 0) return err }); err != nil { return err } - return writeKubeconfigFile(&cluster, kubeconfig, cfg.configName) + if err := writeKubeconfigFile(&cluster, kubeconfig, cfg.configName); err != nil { + return err + } + + if cluster.Spec.Mode == v1beta1.HCPClusterMode { + printHCPJoinInstructions(&cluster, kubeconfig) + } + + return nil } } +// resolveServerHost returns the host that should be embedded in the kubeconfig +// server URL and used as the TLS-SAN. If override is set it takes precedence; +// otherwise the host is extracted from restConfigHost. +func resolveServerHost(restConfigHost, override string) (string, error) { + if override != "" { + return override, nil + } + + u, err := url.Parse(restConfigHost) + if err != nil { + return "", err + } + + return strings.Split(u.Host, ":")[0], nil +} + func writeKubeconfigFile(cluster *v1beta1.Cluster, kubeconfig *clientcmdapi.Config, configName string) error { if configName == "" { configName = cluster.Namespace + "-" + cluster.Name + "-kubeconfig.yaml"