feat: update kubeconfig generate command (#1158)

* feat: update kubeconfig generate command

* improve messages and docs
This commit is contained in:
Enrico Candino
2026-08-19 13:08:53 +02:00
committed by GitHub
parent 8fec5a4079
commit af7fd3918a
9 changed files with 169 additions and 14 deletions
+1 -1
View File
@@ -144,7 +144,7 @@ EOF
and use the `k3kcli` to retrieve the kubeconfig:
```bash
k3kcli kubeconfig generate --namespace k3k-mycluster --name mycluster
k3kcli kubeconfig generate mycluster
```
+40 -6
View File
@@ -1,6 +1,8 @@
package cmds
import (
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
@@ -49,12 +51,15 @@ func NewKubeconfigGenerateCmd(appCtx *AppContext) *cobra.Command {
cfg := &GenerateKubeconfigConfig{}
cmd := &cobra.Command{
Use: "generate",
Short: "Generate kubeconfig for clusters.",
RunE: generate(appCtx, cfg),
Args: cobra.NoArgs,
Use: "generate",
Short: "Generate kubeconfig for clusters.",
Example: "k3kcli kubeconfig generate [command options] NAME",
RunE: generate(appCtx, cfg),
Args: cobra.MaximumNArgs(1),
}
cmd.ValidArgsFunction = completeClusterNames
CobraFlagNamespace(appCtx, cmd, completeClusterNamespaces)
generateKubeconfigFlags(cmd, cfg)
@@ -64,6 +69,11 @@ func NewKubeconfigGenerateCmd(appCtx *AppContext) *cobra.Command {
func generateKubeconfigFlags(cmd *cobra.Command, cfg *GenerateKubeconfigConfig) {
cmd.Flags().StringVar(&cfg.name, "name", "", "cluster name")
if err := cmd.Flags().MarkDeprecated("name", "it will be removed in a future release, use the NAME argument instead"); err != nil {
logrus.Fatal(err)
}
cmd.Flags().StringVar(&cfg.configName, "config-name", "", "the name of the generated kubeconfig file")
cmd.Flags().StringVar(&cfg.cn, "cn", controller.AdminCommonName, "Common name (CN) of the generated certificates for the kubeconfig")
cmd.Flags().StringSliceVar(&cfg.org, "org", nil, "Organization name (ORG) of the generated certificates for the kubeconfig")
@@ -77,14 +87,38 @@ func generate(appCtx *AppContext, cfg *GenerateKubeconfigConfig) func(cmd *cobra
ctx := cmd.Context()
client := appCtx.Client
name, namespace := cfg.name, appCtx.Namespace(cfg.name)
switch {
case len(args) == 1:
if cfg.name != "" {
logrus.Warnf("the --name flag is deprecated and will be removed in a future release, ignoring it in favor of the '%s' argument", args[0])
}
var err error
if namespace, name, err = resolveClusterArg(appCtx, args[0]); err != nil {
return err
}
case name != "":
logrus.Warn("the --name flag is deprecated and will be removed in a future release, use the NAME argument instead")
default:
return errors.New("expected exactly one cluster name")
}
clusterKey := types.NamespacedName{
Name: cfg.name,
Namespace: appCtx.Namespace(cfg.name),
Name: name,
Namespace: namespace,
}
var cluster v1beta1.Cluster
if err := client.Get(ctx, clusterKey, &cluster); err != nil {
if apierrors.IsNotFound(err) {
return fmt.Errorf("cluster %q not found in namespace %q", name, namespace)
}
return err
}
+111
View File
@@ -0,0 +1,111 @@
package cmds
import (
"testing"
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
)
// Test_generate_clusterKey checks how the cluster name and namespace are resolved from the
// NAME argument and the deprecated --name flag. The cluster is always missing, so the
// "not found" error reports the resolved key without going through the kubeconfig generation.
func Test_generate_clusterKey(t *testing.T) {
tests := []struct {
name string
flagNamespace string
flagName string
args []string
wantErr string
wantWarn string
}{
{
name: "bare name argument defaults to k3k-<name>",
args: []string{"mycluster"},
wantErr: `cluster "mycluster" not found in namespace "k3k-mycluster"`,
},
{
name: "namespace/name argument is split",
args: []string{"myns/mycluster"},
wantErr: `cluster "mycluster" not found in namespace "myns"`,
},
{
name: "bare name argument respects the namespace flag",
flagNamespace: "myns",
args: []string{"mycluster"},
wantErr: `cluster "mycluster" not found in namespace "myns"`,
},
{
name: "namespace/name argument conflicting with the namespace flag errors",
flagNamespace: "otherns",
args: []string{"myns/mycluster"},
wantErr: `namespace mismatch: flag --namespace "otherns" conflicts with argument namespace "myns"`,
},
{
name: "deprecated name flag is still supported",
flagName: "mycluster",
wantErr: `cluster "mycluster" not found in namespace "k3k-mycluster"`,
wantWarn: "the --name flag is deprecated and will be removed in a future release, use the NAME argument instead",
},
{
name: "deprecated name flag respects the namespace flag",
flagNamespace: "myns",
flagName: "mycluster",
wantErr: `cluster "mycluster" not found in namespace "myns"`,
wantWarn: "the --name flag is deprecated and will be removed in a future release, use the NAME argument instead",
},
{
name: "the argument wins over the deprecated name flag",
flagName: "ignored",
args: []string{"myns/mycluster"},
wantErr: `cluster "mycluster" not found in namespace "myns"`,
wantWarn: "the --name flag is deprecated and will be removed in a future release, ignoring it in favor of the 'myns/mycluster' argument",
},
{
name: "no cluster name at all errors",
wantErr: "expected exactly one cluster name",
},
}
scheme := runtime.NewScheme()
require.NoError(t, v1beta1.AddToScheme(scheme))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logs := test.NewGlobal()
defer logs.Reset()
appCtx := &AppContext{
Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
namespace: tt.flagNamespace,
}
cfg := &GenerateKubeconfigConfig{name: tt.flagName}
err := generate(appCtx, cfg)(&cobra.Command{}, tt.args)
assert.EqualError(t, err, tt.wantErr)
var warnings []string
for _, entry := range logs.AllEntries() {
if entry.Level == logrus.WarnLevel {
warnings = append(warnings, entry.Message)
}
}
if tt.wantWarn == "" {
assert.Empty(t, warnings)
return
}
assert.Equal(t, []string{tt.wantWarn}, warnings)
})
}
}
+1 -1
View File
@@ -151,7 +151,7 @@ Add `my-cluster.example.com` to `spec.tlsSANs` so the API server certificate cov
the kubeconfig with the matching endpoint:
```bash
k3kcli kubeconfig generate --namespace my-namespace --name my-virtual-cluster \
k3kcli kubeconfig generate my-namespace/my-virtual-cluster \
--kubeconfig-server https://my-cluster.example.com
```
+6 -1
View File
@@ -194,6 +194,12 @@ Generate kubeconfig for clusters.
k3kcli kubeconfig generate [flags]
----
=== Examples
----
k3kcli kubeconfig generate [command options] NAME
----
=== Options
----
@@ -203,7 +209,6 @@ k3kcli kubeconfig generate [flags]
--expiration-days int Expiration date of the certificates used for the kubeconfig (default 365)
-h, --help help for generate
--kubeconfig-server string override the kubeconfig server host
--name string cluster name
-n, --namespace string namespace of the k3k cluster
--org strings Organization name (ORG) of the generated certificates for the kubeconfig
----
+6 -1
View File
@@ -6,6 +6,12 @@ Generate kubeconfig for clusters.
k3kcli kubeconfig generate [flags]
```
### Examples
```
k3kcli kubeconfig generate [command options] NAME
```
### Options
```
@@ -15,7 +21,6 @@ k3kcli kubeconfig generate [flags]
--expiration-days int Expiration date of the certificates used for the kubeconfig (default 365)
-h, --help help for generate
--kubeconfig-server string override the kubeconfig server host
--name string cluster name
-n, --namespace string namespace of the k3k cluster
--org strings Organization name (ORG) of the generated certificates for the kubeconfig
```
+1 -1
View File
@@ -177,7 +177,7 @@ kubectl get po -n k3k-mycluster
Last thing to do is to get the kubeconfig to connect to the virtual cluster we've just created:
```bash
k3kcli kubeconfig generate --name mycluster --namespace k3k-mycluster --kubeconfig-server localhost:30001
k3kcli kubeconfig generate mycluster --kubeconfig-server localhost:30001
```
+2 -2
View File
@@ -294,8 +294,8 @@ Once the virtual cluster is running, you can connect to it using the CLI:
### CLI Method
```sh
k3kcli kubeconfig generate --namespace k3k-mycluster --name mycluster
export KUBECONFIG=$PWD/mycluster-kubeconfig.yaml
k3kcli kubeconfig generate mycluster
export KUBECONFIG=$PWD/k3k-mycluster-mycluster-kubeconfig.yaml
kubectl get nodes
```
+1 -1
View File
@@ -535,7 +535,7 @@ var _ = When("using the k3kcli", Label("cli"), func() {
By("Generating the kubeconfig")
_, stderr, err = K3kcli("kubeconfig", "generate", "--namespace", clusterNamespace, "--name", clusterName)
_, stderr, err = K3kcli("kubeconfig", "generate", clusterNamespace+"/"+clusterName)
Expect(err).To(Not(HaveOccurred()), string(stderr))
Expect(stderr).To(ContainSubstring("You can start using the cluster"))