Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions tool/tsh/common/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,32 @@ func overwriteKubeconfigInEnv(env []string, newPath string) (output []string) {
output = append(output, kubeConfigEnvPrefix+newPath)
return
}

// insertDoubleDashAfterKubectl returns a copy of args with "--" inserted immediately after "kubectl"
// when kubectl flags would otherwise be misinterpreted as tsh flags by kingpin.
// The "--" is only inserted when "kubectl" is the tsh subcommand (first non-flag argument)
// and the argument immediately following it starts with "-".
// See https://github.com/gravitational/teleport/issues/63621 for more details.
func insertDoubleDashAfterKubectl(args []string) []string {
for i, arg := range args {
// Skip flags to find the subcommand position.
if strings.HasPrefix(arg, "-") {
continue
}
// First non-flag argument is the subcommand.
// If it's not "kubectl", then "kubectl" is not the subcommand (e.g. "ssh jake@foo kubectl").
if arg != "kubectl" {
return args
}
// Only insert "--" when the next arg starts with "-" to prevent kingpin from parsing it as a tsh flag.
if i+1 >= len(args) || !strings.HasPrefix(args[i+1], "-") || args[i+1] == "--" {
return args
}
result := make([]string, 0, len(args)+1)
result = append(result, args[:i+1]...)
result = append(result, "--")
result = append(result, args[i+1:]...)
return result
}
return args
}
67 changes: 67 additions & 0 deletions tool/tsh/common/kubectl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package common

import (
"path/filepath"
"slices"
"testing"

"github.com/google/uuid"
Expand Down Expand Up @@ -265,6 +266,72 @@ func Test_overwriteKubeconfigFlagInEnv(t *testing.T) {
require.Equal(t, wantEnv, overwriteKubeconfigInEnv(inputEnv, "new-path"))
}

func Test_insertDoubleDashAfterKubectl(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input []string
expect []string
}{
{
name: "short flag before positional arg",
input: []string{"kubectl", "-n", "default", "get", "pod"},
expect: []string{"kubectl", "--", "-n", "default", "get", "pod"},
},
{
name: "long flag before positional arg",
input: []string{"kubectl", "--namespace", "default", "get", "pod"},
expect: []string{"kubectl", "--", "--namespace", "default", "get", "pod"},
},
{
name: "positional arg first",
input: []string{"kubectl", "get", "pod", "-n", "default"},
expect: []string{"kubectl", "get", "pod", "-n", "default"},
},
{
name: "global flags before kubectl",
input: []string{"--debug", "kubectl", "-n", "default", "get", "pod"},
expect: []string{"--debug", "kubectl", "--", "-n", "default", "get", "pod"},
},
{
name: "double dash already present",
input: []string{"kubectl", "--", "-n", "default", "get", "pod"},
expect: []string{"kubectl", "--", "-n", "default", "get", "pod"},
},
{
name: "no kubectl in args",
input: []string{"ssh", "user@host"},
expect: []string{"ssh", "user@host"},
},
{
name: "kubectl with no extra args",
input: []string{"kubectl"},
expect: []string{"kubectl"},
},
{
name: "kubectl as arg to another command",
input: []string{"ssh", "jake@foo", "kubectl"},
expect: []string{"ssh", "jake@foo", "kubectl"},
},
{
name: "kubectl as arg to another command with flags",
input: []string{"ssh", "jake@foo", "kubectl", "-n", "default", "get", "pod"},
expect: []string{"ssh", "jake@foo", "kubectl", "-n", "default", "get", "pod"},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
original := slices.Clone(test.input)
result := insertDoubleDashAfterKubectl(test.input)
require.Equal(t, test.expect, result)
// Verify original slice is not modified.
require.Equal(t, original, test.input)
})
}
}

func mustSetupKubeconfig(t *testing.T, tshHome, kubeCluster string) string {
kubeconfigLocation := filepath.Join(tshHome, "kubeconfig")
key, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
Expand Down
2 changes: 1 addition & 1 deletion tool/tsh/common/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -1525,7 +1525,7 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error {

// parse CLI commands+flags:
utils.UpdateAppUsageTemplate(app, args)
command, err := app.Parse(args)
command, err := app.Parse(insertDoubleDashAfterKubectl(args))
if errors.Is(err, kingpin.ErrExpectedCommand) {
if _, ok := cf.TSHConfig.Aliases[aliasCommand]; ok {
logger.DebugContext(ctx, "Failing due to recursive alias",
Expand Down
Loading