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
oidc getKubeconfigOIDCParams
concierge getKubeconfigConciergeParams
generatedNameSuffix string
}
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.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation")
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")
@ -178,15 +180,23 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if err != nil {
return fmt.Errorf("could not load --kubeconfig: %w", err)
}
cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, flags.kubeconfigContextOverride)
currentKubeconfigNames, err := getCurrentContext(currentKubeConfig, flags)
if err != nil {
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
}
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
if err != nil {
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 {
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
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)
}
kubeconfig := newExecKubeconfig(cluster, &execConfig)
kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
return err
}
@ -271,13 +281,33 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
if 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 {
return err
}
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) {
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
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())
}
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig) clientcmdapi.Config {
const name = "pinniped"
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig, newNames *kubeconfigNames) clientcmdapi.Config {
return clientcmdapi.Config{
Kind: "Config",
APIVersion: clientcmdapi.SchemeGroupVersion.Version,
Clusters: map[string]*clientcmdapi.Cluster{name: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{name: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{name: {Cluster: name, AuthInfo: name}},
CurrentContext: name,
Clusters: map[string]*clientcmdapi.Cluster{newNames.ClusterName: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{newNames.UserName: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{newNames.ContextName: {Cluster: newNames.ClusterName, AuthInfo: newNames.UserName}},
CurrentContext: newNames.ContextName,
}
}
@ -560,18 +589,6 @@ func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
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 {
if flags.skipValidate {
return nil

View File

@ -73,6 +73,7 @@ func TestGetKubeconfig(t *testing.T) {
--concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--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
--kubeconfig string Path to kubeconfig file
--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{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid",
@ -143,6 +144,28 @@ func TestGetKubeconfig(t *testing.T) {
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",
args: []string{
@ -584,17 +607,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -653,17 +676,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -733,17 +756,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -785,6 +808,7 @@ func TestGetKubeconfig(t *testing.T) {
"--oidc-debug-session-cache",
"--oidc-request-audience", "test-audience",
"--skip-validation",
"--generated-name-suffix", "-sso",
},
conciergeObjects: []runtime.Object{
&configv1alpha1.CredentialIssuer{
@ -815,17 +839,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: %s
server: https://explicit-concierge-endpoint.example.com
name: pinniped
name: kind-cluster-sso
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-sso
user: kind-user-sso
name: kind-context-sso
current-context: kind-context-sso
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-sso
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -929,17 +953,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: %s
server: https://impersonation-proxy-endpoint.test
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -1035,17 +1059,17 @@ func TestGetKubeconfig(t *testing.T) {
- cluster:
certificate-authority-data: dGVzdC1jb25jaWVyZ2UtY2E=
server: https://impersonation-proxy-endpoint.test
name: pinniped
name: kind-cluster-pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: pinniped
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1

View File

@ -3,25 +3,33 @@ clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value
server: https://fake-server-url-value
name: kind-kind
name: kind-cluster
- cluster:
certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value
server: https://some-other-fake-server-url-value
name: some-other-cluster
contexts:
- context:
cluster: kind-kind
user: kind-kind
name: kind-kind
cluster: kind-cluster
user: kind-user
name: kind-context
- context:
cluster: some-other-cluster
user: some-other-user
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
preferences: {}
users:
- name: kind-kind
- name: kind-user
user:
client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-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(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -68,7 +68,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -84,7 +84,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -100,7 +100,7 @@ func TestWhoami(t *testing.T) {
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info:
@ -209,12 +209,12 @@ func TestWhoami(t *testing.T) {
name: "different kubeconfig context, but same as current",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "kind-kind",
"--kubeconfig-context", "kind-context",
},
wantStdout: here.Doc(`
Current cluster info:
Name: kind-kind
Name: kind-cluster
URL: https://fake-server-url-value
Current user info: