mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +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
|
package main
|
||||||
|
|
||||||
import (
|
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -13,21 +29,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
namespace_example = `
|
namespaceExample = `
|
||||||
# view the current namespace in your KUBECONFIG
|
# view the current namespace in your KUBECONFIG
|
||||||
%[1]s ns
|
%[1]s ns
|
||||||
|
|
||||||
# view all of the namespaces in use by contexts in your KUBECONFIG
|
# 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
|
# switch your current-context to one that contains the desired namespace
|
||||||
%[1]s ns foo
|
%[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 {
|
type NamespaceOptions struct {
|
||||||
configFlags *genericclioptions.ConfigFlags
|
configFlags *genericclioptions.ConfigFlags
|
||||||
|
|
||||||
|
resultingContext *api.Context
|
||||||
|
resultingContextName string
|
||||||
|
|
||||||
|
userSpecifiedCluster string
|
||||||
|
userSpecifiedContext string
|
||||||
|
userSpecifiedAuthInfo string
|
||||||
|
userSpecifiedNamespace string
|
||||||
|
|
||||||
rawConfig api.Config
|
rawConfig api.Config
|
||||||
listNamespaces bool
|
listNamespaces bool
|
||||||
args []string
|
args []string
|
||||||
@ -35,6 +63,7 @@ type NamespaceOptions struct {
|
|||||||
genericclioptions.IOStreams
|
genericclioptions.IOStreams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewNamespaceOptions provides an instance of NamespaceOptions with default values
|
||||||
func NewNamespaceOptions(streams genericclioptions.IOStreams) *NamespaceOptions {
|
func NewNamespaceOptions(streams genericclioptions.IOStreams) *NamespaceOptions {
|
||||||
return &NamespaceOptions{
|
return &NamespaceOptions{
|
||||||
configFlags: genericclioptions.NewConfigFlags(),
|
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 {
|
func NewCmdNamespace(streams genericclioptions.IOStreams) *cobra.Command {
|
||||||
o := NewNamespaceOptions(streams)
|
o := NewNamespaceOptions(streams)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ns [new-namespace] [flags]",
|
Use: "ns [new-namespace] [flags]",
|
||||||
Short: "View or set the current namespace",
|
Short: "View or set the current namespace",
|
||||||
Example: namespace_example,
|
Example: fmt.Sprintf(namespaceExample, "kubectl"),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
RunE: func(c *cobra.Command, args []string) error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := o.Validate(); err != nil {
|
if err := o.Validate(); err != nil {
|
||||||
@ -72,20 +102,103 @@ func NewCmdNamespace(streams genericclioptions.IOStreams) *cobra.Command {
|
|||||||
return cmd
|
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
|
var err error
|
||||||
o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig()
|
o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
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 {
|
func (o *NamespaceOptions) Validate() error {
|
||||||
if len(o.rawConfig.CurrentContext) == 0 {
|
if len(o.rawConfig.CurrentContext) == 0 {
|
||||||
return fmt.Errorf("no context is currently set in your configuration")
|
return errNoContext
|
||||||
}
|
}
|
||||||
if len(o.args) > 1 {
|
if len(o.args) > 1 {
|
||||||
return fmt.Errorf("either one or no arguments are allowed")
|
return fmt.Errorf("either one or no arguments are allowed")
|
||||||
@ -94,9 +207,11 @@ func (o *NamespaceOptions) Validate() error {
|
|||||||
return nil
|
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 {
|
func (o *NamespaceOptions) Run() error {
|
||||||
if len(o.args) > 0 && len(o.args[0]) > 0 {
|
if len(o.userSpecifiedNamespace) > 0 && o.resultingContext != nil {
|
||||||
return o.setNamespace(o.args[0])
|
return o.setNamespace(o.resultingContext, o.resultingContextName)
|
||||||
}
|
}
|
||||||
|
|
||||||
namespaces := map[string]bool{}
|
namespaces := map[string]bool{}
|
||||||
@ -134,58 +249,41 @@ func (o *NamespaceOptions) Run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *NamespaceOptions) setNamespace(newNamespace string) error {
|
func isContextEqual(ctxA, ctxB *api.Context) bool {
|
||||||
if len(newNamespace) == 0 {
|
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")
|
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()
|
configAccess := clientcmd.NewDefaultPathOptions()
|
||||||
o.rawConfig.CurrentContext = existingCtxName
|
|
||||||
|
|
||||||
if err := clientcmd.ModifyConfig(configAccess, o.rawConfig, true); err != nil {
|
// determine if we have already saved this context to the user's KUBECONFIG before
|
||||||
return err
|
// 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)
|
fmt.Fprintf(o.Out, "namespace changed to %q\n", fromContext.Namespace)
|
||||||
return nil
|
return clientcmd.ModifyConfig(configAccess, o.rawConfig, true)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user