Skip to content
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
199 changes: 152 additions & 47 deletions pkg/cmd/cli/cmd/project.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package cmd

import (
"bytes"
"fmt"
"io"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
kclientcmd "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
"github.com/openshift/origin/pkg/cmd/cli/config"

"github.com/openshift/origin/pkg/client"
cliconfig "github.com/openshift/origin/pkg/cmd/cli/config"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
"github.com/openshift/origin/pkg/project/api"

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

Expand All @@ -36,6 +44,7 @@ func NewCmdProject(f *clientcmd.Factory, out io.Writer) *cobra.Command {
oClient, _, err := f.Clients()
checkErr(err)

// No argument provided, we will just print info
if argsLength == 0 {
currentContext := rawCfg.Contexts[rawCfg.CurrentContext]
currentProject := currentContext.Namespace
Expand All @@ -44,79 +53,175 @@ func NewCmdProject(f *clientcmd.Factory, out io.Writer) *cobra.Command {
_, err := oClient.Projects().Get(currentProject)
if err != nil {
if errors.IsNotFound(err) {
glog.Fatalf("The project '%v' specified in your config does not exist or you do not have rights to view it.", currentProject)
glog.Fatalf("The project %q specified in your config does not exist.", currentProject)
}
if clientcmd.IsForbidden(err) {
glog.Fatalf("You do not have rights to view project %q.", currentProject)
}
checkErr(err)
}

fmt.Printf("Using project '%v'.\n", currentProject)
if rawCfg.CurrentContext != currentProject {
fmt.Printf("Using project %q from context named %q on server %q.\n", currentProject, rawCfg.CurrentContext, clientCfg.Host)
} else {
fmt.Printf("Using project %q on server %q.\n", currentProject, clientCfg.Host)
}

} else {
fmt.Printf("No specific project in use.\n")
fmt.Printf("No project has been set. Pass a project name to make that the default.\n")
}
return

}

projectName := args[0]
// We have an argument that can be either a context or project
argument := args[0]

configStore, err := loadConfigStore(cmd)
checkErr(err)
config := configStore.Config

contextInUse := ""
namespaceInUse := ""

// Check if argument is an existing context, if so just set it as the context in use.
// If not a context then we will try to handle it as a project.
if context, ok := config.Contexts[argument]; ok {
contextInUse = argument
namespaceInUse = context.Namespace

config.CurrentContext = argument

project, err := oClient.Projects().Get(projectName)
if err != nil {
if errors.IsNotFound(err) {
glog.Fatalf("Unable to find a project with name '%v'.", projectName)
} else {
project, err := oClient.Projects().Get(argument)
if err != nil {
if isNotFound, isForbidden := errors.IsNotFound(err), clientcmd.IsForbidden(err); isNotFound || isForbidden {
msg := ""

if isNotFound {
msg = fmt.Sprintf("A project named %q does not exist on server %q.", argument, clientCfg.Host)
} else {
msg = fmt.Sprintf("You do not have rights to view project %q on server %q.", argument, clientCfg.Host)
}

projects, err := getProjects(oClient)
if err == nil {
msg += "\nYour projects are:"
for _, project := range projects {
msg += "\n" + project.Name
}
}

if hasMultipleServers(config) {
msg += "\nTo see projects on another server, pass '--server=<server>'."
}

glog.Fatal(msg)
}

checkErr(err)
}
checkErr(err)
}

pathFromFlag := cmdutil.GetFlagString(cmd, config.OpenShiftConfigFlagName)
// If a context exists, just set it as the current one.
exists := false
for k, ctx := range config.Contexts {
namespace := ctx.Namespace
cluster := config.Clusters[ctx.Cluster]
authInfo := config.AuthInfos[ctx.AuthInfo]

configStore, err := config.LoadFrom(pathFromFlag)
if err != nil {
configStore, err = config.LoadWithLoadingRules()
checkErr(err)
}
checkErr(err)
if namespace == project.Name && clusterAndAuthEquality(clientCfg, cluster, authInfo) {
exists = true
config.CurrentContext = k

config := configStore.Config
contextInUse = k
namespaceInUse = namespace

// check if context exists in the file I'm going to save
// if so just set it as the current one
exists := false
for k, ctx := range config.Contexts {
namespace := ctx.Namespace
cluster := config.Clusters[ctx.Cluster]
authInfo := config.AuthInfos[ctx.AuthInfo]

if namespace == project.Name && cluster.Server == clientCfg.Host && authInfo.Token == clientCfg.BearerToken {
exists = true
config.CurrentContext = k
break
break
}
}
}

// otherwise use the current context if it's in the file I'm going to save,
// or create a new one if it's not
if !exists {
currentCtx := rawCfg.CurrentContext
if ctx, ok := config.Contexts[currentCtx]; ok {
ctx.Namespace = project.Name
config.Contexts[currentCtx] = ctx
} else {
ctx = rawCfg.Contexts[currentCtx]
// Otherwise create a new context, reusing the cluster and auth info
if !exists {
currentCtx := rawCfg.CurrentContext

newCtx := clientcmdapi.NewContext()
newCtx.Namespace = project.Name
newCtx.AuthInfo = ctx.AuthInfo
newCtx.Cluster = ctx.Cluster
config.Contexts[fmt.Sprint(util.NewUUID())] = *newCtx

newCtx.AuthInfo = rawCfg.Contexts[currentCtx].AuthInfo
newCtx.Cluster = rawCfg.Contexts[currentCtx].Cluster

existingContexIdentifiers := &util.StringSet{}
for key := range rawCfg.Contexts {
existingContexIdentifiers.Insert(key)
}

newCtxName := cliconfig.GenerateContextIdentifier(newCtx.Namespace, newCtx.Cluster, "", existingContexIdentifiers)

config.Contexts[newCtxName] = *newCtx
config.CurrentContext = newCtxName

contextInUse = newCtxName
namespaceInUse = project.Name
}
}

if err = kclientcmd.WriteToFile(*config, configStore.Path); err != nil {
glog.Fatalf("Error saving project information in the config: %v.", err)
}

fmt.Printf("Now using project '%v'.\n", project.Name)
if contextInUse != namespaceInUse {
fmt.Printf("Now using project %q from context named %q on server %q.\n", namespaceInUse, contextInUse, clientCfg.Host)
} else {
fmt.Printf("Now using project %q on server %q.\n", namespaceInUse, clientCfg.Host)
}
},
}
return cmd
}

func getProjects(oClient *client.Client) ([]api.Project, error) {
projects, err := oClient.Projects().List(labels.Everything(), fields.Everything())
if err != nil {
return nil, err
}
return projects.Items, nil
}

func loadConfigStore(cmd *cobra.Command) (*cliconfig.ConfigStore, error) {
pathFromFlag := cmdutil.GetFlagString(cmd, cliconfig.OpenShiftConfigFlagName)

configStore, err := cliconfig.LoadFrom(pathFromFlag)
if err != nil {
configStore, err = cliconfig.LoadWithLoadingRules()
if err != nil {
return nil, err
}
}

return configStore, err
}

func clusterAndAuthEquality(clientCfg *kclient.Config, cluster clientcmdapi.Cluster, authInfo clientcmdapi.AuthInfo) bool {
return cluster.Server == clientCfg.Host &&
Copy link
Contributor

Choose a reason for hiding this comment

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

This could have changed because the user specified it on the CLI?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

cluster.InsecureSkipTLSVerify == clientCfg.Insecure &&
cluster.CertificateAuthority == clientCfg.CAFile &&
bytes.Equal(cluster.CertificateAuthorityData, clientCfg.CAData) &&
authInfo.Token == clientCfg.BearerToken &&
authInfo.ClientCertificate == clientCfg.TLSClientConfig.CertFile &&
bytes.Equal(authInfo.ClientCertificateData, clientCfg.TLSClientConfig.CertData) &&
authInfo.ClientKey == clientCfg.TLSClientConfig.KeyFile &&
bytes.Equal(authInfo.ClientKeyData, clientCfg.TLSClientConfig.KeyData)
}

// TODO these kind of funcs could be moved to some kind of clientcmd util
func hasMultipleServers(config *clientcmdapi.Config) bool {
server := ""
for _, cluster := range config.Clusters {
if len(server) == 0 {
server = cluster.Server
}
if server != cluster.Server {
return true
}
}
return false
}
86 changes: 85 additions & 1 deletion pkg/cmd/cli/config/smart_merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package config

import (
"fmt"
"net"
"net/url"
"reflect"
"regexp"

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

var invalidSafeStringSep = regexp.MustCompile(`[.:/]`)

// 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
Expand Down Expand Up @@ -73,7 +79,7 @@ func MergeConfig(haystack, toModify, addition clientcmdapi.Config) (*clientcmdap
continue
}

uniqueName := getUniqueName(actualContext.Cluster+"-"+actualContext.AuthInfo, existingContextNames)
uniqueName := GenerateContextIdentifier(actualContext.Namespace, actualContext.Cluster, actualContext.AuthInfo, existingContextNames)
requestedContextNamesToActualContextNames[requestedKey] = uniqueName
ret.Contexts[uniqueName] = *actualContext
}
Expand Down Expand Up @@ -142,6 +148,19 @@ func getMapKeys(theMap reflect.Value) (*util.StringSet, error) {
}

func getUniqueName(basename string, existingNames *util.StringSet) string {
if parsedUrl, err := url.Parse(basename); err != nil {
if host, port, err := net.SplitHostPort(parsedUrl.Host); err != nil {
if !existingNames.Has(host) {
return host
}
if id := host + "-" + port; !existingNames.Has(id) {
return id
}
}
}

basename = invalidSafeStringSep.ReplaceAllString(basename, "-")

if !existingNames.Has(basename) {
return basename
}
Expand All @@ -155,3 +174,68 @@ func getUniqueName(basename string, existingNames *util.StringSet) string {

return string(util.NewUUID())
}

// Generates the best context identifier possible based on the information it gets.
func GenerateContextIdentifier(namespace string, cluster string, authInfo string, existingContextIdentifiers *util.StringSet) string {
ctx := ""

// try to use plain namespace
if len(namespace) > 0 {
ctx += namespace

if !existingContextIdentifiers.Has(ctx) {
return ctx
}
}

// tries appending "-host" or "-host-port"
if len(cluster) > 0 {
if parsedUrl, err := url.Parse(cluster); err != nil {
if host, port, err := net.SplitHostPort(parsedUrl.Host); err != nil {
if len(ctx) > 0 {
ctx += "-"
}
ctx += host
if !existingContextIdentifiers.Has(ctx) {
return ctx
}

ctx += "-" + port
if !existingContextIdentifiers.Has(ctx) {
return ctx
}

} else {
if len(ctx) > 0 {
ctx += "-"
}
ctx += "-" + parsedUrl.Host
if !existingContextIdentifiers.Has(ctx) {
return ctx
}
}
}
}

// tries appending "-username"
if len(authInfo) > 0 {
if len(ctx) > 0 {
ctx += "-"
}
ctx += authInfo

if !existingContextIdentifiers.Has(ctx) {
return ctx
}
}

// append an integer
for i := 0; i < 100; i++ {
if trialName := fmt.Sprintf("%v-%d", ctx, i); !existingContextIdentifiers.Has(trialName) {
return trialName
}
}

glog.Fatalf("Unable to generate a context identifier. Please provide a context using the '--context=<context>' flag.")
return ""
}