Skip to content

Commit

Permalink
Add API to get the Kubeconfig for arbitrary UCP resource
Browse files Browse the repository at this point in the history
- Add API to get the kubeconfig for arbitrary UCP resource given the UCP context

Signed-off-by: Prem Kumar Kalle <[email protected]>
  • Loading branch information
prkalle committed Sep 28, 2023
1 parent 923c4c1 commit df102b0
Show file tree
Hide file tree
Showing 6 changed files with 644 additions and 0 deletions.
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

0 comments on commit df102b0

Please sign in to comment.