Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API to get the Kubeconfig for an arbitrary UCP resource #100

Merged
merged 1 commit into from
Sep 28, 2023
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
51 changes: 51 additions & 0 deletions fakes/config/kubeconfig-1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: v1
kind: Config
current-context: foo-context
preferences:
colors: true
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://bar.org:4443
name: bar-cluster
- cluster:
insecure-skip-tls-verify: true
server: https://foo.org:443
name: foo-cluster
- cluster:
insecure-skip-tls-verify: true
server: https://api.tanzu.cloud.vmware.com:443/org/fake-org-id
name: tanzu-cli-myucp/current
contexts:
- context:
cluster: bar-cluster
namespace: chisel-ns
user: green-user
name: bar-context
- context:
cluster: foo-cluster
namespace: saw-ns
user: blue-user
name: foo-context
- context:
cluster: tanzu-cli-myucp/current
user: tanzu-cli-myucp-user
name: tanzu-cli-myucp
users:
- name: blue-user
user:
token: blue-token
- name: green-user
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQ24vUDZ1S1pWWTR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TURBeE1qVXdPRFV4TVRCYUZ3MHlNVEF4TWpRd09EVXhNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJuUmc4dklXQlBWQUJWTFUKNGNFckd4bE5XZVJHR2x0OW0wVElsUStNUytSZU9GNUhROW5pQlRSQWMwVWpsUk1EdzR3aEREVXV3Y2NsazFCNApoa3l2YUx5elZyeW9jOWV5NHU0SVZ0MFR4ZU85VUI5bXZHMkF6TlVnY0FkRkp4cWdKbSt2OXo0RzMyU0EwUEc1CjhxZWtBRzVLYU1RQlJEVm5pNXZBajJ5c0hpdHhkbGdJdEZkc1dzNUgwM3FIUjVNQU5jT01nYlZLSnRtbE9MUngKYWFmRHhxSWRaS0pERENxeDFoaWFlMk5WdWp1TzI3OE9DV3NaN3FUWVR4UzNpVVJBUXNpYWk5Y2Q5T2FITnJSTAozWWtUdTl0b1c5RTNFdVB0b0h3S2N6RlZsNk01dWtaWW5wZDEwL213Y2wzUzA1ZFd2SExhNGVMakZ3Z2RQc05XCkZ4NThTd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNaThWVXZ0RWo0d3g3Z1hZSXhxZU95L1UyY0psQVp3S0Zmdwp3eEJYQjg3V1JYblhZaEt1aWhPeUpCSnM2RVpSOUZWcWUwNm1yZFNOMVRPQW5JdmovZVlEejB5SlZFQlVNMHJ1CmlXWkN2djRxZXBRYUpWSzZBRDBjcjBtZEIvbmFrd3lyS203eTRXdjRSMkhVcWxZdTZXcVRsdTY3V3Ezb1g5VFIKSUFZRUozWXJmSy9QUXV3WHZyUlNDWGg2NVpHT21aL3M0di9vbWlvajdocHp4Zm9OOUkxQ0hoYUV2clFGcW9UcwpiSWJWMDFDa3Nzak9DdUhrQlIzRGxLcENyQ29iMEVYd2E0c2pkNjlVWnJzRElCeUtmZmxqV0pscllYdWdOQ0pPCi9OeUhVS2dMTDk3c0lOR043NElJRmJhNXV2cWRkQm9jTUxGMkFrRHV6UndweC9mVWNrUT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBcm5SZzh2SVdCUFZBQlZMVTRjRXJHeGxOV2VSR0dsdDltMFRJbFErTVMrUmVPRjVIClE5bmlCVFJBYzBVamxSTUR3NHdoRERVdXdjY2xrMUI0aGt5dmFMeXpWcnlvYzlleTR1NElWdDBUeGVPOVVCOW0KdkcyQXpOVWdjQWRGSnhxZ0ptK3Y5ejRHMzJTQTBQRzU4cWVrQUc1S2FNUUJSRFZuaTV2QWoyeXNIaXR4ZGxnSQp0RmRzV3M1SDAzcUhSNU1BTmNPTWdiVktKdG1sT0xSeGFhZkR4cUlkWktKRERDcXgxaGlhZTJOVnVqdU8yNzhPCkNXc1o3cVRZVHhTM2lVUkFRc2lhaTljZDlPYUhOclJMM1lrVHU5dG9XOUUzRXVQdG9Id0tjekZWbDZNNXVrWlkKbnBkMTAvbXdjbDNTMDVkV3ZITGE0ZUxqRndnZFBzTldGeDU4U3dJREFRQUJBb0lCQUNMUloyZ1ZtUDkwVTBxOAp0WEE1TlhrN0c0ME5XbEI0WWlGVElSVmUvUWxJa3VWOUs0d0hPRzBCZUx1STJRa3Z1bGlVNXlPZ21heGpLc1MwCkV0bjdCQ2RMUWgvVmwybEhhNVNQSFdyNHhMR0NPbzU3TmUzMWpQZFVzaHlwMXN0dkxQZCs0d2ZkZ1ZHa3BYRU8KVGFaNGZ1cjNHRExBcStBSktKbGNoSm9iZmw2bXlLc2tKV2NrUmNYY3FWOW5adFRtbGh5aFljZ2tuYkg0dFJmOAp0YzNsR1B5cHF0SmhiWmEyaUMxUGkxMlltYjBwNThFWVVFdVRXdWVjaHBXNHY0cU02V2ZYMXJSeGRSU3RwQkF5Cm50WUF5b3pYYUQ5cDlYbnI5OFNOekFMMW83azF0V29FanRBSXhNVTNlQ1VFTWx5Q01VVVFrUjNGN0JOUk9GWS8KcnNGVVlVRUNnWUVBMHorZ0QwNithSlpsSWkyeUtOYnJNMm1DTjFjQ2hkdVFCSmdvc0VMTS9IN0p1cnY4SFJsQQpvYkxwekJSWXQ3aW82WHBaQndsRnIrM0dabWdTWkkwY3U0L0tIMXRVWU1DRS9pVnVYa0tXVnYva0laUnd0ckpkCmNEU0htNkVxbnRwY2h3aTBQbFltNmx3Vm5lbEpvN282T2dycHFvVXA0cjNvRGNHUzREdU1vTThDZ1lFQTAybGEKelMycTdMVjlEQXZhdVFOQ2wvZ2JLTk9FZG9FR0tYWEVBaFJTYkc3SHpHeVkveDJVSUFocCtuUytFS21wdVF6WQp4UUdqOU5QSlY3WGJqQkg4MmtDcmhtRUx0bG9QOGdQVlhxUGhXTHFzemNUOE1xUFRCYUcxS21IaTRrb3FZb2hhCldJS0pnTFErOEFCTjBOUEJYUTl4NVJVSHVPejhJaUcveVBxcXM4VUNnWUE3WFVqc3BGTkw3dCt5MENhZDVXK3AKUGdBeTd3ZXRlRHNybjFybjFZM25jdlhidlJJblZ6NWJjbnpUTmZDTWlKOG5KWk81TDNqZTdMSHhlMU9YNERQdgozWU9PZGtycStZOG1JSHk0am52VExRditCOG40L1h6V21GeDNkcjRVY0FiS2g3Nm5PZXlydFg3NXBtSmtXV1FkCmhZMk90dWREYWR5NWFPbU9qQTJEN3dLQmdGQ1UwN3RwTU1GUTIvei9kN3NWZHdpZDFSeFdveUxZUXhVQ2dsZVkKajJJdFI1S3Z3aEZib040azF1QlVKeTRLdlZwL2Z4QjRjNW9hTDZCeS9PQUM2ekgxZkd1WUNmTFRtVWhTRmI0aApFUC9WQjVEWENKbjB2N1poMEwvNjE1UVJXTjU5d3BJQ0Q0OHpKTm91QTNzWmU1YVJFSVNVNDRDbE0rVituNjluCmZERlJBb0dBUDZ1NGxrSjg0amRjTVROVTdYeW16ZkMwSkd5aFRNcWQ2YnV4NG9FQlViSFhoOW1DaVRMSmFaV1YKN0F5NkxTV21lRGIrWEFrS0tQL2UwQVMrWElKV2p6MDI5SFdDaXM0T3NZV3VqeksrVjVaVUVkQlJNUEN0QUo4ZApCaDBvRTliSUdIOVB0RTFIdTh6NGpGaG5WUmVqdmdhMzRQdG9QV3JNL01sZnQzLy9Lazg9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
- name: tanzu-cli-myucp-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- context
- get-token
- myucp
command: tanzu
env: []
86 changes: 86 additions & 0 deletions ucp/internal/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2023 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Package kubeconfig provides the kubeconfig file access functionality
package kubeconfig

import (
"os"

"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)

// ReadKubeConfig reads the kubeconfig file and returns the Config
func ReadKubeConfig(path string) (*Config, error) {
kubeconfig, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config Config
err = yaml.Unmarshal(kubeconfig, &config)
if err != nil {
return nil, err
}

return &config, nil
}

// MinifyKubeConfig removes all information not used by the provided kubecontext name
func MinifyKubeConfig(kubeconfig *Config, kubeContextName string) (*Config, error) {
context := GetContext(kubeconfig, kubeContextName)
if context == nil {
return nil, errors.Errorf("context %q missing in the kubeconfig", kubeContextName)
}

clusterName := context.Context.Cluster
userName := context.Context.AuthInfo

cluster := GetCluster(kubeconfig, clusterName)
if cluster == nil {
return nil, errors.Errorf("cluster %q missing in the kubeconfig", clusterName)
}
user := GetAuthInfo(kubeconfig, userName)
if user == nil {
return nil, errors.Errorf("user %q missing in the kubeconfig", userName)
}

return &Config{
Kind: "Config",
APIVersion: "v1",
Clusters: []*Cluster{{Name: clusterName, Cluster: cluster.Cluster}},
AuthInfos: []*AuthInfo{{Name: userName, AuthInfo: user.AuthInfo}},
Contexts: []*Context{{Name: kubeContextName, Context: context.Context}},
CurrentContext: kubeContextName,
}, nil
}

// GetContext returns the Context information from the provided config
func GetContext(kubeconfig *Config, kubeContextName string) *Context {
for idx := range kubeconfig.Contexts {
if kubeconfig.Contexts[idx].Name == kubeContextName {
return kubeconfig.Contexts[idx]
}
}
return nil
}

// GetCluster returns the Cluster information from the provided config
func GetCluster(kubeconfig *Config, clusterName string) *Cluster {
for idx := range kubeconfig.Clusters {
if kubeconfig.Clusters[idx].Name == clusterName {
return kubeconfig.Clusters[idx]
}
}
return nil
}

// GetAuthInfo returns the AuthInfo information from the provided config
func GetAuthInfo(kubeconfig *Config, userName string) *AuthInfo {
for idx := range kubeconfig.AuthInfos {
if kubeconfig.AuthInfos[idx].Name == userName {
return kubeconfig.AuthInfos[idx]
}
}
return nil
}
106 changes: 106 additions & 0 deletions ucp/internal/kubeconfig/kubeconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2023 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package kubeconfig

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)

const ConfigFilePermissions = 0o600

func copyFile(t *testing.T, sourceFile, destFile string) {
input, err := os.ReadFile(sourceFile)
assert.NoError(t, err)
err = os.WriteFile(destFile, input, ConfigFilePermissions)
assert.NoError(t, err)
}

func TestReadAndMinifyKubeConfig(t *testing.T) {
expectedMyucpKubeconfig := `
kind: Config
apiVersion: v1
preferences: {}
clusters:
- name: tanzu-cli-myucp/current
cluster:
server: https://api.tanzu.cloud.vmware.com:443/org/fake-org-id
insecure-skip-tls-verify: true
users:
- name: tanzu-cli-myucp-user
user:
exec:
command: tanzu
args:
- context
- get-token
- myucp
env: []
apiVersion: client.authentication.k8s.io/v1beta1
provideClusterInfo: false
contexts:
- name: tanzu-cli-myucp
context:
cluster: tanzu-cli-myucp/current
user: tanzu-cli-myucp-user
current-context: tanzu-cli-myucp
`

expectedFooContextKubeconfig := `
kind: Config
apiVersion: v1
preferences: {}
clusters:
- name: foo-cluster
cluster:
server: https://foo.org:443
insecure-skip-tls-verify: true
users:
- name: blue-user
user:
token: blue-token
contexts:
- name: foo-context
context:
cluster: foo-cluster
user: blue-user
namespace: saw-ns
current-context: foo-context
`

testKubeconfiFilePath := "../../../fakes/config/kubeconfig-1.yaml"
kubeconfigFilePath, err := os.CreateTemp("", "config")
assert.NoError(t, err)
copyFile(t, testKubeconfiFilePath, kubeconfigFilePath.Name())

defer func() {
_ = os.RemoveAll(kubeconfigFilePath.Name())
}()

config, err := ReadKubeConfig(kubeconfigFilePath.Name())
assert.NoError(t, err)

// Test with non-existing context
_, err = MinifyKubeConfig(config, "non-existing-context")
assert.Equal(t, err.Error(), `context "non-existing-context" missing in the kubeconfig`)

// Test reading and minifying the kubeconfig using Token as user(AuthInfo)
minifiedKubeconfig, err := MinifyKubeConfig(config, "foo-context")
assert.NoError(t, err)
wantKubeConfig := &Config{}
err = yaml.Unmarshal([]byte(expectedFooContextKubeconfig), wantKubeConfig)
assert.NoError(t, err)
assert.Equal(t, minifiedKubeconfig, wantKubeConfig)

// Test reading and minifying the kubeconfig having ExecConfig as user(AuthInfo)
minifiedKubeconfig, err = MinifyKubeConfig(config, "tanzu-cli-myucp")
assert.NoError(t, err)
wantKubeConfig = &Config{}
err = yaml.Unmarshal([]byte(expectedMyucpKubeconfig), wantKubeConfig)
assert.NoError(t, err)
assert.Equal(t, minifiedKubeconfig, wantKubeConfig)
}
Loading