mirror of
https://github.com/kubernetes/client-go.git
synced 2025-09-25 14:42:33 +00:00
exec credential provider: add install hint
This commit adds the ability for users to specify an install hint for their exec credential provider binary. In the exec credential provider workflow, if the exec credential binary does not exist, then the user will see some sort of ugly exec: exec: "does-not-exist": executable file not found in $PATH error message. If some user downloads a kubeconfig from somewhere, they may not know that kubectl is trying to use a binary to obtain credentials to auth to the API, and scratch their head when they see this error message. Furthermore, even if a user does know that their kubeconfig is trying to run a binary, they might not know how to obtain the binary. This install hint seeks to ease the above 2 user pains. Signed-off-by: Andrew Keesler <akeesler@vmware.com> Kubernetes-commit: 94e2065df2eef3b198942efb156ef6e27abcc6f9
This commit is contained in:
committed by
Kubernetes Publisher
parent
1e6150831a
commit
6b620f1777
@@ -210,6 +210,11 @@ type ExecConfig struct {
|
||||
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
|
||||
// the same encoding version as the input.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// This text is shown to the user when the executable doesn't seem to be
|
||||
// present. For example, `brew install foo-cli` might be a good InstallHint for
|
||||
// foo-cli on Mac OS systems.
|
||||
InstallHint string `json:"installHint,omitempty"`
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = new(ExecConfig)
|
||||
|
@@ -209,6 +209,11 @@ type ExecConfig struct {
|
||||
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
|
||||
// the same encoding version as the input.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// This text is shown to the user when the executable doesn't seem to be
|
||||
// present. For example, `brew install foo-cli` might be a good InstallHint for
|
||||
// foo-cli on Mac OS systems.
|
||||
InstallHint string `json:"installHint,omitempty"`
|
||||
}
|
||||
|
||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
||||
|
@@ -358,6 +358,7 @@ func autoConvert_v1_ExecConfig_To_api_ExecConfig(in *ExecConfig, out *api.ExecCo
|
||||
out.Args = *(*[]string)(unsafe.Pointer(&in.Args))
|
||||
out.Env = *(*[]api.ExecEnvVar)(unsafe.Pointer(&in.Env))
|
||||
out.APIVersion = in.APIVersion
|
||||
out.InstallHint = in.InstallHint
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -371,6 +372,7 @@ func autoConvert_api_ExecConfig_To_v1_ExecConfig(in *api.ExecConfig, out *ExecCo
|
||||
out.Args = *(*[]string)(unsafe.Pointer(&in.Args))
|
||||
out.Env = *(*[]ExecEnvVar)(unsafe.Pointer(&in.Env))
|
||||
out.APIVersion = in.APIVersion
|
||||
out.InstallHint = in.InstallHint
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientauth "k8s.io/client-go/tools/auth"
|
||||
@@ -269,6 +270,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
||||
}
|
||||
if configAuthInfo.Exec != nil {
|
||||
mergedConfig.ExecProvider = configAuthInfo.Exec
|
||||
mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
|
||||
}
|
||||
|
||||
// if there still isn't enough information to authenticate the user, try prompting
|
||||
@@ -314,6 +316,41 @@ func canIdentifyUser(config restclient.Config) bool {
|
||||
config.ExecProvider != nil
|
||||
}
|
||||
|
||||
// cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no
|
||||
// ANSI escape sequences that could put the terminal in a weird state (e.g.,
|
||||
// "\e[1m" bolds text)
|
||||
func cleanANSIEscapeCodes(s string) string {
|
||||
// spaceControlCharacters includes tab, new line, vertical tab, new page, and
|
||||
// carriage return. These are in the unicode.Cc category, but that category also
|
||||
// contains ESC (U+001B) which we don't want.
|
||||
spaceControlCharacters := unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{Lo: 0x0009, Hi: 0x000D, Stride: 1},
|
||||
},
|
||||
}
|
||||
|
||||
// Why not make this deny-only (instead of allow-only)? Because unicode.C
|
||||
// contains newline and tab characters that we want.
|
||||
allowedRanges := []*unicode.RangeTable{
|
||||
unicode.L,
|
||||
unicode.M,
|
||||
unicode.N,
|
||||
unicode.P,
|
||||
unicode.S,
|
||||
unicode.Z,
|
||||
&spaceControlCharacters,
|
||||
}
|
||||
builder := strings.Builder{}
|
||||
for _, roon := range s {
|
||||
if unicode.IsOneOf(allowedRanges, roon) {
|
||||
builder.WriteRune(roon) // returns nil error, per go doc
|
||||
} else {
|
||||
fmt.Fprintf(&builder, "%U", roon)
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// Namespace implements ClientConfig
|
||||
func (config *DirectClientConfig) Namespace() (string, bool, error) {
|
||||
if config.overrides != nil && config.overrides.Context.Namespace != "" {
|
||||
|
@@ -624,6 +624,26 @@ func TestCreateMissingContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAuthConfigExecInstallHintCleanup(t *testing.T) {
|
||||
config := createValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
Exec: &clientcmdapi.ExecConfig{
|
||||
APIVersion: "client.authentication.k8s.io/v1alpha1",
|
||||
Command: "some-command",
|
||||
InstallHint: "some install hint with \x1b[1mcontrol chars\x1b[0m\nand a newline",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
cleanedInstallHint := "some install hint with U+001B[1mcontrol charsU+001B[0m\nand a newline"
|
||||
|
||||
clientConfig, err := clientBuilder.ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
matchStringArg(cleanedInstallHint, clientConfig.ExecProvider.InstallHint, t)
|
||||
}
|
||||
|
||||
func TestInClusterClientConfigPrecedence(t *testing.T) {
|
||||
tt := []struct {
|
||||
overrides *ConfigOverrides
|
||||
@@ -850,3 +870,70 @@ users:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCleanANSIEscapeCodes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in, out string
|
||||
}{
|
||||
{
|
||||
name: "DenyBoldCharacters",
|
||||
in: "\x1b[1mbold tuna\x1b[0m, fish, \x1b[1mbold marlin\x1b[0m",
|
||||
out: "U+001B[1mbold tunaU+001B[0m, fish, U+001B[1mbold marlinU+001B[0m",
|
||||
},
|
||||
{
|
||||
name: "DenyCursorNavigation",
|
||||
in: "\x1b[2Aup up, \x1b[2Cright right",
|
||||
out: "U+001B[2Aup up, U+001B[2Cright right",
|
||||
},
|
||||
{
|
||||
name: "DenyClearScreen",
|
||||
in: "clear: \x1b[2J",
|
||||
out: "clear: U+001B[2J",
|
||||
},
|
||||
{
|
||||
name: "AllowSpaceCharactersUnchanged",
|
||||
in: "tuna\nfish\r\nmarlin\t\r\ntuna\vfish\fmarlin",
|
||||
},
|
||||
{
|
||||
name: "AllowLetters",
|
||||
in: "alpha: \u03b1, beta: \u03b2, gamma: \u03b3",
|
||||
},
|
||||
{
|
||||
name: "AllowMarks",
|
||||
in: "tu\u0301na with a mark over the u, fi\u0302sh with a mark over the i," +
|
||||
" ma\u030Arlin with a mark over the a",
|
||||
},
|
||||
{
|
||||
name: "AllowNumbers",
|
||||
in: "t1na, f2sh, m3rlin, t12a, f34h, m56lin, t123, f456, m567n",
|
||||
},
|
||||
{
|
||||
name: "AllowPunctuation",
|
||||
in: "\"here's a sentence; with! some...punctuation ;)\"",
|
||||
},
|
||||
{
|
||||
name: "AllowSymbols",
|
||||
in: "the integral of f(x) from 0 to n approximately equals the sum of f(x)" +
|
||||
" from a = 0 to n, where a and n are natural numbers:" +
|
||||
"\u222b\u2081\u207F f(x) dx \u2248 \u2211\u2090\u208C\u2081\u207F f(x)," +
|
||||
" a \u2208 \u2115, n \u2208 \u2115",
|
||||
},
|
||||
{
|
||||
name: "AllowSepatators",
|
||||
in: "here is a paragraph separator\u2029and here\u2003are\u2003some" +
|
||||
"\u2003em\u2003spaces",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if len(test.out) == 0 {
|
||||
test.out = test.in
|
||||
}
|
||||
|
||||
if actualOut := cleanANSIEscapeCodes(test.in); test.out != actualOut {
|
||||
t.Errorf("expected %q, actual %q", test.out, actualOut)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user