Nothing Special   »   [go: up one dir, main page]

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

make more intelligent kubeconfig merge #953

Merged
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
111 changes: 67 additions & 44 deletions pkg/cmd/experimental/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import (
"fmt"
"os"

"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
kubecmd "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/cmd/cli/cmd"
"github.com/openshift/origin/pkg/cmd/flagtypes"
"github.com/openshift/origin/pkg/cmd/util/tokencmd"
)

Expand All @@ -36,20 +39,33 @@ prompt for user input if not provided.
glog.Fatalf("%v\n", err)
}

usernameFlag := kubecmd.GetFlagString(cmd, "username")
passwordFlag := kubecmd.GetFlagString(cmd, "password")
username := ""

accessToken, err := tokencmd.RequestToken(clientCfg, os.Stdin, usernameFlag, passwordFlag)
if err != nil {
glog.Fatalf("%v\n", err)
}
// check to see if we're already signed in. If so, simply make sure that .kubeconfig has that information
if userFullName, err := whoami(clientCfg); err == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if the --username flag was provided we may want to make sure we are already signed in with the same username specified by the flag. That would allow users to simply switch between accounts by doing

openshift ex login -u stark
openshift ex login -u targaryen

It's already in my list btw the osc logout command that would also help in that flow.

if err := updateKubeconfigFile(userFullName, clientCfg.BearerToken, f.OpenShiftClientConfig); err != nil {
glog.Fatalf("%v\n", err)
}
username = userFullName

err = updateKubeconfigFile(usernameFlag, accessToken, clientCfg)
if err != nil {
glog.Fatalf("%v\n", err)
} else {
usernameFlag := kubecmd.GetFlagString(cmd, "username")
passwordFlag := kubecmd.GetFlagString(cmd, "password")

accessToken, err := tokencmd.RequestToken(clientCfg, os.Stdin, usernameFlag, passwordFlag)
if err != nil {
glog.Fatalf("%v\n", err)
}

err = updateKubeconfigFile(usernameFlag, accessToken, f.OpenShiftClientConfig)
if err != nil {
glog.Fatalf("%v\n", err)
}

username = usernameFlag
}

fmt.Printf("Auth token: %v\n", string(accessToken))
fmt.Printf("Logged into %v as %v\n", clientCfg.Host, username)
},
}

Expand All @@ -58,6 +74,20 @@ prompt for user input if not provided.
return cmds
}

func whoami(clientCfg *kclient.Config) (string, error) {
osClient, err := client.New(clientCfg)
if err != nil {
return "", err
}

me, err := osClient.Users().Get("~")
if err != nil {
return "", err
}

return me.FullName, nil
}

// Copy of kubectl/cmd/DefaultClientConfig, using NewNonInteractiveDeferredLoadingClientConfig
// TODO find and merge duplicates, this is also in other places
func defaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
Expand All @@ -74,70 +104,63 @@ func defaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
return clientConfig
}

func updateKubeconfigFile(username, token string, clientConfig *kclient.Config) error {
config, err := getConfigFromFile(".kubeconfig")
func updateKubeconfigFile(username, token string, clientCfg clientcmd.ClientConfig) error {
rawMergedConfig, err := clientCfg.RawConfig()
if err != nil {
return err
}
clientConfig, err := clientCfg.ClientConfig()
if err != nil {
return err
}
namespace, err := clientCfg.Namespace()
if err != nil {
return err
}

config := clientcmdapi.NewConfig()

credentialsName := username
if len(credentialsName) == 0 {
credentialsName = "osc-login"
}
credentialsName = getUniqueName(credentialsName, getAuthInfoNames(config))
credentials := clientcmdapi.NewAuthInfo()
credentials.Token = token
config.AuthInfos[credentialsName] = *credentials

clusterName := getUniqueName("osc-login-cluster", getClusterNames(config))
serverAddr := flagtypes.Addr{Value: clientConfig.Host}.Default()
clusterName := fmt.Sprintf("%v:%v", serverAddr.Host, serverAddr.Port)
cluster := clientcmdapi.NewCluster()
cluster.Server = clientConfig.Host
cluster.InsecureSkipTLSVerify = clientConfig.Insecure
cluster.CertificateAuthority = clientConfig.CAFile
config.Clusters[clusterName] = *cluster

contextName := getUniqueName(clusterName+"-"+credentialsName, getContextNames(config))
contextName := clusterName + "-" + credentialsName
context := clientcmdapi.NewContext()
context.Cluster = clusterName
context.AuthInfo = credentialsName
context.Namespace = namespace
config.Contexts[contextName] = *context

config.CurrentContext = contextName

err = clientcmd.WriteToFile(*config, ".kubeconfig")
configToModify, err := getConfigFromFile(".kubeconfig")
if err != nil {
return err
}

return nil

}

func getAuthInfoNames(config *clientcmdapi.Config) *util.StringSet {
ret := &util.StringSet{}
for key := range config.AuthInfos {
ret.Insert(key)
configToWrite, err := MergeConfig(rawMergedConfig, *configToModify, *config)
if err != nil {
return err
}

return ret
}

func getContextNames(config *clientcmdapi.Config) *util.StringSet {
ret := &util.StringSet{}
for key := range config.Contexts {
ret.Insert(key)
err = clientcmd.WriteToFile(*configToWrite, ".kubeconfig")
if err != nil {
return err
}

return ret
}

func getClusterNames(config *clientcmdapi.Config) *util.StringSet {
ret := &util.StringSet{}
for key := range config.Clusters {
ret.Insert(key)
}
return nil

return ret
}

func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
Expand Down
142 changes: 142 additions & 0 deletions pkg/cmd/experimental/login/smart_merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package login
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If moving upstream it would be under pkg/client/clientcmd, correct? Just make sure all important methods are public so we can make use of it.


import (
"fmt"
"reflect"

clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)

// MergeConfig takes a haystack to look for existing stanzas in (probably the merged config), a config object to modify (probably
// either the local or envvar config), and the new additions to merge in. It tries to find equivalents for the addition inside of the
// haystack and uses the mapping to avoid creating additional stanzas with duplicate information. It either locates or original
// stanzas or creates new ones for clusters and users. Then it uses the mapped names to build the correct contexts
func MergeConfig(haystack, toModify, addition clientcmdapi.Config) (*clientcmdapi.Config, error) {
ret := toModify

requestedClusterNamesToActualClusterNames := map[string]string{}
existingClusterNames, err := getMapKeys(reflect.ValueOf(haystack.Clusters))
if err != nil {
return nil, err
}
for requestedKey, needle := range addition.Clusters {
if existingName := FindExistingClusterName(haystack, needle); len(existingName) > 0 {
requestedClusterNamesToActualClusterNames[requestedKey] = existingName
continue
}

uniqueName := getUniqueName(requestedKey, existingClusterNames)
requestedClusterNamesToActualClusterNames[requestedKey] = uniqueName
ret.Clusters[uniqueName] = needle
}

requestedAuthInfoNamesToActualAuthInfoNames := map[string]string{}
existingAuthInfoNames, err := getMapKeys(reflect.ValueOf(haystack.AuthInfos))
if err != nil {
return nil, err
}
for requestedKey, needle := range addition.AuthInfos {
if existingName := FindExistingAuthInfoName(haystack, needle); len(existingName) > 0 {
requestedAuthInfoNamesToActualAuthInfoNames[requestedKey] = existingName
continue
}

uniqueName := getUniqueName(requestedKey, existingAuthInfoNames)
requestedAuthInfoNamesToActualAuthInfoNames[requestedKey] = uniqueName
ret.AuthInfos[uniqueName] = needle
}

requestedContextNamesToActualContextNames := map[string]string{}
existingContextNames, err := getMapKeys(reflect.ValueOf(haystack.Contexts))
if err != nil {
return nil, err
}
for requestedKey, needle := range addition.Contexts {
exists := false

actualContext := clientcmdapi.NewContext()
actualContext.AuthInfo, exists = requestedAuthInfoNamesToActualAuthInfoNames[needle.AuthInfo]
if !exists {
actualContext.AuthInfo = needle.AuthInfo
}
actualContext.Cluster, exists = requestedClusterNamesToActualClusterNames[needle.Cluster]
if !exists {
actualContext.Cluster = needle.Cluster
}
actualContext.Namespace = needle.Namespace
actualContext.Extensions = needle.Extensions

if existingName := FindExistingContextName(haystack, *actualContext); len(existingName) > 0 {
// if this already exists, just move to the next, our job is done
requestedContextNamesToActualContextNames[requestedKey] = existingName
continue
}

uniqueName := getUniqueName(actualContext.Cluster+"-"+actualContext.AuthInfo, existingContextNames)
requestedContextNamesToActualContextNames[requestedKey] = uniqueName
ret.Contexts[uniqueName] = *actualContext
}

if len(addition.CurrentContext) > 0 {
if newCurrentContext, exists := requestedContextNamesToActualContextNames[addition.CurrentContext]; exists {
ret.CurrentContext = newCurrentContext
} else {
ret.CurrentContext = addition.CurrentContext
}
}

return &ret, nil
}

// FindExistingClusterName finds the nickname for the passed cluster config
func FindExistingClusterName(haystack clientcmdapi.Config, needle clientcmdapi.Cluster) string {
for key, cluster := range haystack.Clusters {
if reflect.DeepEqual(cluster, needle) {
return key
}
}

return ""
}

// FindExistingAuthInfoName finds the nickname for the passed auth info
func FindExistingAuthInfoName(haystack clientcmdapi.Config, needle clientcmdapi.AuthInfo) string {
for key, authInfo := range haystack.AuthInfos {
if reflect.DeepEqual(authInfo, needle) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to make sure we are using the same equality check rules used in other parts, i.e. when adding users (e.g. nickname forces/ignores letter case, etc). I didn't check.

return key
}
}

return ""
}

// FindExistingContextName finds the nickname for the passed context
func FindExistingContextName(haystack clientcmdapi.Config, needle clientcmdapi.Context) string {
for key, context := range haystack.Contexts {
if reflect.DeepEqual(context, needle) {
return key
}
}

return ""
}

func getMapKeys(theMap reflect.Value) (*util.StringSet, error) {
if theMap.Kind() != reflect.Map {
return nil, fmt.Errorf("theMap must be of type %v, not %v", reflect.Map, theMap.Kind())
}

ret := &util.StringSet{}

switch theMap.Kind() {
case reflect.Map:
for _, keyValue := range theMap.MapKeys() {
ret.Insert(keyValue.String())
}

}

return ret, nil

}