mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
add support for --cluster --context --user flags
This commit is contained in:
parent
985406c969
commit
a510285d63
@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@ -13,21 +29,33 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
namespace_example = `
|
||||
namespaceExample = `
|
||||
# view the current namespace in your KUBECONFIG
|
||||
%[1]s ns
|
||||
|
||||
# view all of the namespaces in use by contexts in your KUBECONFIG
|
||||
%[1] ns --list
|
||||
%[1]s ns --list
|
||||
|
||||
# switch your current-context to one that contains the desired namespace
|
||||
%[1]s ns foo
|
||||
`
|
||||
|
||||
errNoContext = fmt.Errorf("no context is currently set, use %q to select a new one", "kubectl config use-context <context>")
|
||||
)
|
||||
|
||||
// NamespaceOptions provides information required to update
|
||||
// the current context on a user's KUBECONFIG
|
||||
type NamespaceOptions struct {
|
||||
configFlags *genericclioptions.ConfigFlags
|
||||
|
||||
resultingContext *api.Context
|
||||
resultingContextName string
|
||||
|
||||
userSpecifiedCluster string
|
||||
userSpecifiedContext string
|
||||
userSpecifiedAuthInfo string
|
||||
userSpecifiedNamespace string
|
||||
|
||||
rawConfig api.Config
|
||||
listNamespaces bool
|
||||
args []string
|
||||
@ -35,6 +63,7 @@ type NamespaceOptions struct {
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
// NewNamespaceOptions provides an instance of NamespaceOptions with default values
|
||||
func NewNamespaceOptions(streams genericclioptions.IOStreams) *NamespaceOptions {
|
||||
return &NamespaceOptions{
|
||||
configFlags: genericclioptions.NewConfigFlags(),
|
||||
@ -43,16 +72,17 @@ func NewNamespaceOptions(streams genericclioptions.IOStreams) *NamespaceOptions
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdNamespace provides a cobra command wrapping NamespaceOptions
|
||||
func NewCmdNamespace(streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewNamespaceOptions(streams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ns [new-namespace] [flags]",
|
||||
Short: "View or set the current namespace",
|
||||
Example: namespace_example,
|
||||
Example: fmt.Sprintf(namespaceExample, "kubectl"),
|
||||
SilenceUsage: true,
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
if err := o.Complete(args); err != nil {
|
||||
if err := o.Complete(c, args); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.Validate(); err != nil {
|
||||
@ -72,20 +102,103 @@ func NewCmdNamespace(streams genericclioptions.IOStreams) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *NamespaceOptions) Complete(args []string) error {
|
||||
// Complete sets all information required for updating the current context
|
||||
func (o *NamespaceOptions) Complete(cmd *cobra.Command, args []string) error {
|
||||
o.args = args
|
||||
|
||||
var err error
|
||||
o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.args = args
|
||||
o.userSpecifiedNamespace, err = cmd.Flags().GetString("namespace")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if len(o.userSpecifiedNamespace) > 0 {
|
||||
return fmt.Errorf("cannot specify both a --namespace value and a new namespace argument")
|
||||
}
|
||||
|
||||
o.userSpecifiedNamespace = args[0]
|
||||
}
|
||||
|
||||
// if no namespace argument or flag value was specified, then there
|
||||
// is no need to generate a resulting context
|
||||
if len(o.userSpecifiedNamespace) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
o.userSpecifiedContext, err = cmd.Flags().GetString("context")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.userSpecifiedCluster, err = cmd.Flags().GetString("cluster")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.userSpecifiedAuthInfo, err = cmd.Flags().GetString("user")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentContext, exists := o.rawConfig.Contexts[o.rawConfig.CurrentContext]
|
||||
if !exists {
|
||||
return errNoContext
|
||||
}
|
||||
|
||||
o.resultingContext = api.NewContext()
|
||||
o.resultingContext.Cluster = currentContext.Cluster
|
||||
o.resultingContext.AuthInfo = currentContext.AuthInfo
|
||||
|
||||
// if a target context is explicitly provided by the user,
|
||||
// use that as our reference for the final, resulting context
|
||||
if len(o.userSpecifiedContext) > 0 {
|
||||
o.resultingContextName = o.userSpecifiedContext
|
||||
if userCtx, exists := o.rawConfig.Contexts[o.userSpecifiedContext]; exists {
|
||||
o.resultingContext = userCtx.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
// override context info with user provided values
|
||||
o.resultingContext.Namespace = o.userSpecifiedNamespace
|
||||
|
||||
if len(o.userSpecifiedCluster) > 0 {
|
||||
o.resultingContext.Cluster = o.userSpecifiedCluster
|
||||
}
|
||||
if len(o.userSpecifiedAuthInfo) > 0 {
|
||||
o.resultingContext.AuthInfo = o.userSpecifiedAuthInfo
|
||||
}
|
||||
|
||||
// generate a unique context name based on its new values if
|
||||
// user did not explicitly request a context by name
|
||||
if len(o.userSpecifiedContext) == 0 {
|
||||
o.resultingContextName = generateContextName(o.resultingContext)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateContextName(fromContext *api.Context) string {
|
||||
name := fromContext.Namespace
|
||||
if len(fromContext.Cluster) > 0 {
|
||||
name = fmt.Sprintf("%s/%s", name, fromContext.Cluster)
|
||||
}
|
||||
if len(fromContext.AuthInfo) > 0 {
|
||||
cleanAuthInfo := strings.Split(fromContext.AuthInfo, "/")[0]
|
||||
name = fmt.Sprintf("%s/%s", name, cleanAuthInfo)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// Validate ensures that all required arguments and flag values are provided
|
||||
func (o *NamespaceOptions) Validate() error {
|
||||
if len(o.rawConfig.CurrentContext) == 0 {
|
||||
return fmt.Errorf("no context is currently set in your configuration")
|
||||
return errNoContext
|
||||
}
|
||||
if len(o.args) > 1 {
|
||||
return fmt.Errorf("either one or no arguments are allowed")
|
||||
@ -94,9 +207,11 @@ func (o *NamespaceOptions) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run lists all available namespaces on a user's KUBECONFIG or updates the
|
||||
// current context based on a provided namespace.
|
||||
func (o *NamespaceOptions) Run() error {
|
||||
if len(o.args) > 0 && len(o.args[0]) > 0 {
|
||||
return o.setNamespace(o.args[0])
|
||||
if len(o.userSpecifiedNamespace) > 0 && o.resultingContext != nil {
|
||||
return o.setNamespace(o.resultingContext, o.resultingContextName)
|
||||
}
|
||||
|
||||
namespaces := map[string]bool{}
|
||||
@ -134,58 +249,41 @@ func (o *NamespaceOptions) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *NamespaceOptions) setNamespace(newNamespace string) error {
|
||||
if len(newNamespace) == 0 {
|
||||
func isContextEqual(ctxA, ctxB *api.Context) bool {
|
||||
if ctxA == nil || ctxB == nil {
|
||||
return false
|
||||
}
|
||||
if ctxA.Cluster != ctxB.Cluster {
|
||||
return false
|
||||
}
|
||||
if ctxA.Namespace != ctxB.Namespace {
|
||||
return false
|
||||
}
|
||||
if ctxA.AuthInfo != ctxB.AuthInfo {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// setNamespace receives a "desired" context state and determines if a similar context
|
||||
// is already present in a user's KUBECONFIG. If one is not, then a new context is added
|
||||
// to the user's config under the provided destination name.
|
||||
// The current context field is updated to point to the new context.
|
||||
func (o *NamespaceOptions) setNamespace(fromContext *api.Context, withContextName string) error {
|
||||
if len(fromContext.Namespace) == 0 {
|
||||
return fmt.Errorf("a non-empty namespace must be provided")
|
||||
}
|
||||
|
||||
existingCtx, ok := o.rawConfig.Contexts[o.rawConfig.CurrentContext]
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to gather information about the current context")
|
||||
}
|
||||
|
||||
if existingCtx.Namespace == newNamespace {
|
||||
fmt.Fprintf(o.Out, "already using namespace %q\n", newNamespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
// determine if a context exists for the new namespace
|
||||
existingCtxName := ""
|
||||
for name, c := range o.rawConfig.Contexts {
|
||||
if c.Namespace != newNamespace || c.Cluster != existingCtx.Cluster || c.AuthInfo != existingCtx.AuthInfo {
|
||||
continue
|
||||
}
|
||||
|
||||
existingCtxName = name
|
||||
break
|
||||
}
|
||||
|
||||
if len(existingCtxName) == 0 {
|
||||
newCtx := api.NewContext()
|
||||
newCtx.AuthInfo = existingCtx.AuthInfo
|
||||
newCtx.Cluster = existingCtx.Cluster
|
||||
newCtx.Namespace = newNamespace
|
||||
|
||||
newCtxName := newNamespace
|
||||
if len(existingCtx.Cluster) > 0 {
|
||||
newCtxName = fmt.Sprintf("%s/%s", newCtxName, existingCtx.Cluster)
|
||||
}
|
||||
if len(existingCtx.AuthInfo) > 0 {
|
||||
cleanAuthInfo := strings.Split(existingCtx.AuthInfo, "/")[0]
|
||||
newCtxName = fmt.Sprintf("%s/%s", newCtxName, cleanAuthInfo)
|
||||
}
|
||||
|
||||
o.rawConfig.Contexts[newCtxName] = newCtx
|
||||
existingCtxName = newCtxName
|
||||
}
|
||||
|
||||
configAccess := clientcmd.NewDefaultPathOptions()
|
||||
o.rawConfig.CurrentContext = existingCtxName
|
||||
|
||||
if err := clientcmd.ModifyConfig(configAccess, o.rawConfig, true); err != nil {
|
||||
return err
|
||||
// determine if we have already saved this context to the user's KUBECONFIG before
|
||||
// if so, simply switch the current context to the existing one.
|
||||
if existingResultingCtx, exists := o.rawConfig.Contexts[withContextName]; !exists || !isContextEqual(fromContext, existingResultingCtx) {
|
||||
o.rawConfig.Contexts[withContextName] = fromContext
|
||||
}
|
||||
o.rawConfig.CurrentContext = withContextName
|
||||
|
||||
fmt.Fprintf(o.Out, "namespace changed to %q\n", newNamespace)
|
||||
return nil
|
||||
fmt.Fprintf(o.Out, "namespace changed to %q\n", fromContext.Namespace)
|
||||
return clientcmd.ModifyConfig(configAccess, o.rawConfig, true)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user