Merge pull request #552 from mattmoyer/nicer-generated-kubeconfig-names

Generate more helpful context/cluster/user names in `pinniped get kubeconfig`
This commit is contained in:
Matt Moyer 2021-04-05 11:35:07 -07:00 committed by GitHub
commit 9cd2b6e855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 70 deletions

View File

@ -86,6 +86,7 @@ type getKubeconfigParams struct {
staticTokenEnvName string staticTokenEnvName string
oidc getKubeconfigOIDCParams oidc getKubeconfigOIDCParams
concierge getKubeconfigConciergeParams concierge getKubeconfigConciergeParams
generatedNameSuffix string
} }
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
@ -130,6 +131,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)") f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)")
f.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation") f.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation")
f.StringVarP(&flags.outputPath, "output", "o", "", "Output file path (default: stdout)") f.StringVarP(&flags.outputPath, "output", "o", "", "Output file path (default: stdout)")
f.StringVar(&flags.generatedNameSuffix, "generated-name-suffix", "-pinniped", "Suffix to append to generated cluster, context, user kubeconfig entries")
mustMarkHidden(cmd, "oidc-debug-session-cache") mustMarkHidden(cmd, "oidc-debug-session-cache")
@ -178,15 +180,23 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if err != nil { if err != nil {
return fmt.Errorf("could not load --kubeconfig: %w", err) return fmt.Errorf("could not load --kubeconfig: %w", err)
} }
cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, flags.kubeconfigContextOverride) currentKubeconfigNames, err := getCurrentContext(currentKubeConfig, flags)
if err != nil { if err != nil {
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err) return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
} }
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix) clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
if err != nil { if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err) return fmt.Errorf("could not configure Kubernetes client: %w", err)
} }
// Generate the new context/cluster/user names by appending the --generated-name-suffix to the original values.
newKubeconfigNames := &kubeconfigNames{
ContextName: currentKubeconfigNames.ContextName + flags.generatedNameSuffix,
UserName: currentKubeconfigNames.UserName + flags.generatedNameSuffix,
ClusterName: currentKubeconfigNames.ClusterName + flags.generatedNameSuffix,
}
if !flags.concierge.disabled { if !flags.concierge.disabled {
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps) credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
if err != nil { if err != nil {
@ -236,7 +246,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName) execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName)
} }
kubeconfig := newExecKubeconfig(cluster, &execConfig) kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil { if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err return err
} }
@ -271,13 +281,33 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if flags.oidc.requestAudience != "" { if flags.oidc.requestAudience != "" {
execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience) execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience)
} }
kubeconfig := newExecKubeconfig(cluster, &execConfig) kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil { if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err return err
} }
return writeConfigAsYAML(out, kubeconfig) return writeConfigAsYAML(out, kubeconfig)
} }
type kubeconfigNames struct{ ContextName, UserName, ClusterName string }
func getCurrentContext(currentKubeConfig clientcmdapi.Config, flags getKubeconfigParams) (*kubeconfigNames, error) {
contextName := currentKubeConfig.CurrentContext
if flags.kubeconfigContextOverride != "" {
contextName = flags.kubeconfigContextOverride
}
ctx := currentKubeConfig.Contexts[contextName]
if ctx == nil {
return nil, fmt.Errorf("no such context %q", contextName)
}
if _, exists := currentKubeConfig.Clusters[ctx.Cluster]; !exists {
return nil, fmt.Errorf("no such cluster %q", ctx.Cluster)
}
if _, exists := currentKubeConfig.AuthInfos[ctx.AuthInfo]; !exists {
return nil, fmt.Errorf("no such user %q", ctx.AuthInfo)
}
return &kubeconfigNames{ContextName: contextName, UserName: ctx.AuthInfo, ClusterName: ctx.Cluster}, nil
}
func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.Interface, flags getKubeconfigParams, deps kubeconfigDeps) (*configv1alpha1.CredentialIssuer, error) { func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.Interface, flags getKubeconfigParams, deps kubeconfigDeps) (*configv1alpha1.CredentialIssuer, error) {
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log) credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
if err != nil { if err != nil {
@ -461,15 +491,14 @@ func getConciergeFrontend(credentialIssuer *configv1alpha1.CredentialIssuer, mod
return nil, fmt.Errorf("could not find successful Concierge strategy matching --concierge-mode=%s", mode.String()) return nil, fmt.Errorf("could not find successful Concierge strategy matching --concierge-mode=%s", mode.String())
} }
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig) clientcmdapi.Config { func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig, newNames *kubeconfigNames) clientcmdapi.Config {
const name = "pinniped"
return clientcmdapi.Config{ return clientcmdapi.Config{
Kind: "Config", Kind: "Config",
APIVersion: clientcmdapi.SchemeGroupVersion.Version, APIVersion: clientcmdapi.SchemeGroupVersion.Version,
Clusters: map[string]*clientcmdapi.Cluster{name: cluster}, Clusters: map[string]*clientcmdapi.Cluster{newNames.ClusterName: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{name: {Exec: execConfig}}, AuthInfos: map[string]*clientcmdapi.AuthInfo{newNames.UserName: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{name: {Cluster: name, AuthInfo: name}}, Contexts: map[string]*clientcmdapi.Context{newNames.ContextName: {Cluster: newNames.ClusterName, AuthInfo: newNames.UserName}},
CurrentContext: name, CurrentContext: newNames.ContextName,
} }
} }
@ -560,18 +589,6 @@ func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
return nil return nil
} }
func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Config, currentContextNameOverride string) (*clientcmdapi.Cluster, error) {
contextName := currentKubeConfig.CurrentContext
if currentContextNameOverride != "" {
contextName = currentContextNameOverride
}
ctx := currentKubeConfig.Contexts[contextName]
if ctx == nil {
return nil, fmt.Errorf("no such context %q", contextName)
}
return currentKubeConfig.Clusters[ctx.Cluster], nil
}
func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconfig clientcmdapi.Config, log logr.Logger) error { func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconfig clientcmdapi.Config, log logr.Logger) error {
if flags.skipValidate { if flags.skipValidate {
return nil return nil

View File

@ -73,6 +73,7 @@ func TestGetKubeconfig(t *testing.T) {
--concierge-endpoint string API base for the Concierge endpoint --concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI) --concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false) --concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
-h, --help help for kubeconfig -h, --help help for kubeconfig
--kubeconfig string Path to kubeconfig file --kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context) --kubeconfig-context string Kubeconfig context name (default: current active context)
@ -133,7 +134,7 @@ func TestGetKubeconfig(t *testing.T) {
`), `),
}, },
{ {
name: "invalid kubeconfig context", name: "invalid kubeconfig context, missing",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid", "--kubeconfig-context", "invalid",
@ -143,6 +144,28 @@ func TestGetKubeconfig(t *testing.T) {
Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid" Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"
`), `),
}, },
{
name: "invalid kubeconfig context, missing cluster",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid-context-no-such-cluster",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not load --kubeconfig/--kubeconfig-context: no such cluster "invalid-cluster"
`),
},
{
name: "invalid kubeconfig context, missing user",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid-context-no-such-user",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not load --kubeconfig/--kubeconfig-context: no such user "invalid-user"
`),
},
{ {
name: "clientset creation failure", name: "clientset creation failure",
args: []string{ args: []string{
@ -584,17 +607,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value server: https://fake-server-url-value
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -653,17 +676,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value server: https://fake-server-url-value
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -733,17 +756,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value server: https://fake-server-url-value
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -785,6 +808,7 @@ func TestGetKubeconfig(t *testing.T) {
"--oidc-debug-session-cache", "--oidc-debug-session-cache",
"--oidc-request-audience", "test-audience", "--oidc-request-audience", "test-audience",
"--skip-validation", "--skip-validation",
"--generated-name-suffix", "-sso",
}, },
conciergeObjects: []runtime.Object{ conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{ &configv1alpha1.CredentialIssuer{
@ -815,17 +839,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: %s certificate-authority-data: %s
server: https://explicit-concierge-endpoint.example.com server: https://explicit-concierge-endpoint.example.com
name: pinniped name: kind-cluster-sso
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-sso
user: pinniped user: kind-user-sso
name: pinniped name: kind-context-sso
current-context: pinniped current-context: kind-context-sso
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-sso
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -929,17 +953,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: %s certificate-authority-data: %s
server: https://impersonation-proxy-endpoint.test server: https://impersonation-proxy-endpoint.test
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1
@ -1035,17 +1059,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster: - cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E= certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
server: https://impersonation-proxy-endpoint.test server: https://impersonation-proxy-endpoint.test
name: pinniped name: kind-cluster-pinniped
contexts: contexts:
- context: - context:
cluster: pinniped cluster: kind-cluster-pinniped
user: pinniped user: kind-user-pinniped
name: pinniped name: kind-context-pinniped
current-context: pinniped current-context: kind-context-pinniped
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: pinniped - name: kind-user-pinniped
user: user:
exec: exec:
apiVersion: client.authentication.k8s.io/v1beta1 apiVersion: client.authentication.k8s.io/v1beta1

View File

@ -3,25 +3,33 @@ clusters:
- cluster: - cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value
server: https://fake-server-url-value server: https://fake-server-url-value
name: kind-kind name: kind-cluster
- cluster: - cluster:
certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value
server: https://some-other-fake-server-url-value server: https://some-other-fake-server-url-value
name: some-other-cluster name: some-other-cluster
contexts: contexts:
- context: - context:
cluster: kind-kind cluster: kind-cluster
user: kind-kind user: kind-user
name: kind-kind name: kind-context
- context: - context:
cluster: some-other-cluster cluster: some-other-cluster
user: some-other-user user: some-other-user
name: some-other-context name: some-other-context
current-context: kind-kind - context:
cluster: invalid-cluster
user: some-other-user
name: invalid-context-no-such-cluster
- context:
cluster: some-other-cluster
user: invalid-user
name: invalid-context-no-such-user
current-context: kind-context
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
- name: kind-kind - name: kind-user
user: user:
client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-data-value client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-data-value
client-key-data: ZmFrZS1jbGllbnQta2V5LWRhdGEtdmFsdWU= # fake-client-key-data-value client-key-data: ZmFrZS1jbGllbnQta2V5LWRhdGEtdmFsdWU= # fake-client-key-data-value

View File

@ -53,7 +53,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -68,7 +68,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -84,7 +84,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -100,7 +100,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info:
@ -209,12 +209,12 @@ func TestWhoami(t *testing.T) {
name: "different kubeconfig context, but same as current", name: "different kubeconfig context, but same as current",
args: []string{ args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "kind-kind", "--kubeconfig-context", "kind-context",
}, },
wantStdout: here.Doc(` wantStdout: here.Doc(`
Current cluster info: Current cluster info:
Name: kind-kind Name: kind-cluster
URL: https://fake-server-url-value URL: https://fake-server-url-value
Current user info: Current user info: