Allow to define exec credential plugin config options from kubectl

This commit adds support of setting config options to the exec plugin
from cli.

Next options are added:
  * --exec-command new command for the exec credential plugin
  * --exec-api-version API version of the exec credential plugin.
  * --exec-arg new arguments for the exec credential plugin command
  * --exec-env add, update or remove environment values for the exec credential plugin
k3s-v1.15.3
Mike Fedosin 2019-01-23 23:48:26 +01:00
parent 7a8e11c16d
commit a556e4fcd1
2 changed files with 360 additions and 2 deletions

View File

@ -48,11 +48,22 @@ type createAuthInfoOptions struct {
authProviderArgs map[string]string
authProviderArgsToRemove []string
execCommand cliflag.StringFlag
execAPIVersion cliflag.StringFlag
execArgs []string
execEnv map[string]string
execEnvToRemove []string
}
const (
flagAuthProvider = "auth-provider"
flagAuthProviderArg = "auth-provider-arg"
flagExecCommand = "exec-command"
flagExecAPIVersion = "exec-api-version"
flagExecArg = "exec-arg"
flagExecEnv = "exec-env"
)
var (
@ -90,7 +101,19 @@ var (
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-id=foo --auth-provider-arg=client-secret=bar
# Remove the "client-secret" config value for the OpenID Connect auth provider for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-secret-`)
kubectl config set-credentials cluster-admin --auth-provider=oidc --auth-provider-arg=client-secret-
# Enable new exec auth plugin for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --exec-command=/path/to/the/executable --exec-api-version=client.authentication.k8s.io/v1beta
# Define new exec auth plugin args for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --exec-arg=arg1 --exec-arg=arg2
# Create or update exec auth plugin environment variables for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --exec-env=key1=val1 --exec-env=key2=val2
# Remove exec auth plugin environment variables for the "cluster-admin" entry
kubectl config set-credentials cluster-admin --exec-env=var-to-remove-`)
)
// NewCmdConfigSetAuthInfo returns an Command option instance for 'config set-credentials' sub command
@ -101,7 +124,30 @@ func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess)
func newCmdConfigSetAuthInfo(out io.Writer, options *createAuthInfoOptions) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("set-credentials NAME [--%v=path/to/certfile] [--%v=path/to/keyfile] [--%v=bearer_token] [--%v=basic_user] [--%v=basic_password] [--%v=provider_name] [--%v=key=value]", clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword, flagAuthProvider, flagAuthProviderArg),
Use: fmt.Sprintf(
"set-credentials NAME [--%v=path/to/certfile] "+
"[--%v=path/to/keyfile] "+
"[--%v=bearer_token] "+
"[--%v=basic_user] "+
"[--%v=basic_password] "+
"[--%v=provider_name] "+
"[--%v=key=value] "+
"[--%v=exec_command] "+
"[--%v=exec_api_version] "+
"[--%v=arg] "+
"[--%v=key=value]",
clientcmd.FlagCertFile,
clientcmd.FlagKeyFile,
clientcmd.FlagBearerToken,
clientcmd.FlagUsername,
clientcmd.FlagPassword,
flagAuthProvider,
flagAuthProviderArg,
flagExecCommand,
flagExecAPIVersion,
flagExecArg,
flagExecEnv,
),
DisableFlagsInUseLine: true,
Short: i18n.T("Sets a user entry in kubeconfig"),
Long: createAuthInfoLong,
@ -126,6 +172,10 @@ func newCmdConfigSetAuthInfo(out io.Writer, options *createAuthInfoOptions) *cob
cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in kubeconfig")
cmd.Flags().Var(&options.authProvider, flagAuthProvider, "Auth provider for the user entry in kubeconfig")
cmd.Flags().StringSlice(flagAuthProviderArg, nil, "'key=value' arguments for the auth provider")
cmd.Flags().Var(&options.execCommand, flagExecCommand, "Command for the exec credential plugin for the user entry in kubeconfig")
cmd.Flags().Var(&options.execAPIVersion, flagExecAPIVersion, "API version of the exec credential plugin for the user entry in kubeconfig")
cmd.Flags().StringSlice(flagExecArg, nil, "New arguments for the exec credential plugin command for the user entry in kubeconfig")
cmd.Flags().StringArray(flagExecEnv, nil, "'key=value' environment values for the exec credential plugin")
f := cmd.Flags().VarPF(&options.embedCertData, clientcmd.FlagEmbedCerts, "", "Embed client cert/key for the user entry in kubeconfig")
f.NoOptDefVal = "true"
@ -226,6 +276,72 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
}
}
if o.execCommand.Provided() {
newExecCommand := o.execCommand.Value()
// create new Exec if doesn't exist, otherwise just modify the command
if modifiedAuthInfo.Exec == nil {
modifiedAuthInfo.Exec = &clientcmdapi.ExecConfig{
Command: newExecCommand,
}
} else {
modifiedAuthInfo.Exec.Command = newExecCommand
// explicitly reset exec arguments
modifiedAuthInfo.Exec.Args = nil
}
}
// modify next values only if Exec exists, ignore these changes otherwise
if modifiedAuthInfo.Exec != nil {
if o.execAPIVersion.Provided() {
modifiedAuthInfo.Exec.APIVersion = o.execAPIVersion.Value()
}
// rewrite exec arguments list with new values
if o.execArgs != nil {
modifiedAuthInfo.Exec.Args = o.execArgs
}
// iterate over the existing exec env values and remove the specified
if o.execEnvToRemove != nil {
newExecEnv := []clientcmdapi.ExecEnvVar{}
for _, value := range modifiedAuthInfo.Exec.Env {
needToRemove := false
for _, elemToRemove := range o.execEnvToRemove {
if value.Name == elemToRemove {
needToRemove = true
break
}
}
if !needToRemove {
newExecEnv = append(newExecEnv, value)
}
}
modifiedAuthInfo.Exec.Env = newExecEnv
}
// update or create specified environment variables for the exec plugin
if o.execEnv != nil {
newEnv := []clientcmdapi.ExecEnvVar{}
for newEnvName, newEnvValue := range o.execEnv {
needToCreate := true
for i := 0; i < len(modifiedAuthInfo.Exec.Env); i++ {
if modifiedAuthInfo.Exec.Env[i].Name == newEnvName {
// update the existing value
needToCreate = false
modifiedAuthInfo.Exec.Env[i].Value = newEnvValue
break
}
}
if needToCreate {
// create a new env value
newEnv = append(newEnv, clientcmdapi.ExecEnvVar{Name: newEnvName, Value: newEnvValue})
}
}
modifiedAuthInfo.Exec.Env = append(modifiedAuthInfo.Exec.Env, newEnv...)
}
}
// If any auth info was set, make sure any other existing auth types are cleared
if setToken || setBasic {
if !setToken {
@ -260,6 +376,27 @@ func (o *createAuthInfoOptions) complete(cmd *cobra.Command, out io.Writer) erro
o.authProviderArgsToRemove = removePairs
}
execArgs, err := cmd.Flags().GetStringSlice(flagExecArg)
if err != nil {
return fmt.Errorf("Error: %s", err)
}
if len(execArgs) > 0 {
o.execArgs = execArgs
}
execEnv, err := cmd.Flags().GetStringArray(flagExecEnv)
if err != nil {
return fmt.Errorf("Error: %s", err)
}
if len(execEnv) > 0 {
newPairs, removePairs, err := cmdutil.ParsePairs(execEnv, flagExecEnv, true)
if err != nil {
return fmt.Errorf("Error: %s", err)
}
o.execEnv = newPairs
o.execEnvToRemove = removePairs
}
o.name = args[0]
return nil
}

View File

@ -156,6 +156,48 @@ func TestCreateAuthInfoOptions(t *testing.T) {
},
wantCompleteErr: true,
},
{
name: "test10",
flags: []string{
"--exec-command=example-client-go-exec-plugin",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
execCommand: stringFlagFor("example-client-go-exec-plugin"),
},
},
{
name: "test11",
flags: []string{
"--exec-command=example-client-go-exec-plugin",
"--exec-arg=arg1",
"--exec-arg=arg2",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
execCommand: stringFlagFor("example-client-go-exec-plugin"),
execArgs: []string{"arg1", "arg2"},
},
},
{
name: "test12",
flags: []string{
"--exec-command=example-client-go-exec-plugin",
"--exec-env=key1=val1",
"--exec-env=key2=val2",
"--exec-env=env-remove1-",
"--exec-env=env-remove2-",
"me",
},
wantOptions: &createAuthInfoOptions{
name: "me",
execCommand: stringFlagFor("example-client-go-exec-plugin"),
execEnv: map[string]string{"key1": "val1", "key2": "val2"},
execEnvToRemove: []string{"env-remove1", "env-remove2"},
},
},
}
for _, tt := range tests {
@ -205,6 +247,185 @@ func TestCreateAuthInfoOptions(t *testing.T) {
}
}
func TestModifyExistingAuthInfo(t *testing.T) {
tests := []struct {
name string
flags []string
wantParseErr bool
wantCompleteErr bool
wantValidateErr bool
existingAuthInfo clientcmdapi.AuthInfo
wantAuthInfo clientcmdapi.AuthInfo
}{
{
name: "1. create new exec config",
flags: []string{
"--exec-command=example-client-go-exec-plugin",
"--exec-api-version=client.authentication.k8s.io/v1",
"me",
},
existingAuthInfo: clientcmdapi.AuthInfo{},
wantAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "example-client-go-exec-plugin",
APIVersion: "client.authentication.k8s.io/v1",
},
},
},
{
name: "2. redefine exec args",
flags: []string{
"--exec-arg=new-arg1",
"--exec-arg=new-arg2",
"me",
},
existingAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "example-client-go-exec-plugin",
APIVersion: "client.authentication.k8s.io/v1beta1",
Args: []string{"existing-arg1", "existing-arg2"},
},
},
wantAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "example-client-go-exec-plugin",
APIVersion: "client.authentication.k8s.io/v1beta1",
Args: []string{"new-arg1", "new-arg2"},
},
},
},
{
name: "3. reset exec args",
flags: []string{
"--exec-command=example-client-go-exec-plugin",
"me",
},
existingAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "example-client-go-exec-plugin",
APIVersion: "client.authentication.k8s.io/v1beta1",
Args: []string{"existing-arg1", "existing-arg2"},
},
},
wantAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "example-client-go-exec-plugin",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
},
},
{
name: "4. modify exec env variables",
flags: []string{
"--exec-command=example-client-go-exec-plugin",
"--exec-env=name1=value1000",
"--exec-env=name3=value3",
"--exec-env=name2-",
"--exec-env=non-existing-",
"me",
},
existingAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "existing-command",
APIVersion: "client.authentication.k8s.io/v1beta1",
Env: []clientcmdapi.ExecEnvVar{
{Name: "name1", Value: "value1"},
{Name: "name2", Value: "value2"},
},
},
},
wantAuthInfo: clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "example-client-go-exec-plugin",
APIVersion: "client.authentication.k8s.io/v1beta1",
Env: []clientcmdapi.ExecEnvVar{
{Name: "name1", Value: "value1000"},
{Name: "name3", Value: "value3"},
},
},
},
},
{
name: "5. modify auth provider arguments",
flags: []string{
"--auth-provider=new-auth-provider",
"--auth-provider-arg=key1=val1000",
"--auth-provider-arg=key3=val3",
"--auth-provider-arg=key2-",
"--auth-provider-arg=non-existing-",
"me",
},
existingAuthInfo: clientcmdapi.AuthInfo{
AuthProvider: &clientcmdapi.AuthProviderConfig{
Name: "auth-provider",
Config: map[string]string{
"key1": "val1",
"key2": "val2",
},
},
},
wantAuthInfo: clientcmdapi.AuthInfo{
AuthProvider: &clientcmdapi.AuthProviderConfig{
Name: "new-auth-provider",
Config: map[string]string{
"key1": "val1000",
"key3": "val3",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buff := new(bytes.Buffer)
opts := new(createAuthInfoOptions)
cmd := newCmdConfigSetAuthInfo(buff, opts)
if err := cmd.ParseFlags(tt.flags); err != nil {
if !tt.wantParseErr {
t.Errorf("case %s: parsing error for flags %q: %v: %s", tt.name, tt.flags, err, buff)
}
return
}
if tt.wantParseErr {
t.Errorf("case %s: expected parsing error for flags %q: %s", tt.name, tt.flags, buff)
return
}
if err := opts.complete(cmd, buff); err != nil {
if !tt.wantCompleteErr {
t.Errorf("case %s: complete() error for flags %q: %s", tt.name, tt.flags, buff)
}
return
}
if tt.wantCompleteErr {
t.Errorf("case %s: complete() expected errors for flags %q: %s", tt.name, tt.flags, buff)
return
}
if err := opts.validate(); err != nil {
if !tt.wantValidateErr {
t.Errorf("case %s: flags %q: validate failed: %v", tt.name, tt.flags, err)
}
return
}
if tt.wantValidateErr {
t.Errorf("case %s: flags %q: expected validate to fail", tt.name, tt.flags)
return
}
modifiedAuthInfo := opts.modifyAuthInfo(tt.existingAuthInfo)
if !reflect.DeepEqual(modifiedAuthInfo, tt.wantAuthInfo) {
t.Errorf("case %s: flags %q: mis-matched auth info,\nwanted=%#v\ngot= %#v", tt.name, tt.flags, tt.wantAuthInfo, modifiedAuthInfo)
}
})
}
}
type createAuthInfoTest struct {
description string
config clientcmdapi.Config