Files
k3k/cli/cmds/cluster_create.go
T
Hussein GalalandGitHub b81073619a Generate kubeconfig secret (#392)
* Generate kubeconfig secret

Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>

* fix typo

Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>

* fix typo

Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>

---------

Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>
2025-06-23 14:31:36 +03:00

214 lines
5.5 KiB
Go

package cmds
import (
"context"
"errors"
"net/url"
"strings"
"time"
"github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1"
k3kcluster "github.com/rancher/k3k/pkg/controller/cluster"
"github.com/rancher/k3k/pkg/controller/kubeconfig"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/util/retry"
"k8s.io/utils/ptr"
)
type CreateConfig struct {
token string
clusterCIDR string
serviceCIDR string
servers int
agents int
serverArgs cli.StringSlice
agentArgs cli.StringSlice
serverEnvs cli.StringSlice
agentEnvs cli.StringSlice
persistenceType string
storageClassName string
storageRequestSize string
version string
mode string
kubeconfigServerHost string
policy string
}
func NewClusterCreateCmd(appCtx *AppContext) *cli.Command {
createConfig := &CreateConfig{}
flags := CommonFlags(appCtx)
flags = append(flags, FlagNamespace(appCtx))
flags = append(flags, newCreateFlags(createConfig)...)
return &cli.Command{
Name: "create",
Usage: "Create new cluster",
UsageText: "k3kcli cluster create [command options] NAME",
Action: createAction(appCtx, createConfig),
Flags: flags,
HideHelpCommand: true,
}
}
func createAction(appCtx *AppContext, config *CreateConfig) cli.ActionFunc {
return func(clx *cli.Context) error {
ctx := context.Background()
client := appCtx.Client
if clx.NArg() != 1 {
return cli.ShowSubcommandHelp(clx)
}
name := clx.Args().First()
if name == k3kcluster.ClusterInvalidName {
return errors.New("invalid cluster name")
}
if config.mode == string(v1alpha1.SharedClusterMode) && config.agents != 0 {
return errors.New("invalid flag, --agents flag is only allowed in virtual mode")
}
namespace := appCtx.Namespace(name)
if err := createNamespace(ctx, client, namespace, config.policy); err != nil {
return err
}
if strings.Contains(config.version, "+") {
orig := config.version
config.version = strings.Replace(config.version, "+", "-", -1)
logrus.Warnf("Invalid K3s docker reference version: '%s'. Using '%s' instead", orig, config.version)
}
if config.token != "" {
logrus.Info("Creating cluster token secret")
obj := k3kcluster.TokenSecretObj(config.token, name, namespace)
if err := client.Create(ctx, &obj); err != nil {
return err
}
}
logrus.Infof("Creating cluster [%s] in namespace [%s]", name, namespace)
cluster := newCluster(name, namespace, config)
cluster.Spec.Expose = &v1alpha1.ExposeConfig{
NodePort: &v1alpha1.NodePortConfig{},
}
// add Host IP address as an extra TLS-SAN to expose the k3k cluster
url, err := url.Parse(appCtx.RestConfig.Host)
if err != nil {
return err
}
host := strings.Split(url.Host, ":")
if config.kubeconfigServerHost != "" {
host = []string{config.kubeconfigServerHost}
}
cluster.Spec.TLSSANs = []string{host[0]}
if err := client.Create(ctx, cluster); err != nil {
if apierrors.IsAlreadyExists(err) {
logrus.Infof("Cluster [%s] already exists", name)
} else {
return err
}
}
logrus.Infof("Extracting Kubeconfig for [%s] cluster", name)
logrus.Infof("waiting for cluster to be available..")
// retry every 5s for at most 2m, or 25 times
availableBackoff := wait.Backoff{
Duration: 5 * time.Second,
Cap: 2 * time.Minute,
Steps: 25,
}
cfg := kubeconfig.New()
var kubeconfig *clientcmdapi.Config
if err := retry.OnError(availableBackoff, apierrors.IsNotFound, func() error {
kubeconfig, err = cfg.Generate(ctx, client, cluster, host[0])
return err
}); err != nil {
return err
}
return writeKubeconfigFile(cluster, kubeconfig)
}
}
func newCluster(name, namespace string, config *CreateConfig) *v1alpha1.Cluster {
cluster := &v1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
TypeMeta: metav1.TypeMeta{
Kind: "Cluster",
APIVersion: "k3k.io/v1alpha1",
},
Spec: v1alpha1.ClusterSpec{
Servers: ptr.To(int32(config.servers)),
Agents: ptr.To(int32(config.agents)),
ClusterCIDR: config.clusterCIDR,
ServiceCIDR: config.serviceCIDR,
ServerArgs: config.serverArgs.Value(),
AgentArgs: config.agentArgs.Value(),
ServerEnvs: env(config.serverEnvs.Value()),
AgentEnvs: env(config.agentEnvs.Value()),
Version: config.version,
Mode: v1alpha1.ClusterMode(config.mode),
Persistence: v1alpha1.PersistenceConfig{
Type: v1alpha1.PersistenceMode(config.persistenceType),
StorageClassName: ptr.To(config.storageClassName),
StorageRequestSize: config.storageRequestSize,
},
},
}
if config.storageClassName == "" {
cluster.Spec.Persistence.StorageClassName = nil
}
if config.token != "" {
cluster.Spec.TokenSecretRef = &v1.SecretReference{
Name: k3kcluster.TokenSecretName(name),
Namespace: namespace,
}
}
return cluster
}
func env(envSlice []string) []v1.EnvVar {
var envVars []v1.EnvVar
for _, env := range envSlice {
keyValue := strings.Split(env, "=")
if len(keyValue) != 2 {
logrus.Fatalf("incorrect value for environment variable %s", env)
}
envVars = append(envVars, v1.EnvVar{
Name: keyValue[0],
Value: keyValue[1],
})
}
return envVars
}