From dd011371385a297310d69576564cf2f13d0a7533 Mon Sep 17 00:00:00 2001 From: deads2k Date: Fri, 2 Jan 2015 13:08:37 -0500 Subject: [PATCH] add namespace to kubeconfig file --- docs/kubectl.md | 78 +++++++++---------- pkg/client/clientcmd/api/types.go | 2 + pkg/client/clientcmd/api/v1/types.go | 2 + pkg/client/clientcmd/client_config.go | 52 +++++++++++-- pkg/client/clientcmd/client_config_test.go | 18 +++++ pkg/client/clientcmd/merged_client_builder.go | 10 +++ pkg/client/clientcmd/overrides.go | 68 ++++++++++------ pkg/kubectl/cmd/cmd.go | 46 +++-------- pkg/kubectl/cmd/cmd_test.go | 29 +++++-- pkg/kubectl/cmd/create.go | 5 +- pkg/kubectl/cmd/create_test.go | 6 +- pkg/kubectl/cmd/delete.go | 7 +- pkg/kubectl/cmd/delete_test.go | 14 ++-- pkg/kubectl/cmd/describe.go | 5 +- pkg/kubectl/cmd/describe_test.go | 5 +- pkg/kubectl/cmd/get.go | 13 +++- pkg/kubectl/cmd/get_test.go | 26 +++---- pkg/kubectl/cmd/log.go | 3 +- pkg/kubectl/cmd/printing.go | 10 ++- pkg/kubectl/cmd/resize.go | 6 +- pkg/kubectl/cmd/resource.go | 40 +++++----- pkg/kubectl/cmd/rollingupdate.go | 12 ++- pkg/kubectl/cmd/run.go | 4 +- pkg/kubectl/cmd/update.go | 16 +++- 24 files changed, 300 insertions(+), 177 deletions(-) diff --git a/docs/kubectl.md b/docs/kubectl.md index ba21583102..593464f811 100644 --- a/docs/kubectl.md +++ b/docs/kubectl.md @@ -29,8 +29,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -65,8 +65,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -p, --port=8001: The port on which to run the proxy -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr @@ -122,9 +122,9 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. + --namespace="": If present, the namespace scope for this CLI request. --no-headers=false: When using the default output, don't print headers - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -o, --output="": Output format: json|yaml|template|templatefile --output-version="": Output the formatted object with the given version (default api-version) -l, --selector="": Selector (label query) to filter on @@ -167,8 +167,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -213,8 +213,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -262,8 +262,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. --patch="": A JSON document to override the existing resource. The resource is downloaded, then patched with the JSON, the updated -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr @@ -322,8 +322,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -l, --selector="": Selector (label query) to filter on -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr @@ -370,8 +370,8 @@ Available Commands: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -425,8 +425,8 @@ Usage: --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version --merge=false: merge together the full hierarchy of .kubeconfig files - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -466,8 +466,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. --server="": server for the cluster entry in .kubeconfig --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -507,8 +507,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": token for the user entry in .kubeconfig @@ -549,7 +549,7 @@ Usage: --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version --namespace="": namespace for the context entry in .kubeconfig - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -591,8 +591,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -632,8 +632,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -670,8 +670,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -714,8 +714,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -757,8 +757,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr --token="": Bearer token for authentication to the API server. @@ -805,8 +805,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. --poll-interval="3s": Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr @@ -859,8 +859,8 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --namespace="": If present, the namespace scope for this CLI request. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. --replicas=-1: The new number desired number of replicas. Required. --resource-version="": Precondition for resource version. Requires that the current resource version match this value in order to resize -s, --server="": The address of the Kubernetes API server @@ -915,9 +915,9 @@ Usage: --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version - -n, --namespace="": If present, the namespace scope for this CLI request. + --namespace="": If present, the namespace scope for this CLI request. --no-headers=false: When using the default output, don't print headers - --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -o, --output="": Output format: json|yaml|template|templatefile --output-version="": Output the formatted object with the given version (default api-version) --overrides="": An inline JSON override for the generated object. If this is non-empty, it is parsed used to override the generated object. Requires that the object supply a valid apiVersion field. diff --git a/pkg/client/clientcmd/api/types.go b/pkg/client/clientcmd/api/types.go index 5b90969f5e..d4dbce9d54 100644 --- a/pkg/client/clientcmd/api/types.go +++ b/pkg/client/clientcmd/api/types.go @@ -83,6 +83,8 @@ type Context struct { AuthInfo string `json:"user"` // Namespace is the default namespace to use on unspecified requests Namespace string `json:"namespace,omitempty"` + // NamespacePath is the path to a kubernetes ns file (~/.kubernetes_ns) + NamespacePath string `json:"namespace-path,omitempty"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"` } diff --git a/pkg/client/clientcmd/api/v1/types.go b/pkg/client/clientcmd/api/v1/types.go index b5dcf83c54..7e31d14a4f 100644 --- a/pkg/client/clientcmd/api/v1/types.go +++ b/pkg/client/clientcmd/api/v1/types.go @@ -83,6 +83,8 @@ type Context struct { AuthInfo string `json:"user"` // Namespace is the default namespace to use on unspecified requests Namespace string `json:"namespace,omitempty"` + // NamespacePath is the path to a kubernetes ns file (~/.kubernetes_ns) + NamespacePath string `json:"namespace-path,omitempty"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions []NamedExtension `json:"extensions,omitempty"` } diff --git a/pkg/client/clientcmd/client_config.go b/pkg/client/clientcmd/client_config.go index c285494365..cc2264d7f2 100644 --- a/pkg/client/clientcmd/client_config.go +++ b/pkg/client/clientcmd/client_config.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" ) @@ -36,9 +37,12 @@ var ( // ClientConfig is used to make it easy to get an api server client type ClientConfig interface { + // RawConfig returns the merged result of all overrides RawConfig() (clientcmdapi.Config, error) // ClientConfig returns a complete client config ClientConfig() (*client.Config, error) + // Namespace returns the namespace resulting from the merged result of all overrides + Namespace() (string, error) } // DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information @@ -226,6 +230,35 @@ func canIdentifyUser(config client.Config) bool { } +// Namespace implements KubeConfig +func (config DirectClientConfig) Namespace() (string, error) { + if err := config.ConfirmUsable(); err != nil { + return "", err + } + + configContext := config.getContext() + + if len(configContext.Namespace) != 0 { + return configContext.Namespace, nil + } + + if len(configContext.NamespacePath) != 0 { + nsInfo, err := kubectl.LoadNamespaceInfo(configContext.NamespacePath) + if err != nil { + return "", err + } + + return nsInfo.Namespace, nil + } + + // if nothing was specified, try the default file + nsInfo, err := kubectl.LoadNamespaceInfo(os.Getenv("HOME") + "/.kubernetes_ns") + if err != nil { + return "", err + } + return nsInfo.Namespace, nil +} + // ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config, // but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible. func (config DirectClientConfig) ConfirmUsable() error { @@ -248,21 +281,30 @@ func (config DirectClientConfig) getContextName() string { } func (config DirectClientConfig) getAuthInfoName() string { - if len(config.overrides.AuthInfoName) != 0 { - return config.overrides.AuthInfoName + if len(config.overrides.Context.AuthInfo) != 0 { + return config.overrides.Context.AuthInfo } return config.getContext().AuthInfo } func (config DirectClientConfig) getClusterName() string { - if len(config.overrides.ClusterName) != 0 { - return config.overrides.ClusterName + if len(config.overrides.Context.Cluster) != 0 { + return config.overrides.Context.Cluster } return config.getContext().Cluster } func (config DirectClientConfig) getContext() clientcmdapi.Context { - return config.config.Contexts[config.getContextName()] + contexts := config.config.Contexts + contextName := config.getContextName() + + var mergedContext clientcmdapi.Context + if configContext, exists := contexts[contextName]; exists { + mergo.Merge(&mergedContext, configContext) + } + mergo.Merge(&mergedContext, config.overrides.Context) + + return mergedContext } func (config DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo { diff --git a/pkg/client/clientcmd/client_config_test.go b/pkg/client/clientcmd/client_config_test.go index d5a973c7a7..171bf70f3a 100644 --- a/pkg/client/clientcmd/client_config_test.go +++ b/pkg/client/clientcmd/client_config_test.go @@ -48,6 +48,24 @@ func createValidTestConfig() *clientcmdapi.Config { return config } +func TestMergeContext(t *testing.T) { + const namespace = "overriden-namespace" + + config := createValidTestConfig() + clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ + Context: clientcmdapi.Context{ + Namespace: namespace, + }, + }) + + actual, err := clientBuilder.Namespace() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + matchStringArg(namespace, actual, t) +} + func TestCreateClean(t *testing.T) { config := createValidTestConfig() clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}) diff --git a/pkg/client/clientcmd/merged_client_builder.go b/pkg/client/clientcmd/merged_client_builder.go index ffbe4ab68e..f6bc5c4556 100644 --- a/pkg/client/clientcmd/merged_client_builder.go +++ b/pkg/client/clientcmd/merged_client_builder.go @@ -78,3 +78,13 @@ func (config DeferredLoadingClientConfig) ClientConfig() (*client.Config, error) return mergedClientConfig.ClientConfig() } + +// Namespace implements KubeConfig +func (config DeferredLoadingClientConfig) Namespace() (string, error) { + mergedKubeConfig, err := config.createClientConfig() + if err != nil { + return "", err + } + + return mergedKubeConfig.Namespace() +} diff --git a/pkg/client/clientcmd/overrides.go b/pkg/client/clientcmd/overrides.go index 5534142ddf..6c25d57240 100644 --- a/pkg/client/clientcmd/overrides.go +++ b/pkg/client/clientcmd/overrides.go @@ -27,10 +27,8 @@ import ( type ConfigOverrides struct { AuthInfo clientcmdapi.AuthInfo ClusterInfo clientcmdapi.Cluster - Namespace string + Context clientcmdapi.Context CurrentContext string - ClusterName string - AuthInfoName string } // ConfigOverrideFlags holds the flag names to be used for binding command line flags. Notice that this structure tightly @@ -38,10 +36,8 @@ type ConfigOverrides struct { type ConfigOverrideFlags struct { AuthOverrideFlags AuthOverrideFlags ClusterOverrideFlags ClusterOverrideFlags - Namespace string + ContextOverrideFlags ContextOverrideFlags CurrentContext string - ClusterName string - AuthInfoName string } // AuthOverrideFlags holds the flag names to be used for binding command line flags for AuthInfo objects @@ -52,6 +48,14 @@ type AuthOverrideFlags struct { Token string } +// ContextOverrideFlags holds the flag names to be used for binding command line flags for Cluster objects +type ContextOverrideFlags struct { + ClusterName string + AuthInfoName string + Namespace string + NamespacePath string +} + // ClusterOverride holds the flag names to be used for binding command line flags for Cluster objects type ClusterOverrideFlags struct { APIServer string @@ -61,18 +65,19 @@ type ClusterOverrideFlags struct { } const ( - FlagClusterName = "cluster" - FlagAuthInfoName = "user" - FlagContext = "context" - FlagNamespace = "namespace" - FlagAPIServer = "server" - FlagAPIVersion = "api-version" - FlagAuthPath = "auth-path" - FlagInsecure = "insecure-skip-tls-verify" - FlagCertFile = "client-certificate" - FlagKeyFile = "client-key" - FlagCAFile = "certificate-authority" - FlagBearerToken = "token" + FlagClusterName = "cluster" + FlagAuthInfoName = "user" + FlagContext = "context" + FlagNamespace = "namespace" + FlagNamespacePath = "ns-path" + FlagAPIServer = "server" + FlagAPIVersion = "api-version" + FlagAuthPath = "auth-path" + FlagInsecure = "insecure-skip-tls-verify" + FlagCertFile = "client-certificate" + FlagKeyFile = "client-key" + FlagCAFile = "certificate-authority" + FlagBearerToken = "token" ) // RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing @@ -100,10 +105,18 @@ func RecommendedConfigOverrideFlags(prefix string) ConfigOverrideFlags { return ConfigOverrideFlags{ AuthOverrideFlags: RecommendedAuthOverrideFlags(prefix), ClusterOverrideFlags: RecommendedClusterOverrideFlags(prefix), - Namespace: prefix + FlagNamespace, + ContextOverrideFlags: RecommendedContextOverrideFlags(prefix), CurrentContext: prefix + FlagContext, - ClusterName: prefix + FlagClusterName, - AuthInfoName: prefix + FlagAuthInfoName, + } +} + +// RecommendedContextOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing +func RecommendedContextOverrideFlags(prefix string) ContextOverrideFlags { + return ContextOverrideFlags{ + ClusterName: prefix + FlagClusterName, + AuthInfoName: prefix + FlagAuthInfoName, + Namespace: prefix + FlagNamespace, + NamespacePath: prefix + FlagNamespacePath, } } @@ -129,9 +142,14 @@ func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, f func BindOverrideFlags(overrides *ConfigOverrides, flags *pflag.FlagSet, flagNames ConfigOverrideFlags) { BindAuthInfoFlags(&overrides.AuthInfo, flags, flagNames.AuthOverrideFlags) BindClusterFlags(&overrides.ClusterInfo, flags, flagNames.ClusterOverrideFlags) - // TODO not integrated yet - // flags.StringVar(&overrides.Namespace, flagNames.Namespace, "", "If present, the namespace scope for this CLI request.") + BindContextFlags(&overrides.Context, flags, flagNames.ContextOverrideFlags) flags.StringVar(&overrides.CurrentContext, flagNames.CurrentContext, "", "The name of the kubeconfig context to use") - flags.StringVar(&overrides.ClusterName, flagNames.ClusterName, "", "The name of the kubeconfig cluster to use") - flags.StringVar(&overrides.AuthInfoName, flagNames.AuthInfoName, "", "The name of the kubeconfig user to use") +} + +// BindFlags is a convenience method to bind the specified flags to their associated variables +func BindContextFlags(contextInfo *clientcmdapi.Context, flags *pflag.FlagSet, flagNames ContextOverrideFlags) { + flags.StringVar(&contextInfo.Cluster, flagNames.ClusterName, "", "The name of the kubeconfig cluster to use") + flags.StringVar(&contextInfo.AuthInfo, flagNames.AuthInfoName, "", "The name of the kubeconfig user to use") + flags.StringVar(&contextInfo.Namespace, flagNames.Namespace, "", "If present, the namespace scope for this CLI request.") + flags.StringVar(&contextInfo.NamespacePath, flagNames.NamespacePath, "", "Path to the namespace info file that holds the namespace context to use for CLI requests.") } diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 30a5c8b404..0b7b4e77cd 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -66,6 +66,8 @@ type Factory struct { Resizer func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Resizer, error) // Returns a schema that can validate objects stored on disk. Validator func(*cobra.Command) (validation.Schema, error) + // Returns the default namespace to use in cases where no other namespace is specified + DefaultNamespace func(cmd *cobra.Command) (string, error) } // NewFactory creates a factory with the default Kubernetes resources defined @@ -84,8 +86,11 @@ func NewFactory() *Factory { flags: flags, Object: func(cmd *cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) { - version := GetFlagString(cmd, "api-version") - return kubectl.OutputVersionMapper{mapper, version}, api.Scheme + cfg, err := clientConfig.ClientConfig() + checkErr(err) + cmdApiVersion := cfg.Version + + return kubectl.OutputVersionMapper{mapper, cmdApiVersion}, api.Scheme }, Client: func(cmd *cobra.Command) (*client.Client, error) { return clients.ClientForVersion("") @@ -135,6 +140,9 @@ func NewFactory() *Factory { } return validation.NullSchema{}, nil }, + DefaultNamespace: func(cmd *cobra.Command) (string, error) { + return clientConfig.Namespace() + }, } } @@ -158,8 +166,6 @@ func (f *Factory) BindFlags(flags *pflag.FlagSet) { // TODO Add a verbose flag that turns on glog logging. Probably need a way // to do that automatically for every subcommand. flags.BoolVar(&f.clients.matchVersion, FlagMatchBinaryVersion, false, "Require server version to match client version") - flags.String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.") - flags.StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.") flags.Bool("validate", false, "If true, use a schema to validate the input before sending it") } @@ -264,38 +270,6 @@ func runHelp(cmd *cobra.Command, args []string) { cmd.Help() } -// GetKubeNamespace returns the value of the namespace a -// user provided on the command line or use the default -// namespace. -func GetKubeNamespace(cmd *cobra.Command) string { - result := api.NamespaceDefault - if ns := GetFlagString(cmd, "namespace"); len(ns) > 0 { - result = ns - glog.V(2).Infof("Using namespace from -ns flag") - } else { - nsPath := GetFlagString(cmd, "ns-path") - nsInfo, err := kubectl.LoadNamespaceInfo(nsPath) - if err != nil { - glog.Fatalf("Error loading current namespace: %v", err) - } - result = nsInfo.Namespace - } - glog.V(2).Infof("Using namespace %s", result) - return result -} - -// GetExplicitKubeNamespace returns the value of the namespace a -// user explicitly provided on the command line, or false if no -// such namespace was specified. -func GetExplicitKubeNamespace(cmd *cobra.Command) (string, bool) { - if ns := GetFlagString(cmd, "namespace"); len(ns) > 0 { - return ns, true - } - // TODO: determine when --ns-path is set but equal to the default - // value and return its value and true. - return "", false -} - type clientSwaggerSchema struct { c *client.Client t runtime.ObjectTyper diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 317c46c917..50dc9315a3 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" . "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" @@ -92,13 +93,15 @@ func (t *testDescriber) Describe(namespace, name string) (output string, err err } type testFactory struct { - Mapper meta.RESTMapper - Typer runtime.ObjectTyper - Client kubectl.RESTClient - Describer kubectl.Describer - Printer kubectl.ResourcePrinter - Validator validation.Schema - Err error + Mapper meta.RESTMapper + Typer runtime.ObjectTyper + Client kubectl.RESTClient + Describer kubectl.Describer + Printer kubectl.ResourcePrinter + Validator validation.Schema + Namespace string + ClientConfig *client.Config + Err error } func NewTestFactory() (*Factory, *testFactory, runtime.Codec) { @@ -124,6 +127,12 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) { Validator: func(cmd *cobra.Command) (validation.Schema, error) { return t.Validator, t.Err }, + DefaultNamespace: func(cmd *cobra.Command) (string, error) { + return t.Namespace, t.Err + }, + ClientConfig: func(cmd *cobra.Command) (*client.Config, error) { + return t.ClientConfig, t.Err + }, }, t, codec } @@ -147,6 +156,12 @@ func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) { Validator: func(cmd *cobra.Command) (validation.Schema, error) { return t.Validator, t.Err }, + DefaultNamespace: func(cmd *cobra.Command) (string, error) { + return t.Namespace, t.Err + }, + ClientConfig: func(cmd *cobra.Command) (*client.Config, error) { + return t.ClientConfig, t.Err + }, }, t, latest.Codec } diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index ea97f8ae60..1038560eed 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -47,10 +47,13 @@ Examples: schema, err := f.Validator(cmd) checkErr(err) + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + mapper, typer := f.Object(cmd) r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). ContinueOnError(). - NamespaceParam(GetKubeNamespace(cmd)).RequireNamespace(). + NamespaceParam(cmdNamespace).RequireNamespace(). FilenameParam(flags.Filenames...). Flatten(). Do() diff --git a/pkg/kubectl/cmd/create_test.go b/pkg/kubectl/cmd/create_test.go index a8b6fa0289..2253b9f74b 100644 --- a/pkg/kubectl/cmd/create_test.go +++ b/pkg/kubectl/cmd/create_test.go @@ -41,10 +41,10 @@ func TestCreateObject(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdCreate(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") cmd.Run(cmd, []string{}) @@ -73,10 +73,10 @@ func TestCreateMultipleObject(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdCreate(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -107,10 +107,10 @@ func TestCreateDirectory(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdCreate(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Run(cmd, []string{}) diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 462eb2eae7..9470405638 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -58,10 +58,13 @@ Examples: $ kubectl delete pod 1234-56-7890-234234-456456 `, Run: func(cmd *cobra.Command, args []string) { + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + mapper, typer := f.Object(cmd) r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). ContinueOnError(). - NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace(). + NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(flags.Filenames...). SelectorParam(GetFlagString(cmd, "selector")). ResourceTypeOrNameArgs(args...). @@ -69,7 +72,7 @@ Examples: Do() found := 0 - err := r.IgnoreErrors(errors.IsNotFound).Visit(func(r *resource.Info) error { + err = r.IgnoreErrors(errors.IsNotFound).Visit(func(r *resource.Info) error { found++ if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil { return err diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index ced3b97127..3885d2f9fd 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -43,10 +43,10 @@ func TestDeleteObject(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") cmd.Run(cmd, []string{}) @@ -71,10 +71,10 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") cmd.Run(cmd, []string{}) @@ -98,12 +98,12 @@ func TestDeleteNoObjects(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) stderr := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) cmd.SetOutput(stderr) - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"pods"}) if buf.String() != "" { @@ -133,10 +133,10 @@ func TestDeleteMultipleObject(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -165,10 +165,10 @@ func TestDeleteMultipleObjectIgnoreMissing(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json") cmd.Run(cmd, []string{}) @@ -199,10 +199,10 @@ func TestDeleteDirectory(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Run(cmd, []string{}) @@ -240,10 +240,10 @@ func TestDeleteMultipleSelector(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDelete(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("selector", "a=b") cmd.Run(cmd, []string{"pods,services"}) diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index e5d27984ab..7950d57cb7 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -32,8 +32,11 @@ func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command { This command joins many API calls together to form a detailed description of a given resource.`, Run: func(cmd *cobra.Command, args []string) { + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + mapper, _ := f.Object(cmd) - mapping, namespace, name := ResourceFromArgs(cmd, args, mapper) + mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace) describer, err := f.Describer(cmd, mapping) checkErr(err) diff --git a/pkg/kubectl/cmd/describe_test.go b/pkg/kubectl/cmd/describe_test.go index 914aa2097d..994a38e0f8 100644 --- a/pkg/kubectl/cmd/describe_test.go +++ b/pkg/kubectl/cmd/describe_test.go @@ -34,14 +34,13 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { Codec: codec, Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &internalType{Name: "foo"})}, } + tf.Namespace = "non-default" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdDescribe(buf) - cmd.Flags().String("api-version", "default", "") - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"type", "foo"}) - if d.Name != "foo" || d.Namespace != "test" { + if d.Name != "foo" || d.Namespace != "non-default" { t.Errorf("unexpected describer: %#v", d) } diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index febe5ba22c..273fc1d868 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -73,11 +73,14 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { selector := GetFlagString(cmd, "selector") mapper, typer := f.Object(cmd) + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). - NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace(). + NamespaceParam(cmdNamespace).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(args...). SingleResourceType(). @@ -113,7 +116,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { } b := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)). - NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace(). + NamespaceParam(cmdNamespace).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(args...). Latest() @@ -121,8 +124,12 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { checkErr(err) if generic { + clientConfig, err := f.ClientConfig(cmd) + checkErr(err) + defaultVersion := clientConfig.Version + // the outermost object will be converted to the output-version - version := outputVersion(cmd) + version := outputVersion(cmd, defaultVersion) if len(version) == 0 { // TODO: add a new ResourceBuilder mode for Object() that attempts to ensure the objects // are in the appropriate version if one exists (and if not, use the best effort). diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 9ea8145088..6bf792d115 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -69,12 +69,12 @@ func TestGetUnknownSchemaObject(t *testing.T) { Codec: codec, Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &internalType{Name: "foo"})}, } + tf.Namespace = "test" + tf.ClientConfig = &client.Config{Version: latest.Version} buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("api-version", "default", "") - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"type", "foo"}) expected := &internalType{Name: "foo"} @@ -98,11 +98,11 @@ func TestGetSchemaObject(t *testing.T) { Codec: codec, Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})}, } + tf.Namespace = "test" + tf.ClientConfig = &client.Config{Version: "v1beta3"} buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) - cmd.Flags().String("api-version", "v1beta3", "") - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"replicationcontrollers", "foo"}) if !strings.Contains(buf.String(), "\"foo\"") { @@ -119,11 +119,11 @@ func TestGetObjects(t *testing.T) { Codec: codec, Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"pods", "foo"}) expected := []runtime.Object{&pods.Items[0]} @@ -145,11 +145,11 @@ func TestGetListObjects(t *testing.T) { Codec: codec, Resp: &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"pods"}) expected := []runtime.Object{pods} @@ -181,11 +181,11 @@ func TestGetMultipleTypeObjects(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Run(cmd, []string{"pods,services"}) expected := []runtime.Object{pods, svc} @@ -217,12 +217,12 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { } }), } + tf.Namespace = "test" + tf.ClientConfig = &client.Config{Version: "v1beta1"} buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") - cmd.Flags().String("api-version", "v1beta1", "") cmd.Flags().Set("output", "json") cmd.Run(cmd, []string{"pods,services"}) @@ -269,11 +269,11 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("selector", "a=b") cmd.Run(cmd, []string{"pods,services"}) @@ -345,11 +345,11 @@ func TestWatchSelector(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("watch", "true") cmd.Flags().Set("selector", "a=b") @@ -384,11 +384,11 @@ func TestWatchResource(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("watch", "true") cmd.Run(cmd, []string{"pods", "foo"}) @@ -422,11 +422,11 @@ func TestWatchOnlyResource(t *testing.T) { } }), } + tf.Namespace = "test" buf := bytes.NewBuffer([]byte{}) cmd := f.NewCmdGet(buf) cmd.SetOutput(buf) - cmd.Flags().String("namespace", "test", "") cmd.Flags().Set("watch-only", "true") cmd.Run(cmd, []string{"pods", "foo"}) diff --git a/pkg/kubectl/cmd/log.go b/pkg/kubectl/cmd/log.go index 1e88248e2f..f1067172b5 100644 --- a/pkg/kubectl/cmd/log.go +++ b/pkg/kubectl/cmd/log.go @@ -43,7 +43,8 @@ Examples: usageError(cmd, "log []") } - namespace := GetKubeNamespace(cmd) + namespace, err := f.DefaultNamespace(cmd) + checkErr(err) client, err := f.Client(cmd) checkErr(err) diff --git a/pkg/kubectl/cmd/printing.go b/pkg/kubectl/cmd/printing.go index f66763d256..3f3b5ad5ba 100644 --- a/pkg/kubectl/cmd/printing.go +++ b/pkg/kubectl/cmd/printing.go @@ -56,10 +56,10 @@ func PrintObject(cmd *cobra.Command, obj runtime.Object, f *Factory, out io.Writ } // outputVersion returns the preferred output version for generic content (JSON, YAML, or templates) -func outputVersion(cmd *cobra.Command) string { +func outputVersion(cmd *cobra.Command, defaultVersion string) string { outputVersion := GetFlagString(cmd, "output-version") if len(outputVersion) == 0 { - outputVersion = GetFlagString(cmd, "api-version") + outputVersion = defaultVersion } return outputVersion } @@ -84,7 +84,11 @@ func PrinterForMapping(f *Factory, cmd *cobra.Command, mapping *meta.RESTMapping return nil, err } if ok { - version := outputVersion(cmd) + clientConfig, err := f.ClientConfig(cmd) + checkErr(err) + defaultVersion := clientConfig.Version + + version := outputVersion(cmd, defaultVersion) if len(version) == 0 { version = mapping.APIVersion } diff --git a/pkg/kubectl/cmd/resize.go b/pkg/kubectl/cmd/resize.go index 5b2980f4e2..2a54766342 100644 --- a/pkg/kubectl/cmd/resize.go +++ b/pkg/kubectl/cmd/resize.go @@ -48,8 +48,12 @@ Examples: if len(args) != 2 || count < 0 { usageError(cmd, "--replicas= ") } + + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + mapper, _ := f.Object(cmd) - mapping, namespace, name := ResourceFromArgs(cmd, args, mapper) + mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace) resizer, err := f.Resizer(cmd, mapping) checkErr(err) diff --git a/pkg/kubectl/cmd/resource.go b/pkg/kubectl/cmd/resource.go index 060e9f966d..ab44099678 100644 --- a/pkg/kubectl/cmd/resource.go +++ b/pkg/kubectl/cmd/resource.go @@ -41,18 +41,20 @@ func ResourcesFromArgsOrFile( clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (resource.RESTClient, error), schema validation.Schema, requireNames bool, + cmdNamespace, + cmdVersion string, ) resource.Visitor { // handling filename & resource id if len(selector) == 0 { if requireNames || len(filename) > 0 { - mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema) + mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema, cmdNamespace, cmdVersion) client, err := clientBuilder(cmd, mapping) checkErr(err) return resource.NewInfo(client, mapping, namespace, name) } if len(args) == 2 { - mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, mapper) + mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, mapper, cmdNamespace, cmdVersion) client, err := clientBuilder(cmd, mapping) checkErr(err) return resource.NewInfo(client, mapping, namespace, name) @@ -62,7 +64,7 @@ func ResourcesFromArgsOrFile( labelSelector, err := labels.ParseSelector(selector) checkErr(err) - namespace := GetKubeNamespace(cmd) + namespace := cmdNamespace visitors := resource.VisitorList{} if len(args) < 1 { @@ -94,7 +96,7 @@ func ResourcesFromArgsOrFile( // ResourceFromArgsOrFile expects two arguments or a valid file with a given type, and extracts // the fields necessary to uniquely locate a resource. Displays a usageError if that contract is // not satisfied, or a generic error if any other problems occur. -func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema) (mapping *meta.RESTMapping, namespace, name string) { +func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema, cmdNamespace, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string) { // If command line args are passed in, use those preferentially. if len(args) > 0 && len(args) != 2 { usageError(cmd, "If passing in command line parameters, must be resource and name") @@ -102,7 +104,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, if len(args) == 2 { resource := args[0] - namespace = GetKubeNamespace(cmd) + namespace = cmdNamespace name = args[1] if len(name) == 0 || len(resource) == 0 { usageError(cmd, "Must specify filename or command line params") @@ -113,8 +115,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, // The error returned by mapper is "no resource defined", which is a usage error usageError(cmd, err.Error()) } - version := GetFlagString(cmd, "api-version") - mapping, err = mapper.RESTMapping(kind, version, defaultVersion) + mapping, err = mapper.RESTMapping(kind, cmdVersion, defaultVersion) checkErr(err) return } @@ -123,7 +124,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, usageError(cmd, "Must specify filename or command line params") } - mapping, namespace, name, _ = ResourceFromFile(cmd, filename, typer, mapper, schema) + mapping, namespace, name, _ = ResourceFromFile(filename, typer, mapper, schema, cmdVersion) if len(name) == 0 { checkErr(fmt.Errorf("the resource in the provided file has no name (or ID) defined")) } @@ -134,13 +135,13 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, // ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary // to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or // a generic error if any other problems occur. -func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) { +func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper, cmdNamespace string) (mapping *meta.RESTMapping, namespace, name string) { if len(args) != 2 { usageError(cmd, "Must provide resource and name command line params") } resource := args[0] - namespace = GetKubeNamespace(cmd) + namespace = cmdNamespace name = args[1] if len(name) == 0 || len(resource) == 0 { usageError(cmd, "Must provide resource and name command line params") @@ -157,7 +158,7 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) // ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary // to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or // a generic error if any other problems occur. -func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) { +func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper, cmdNamespace, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string) { if len(args) == 0 || len(args) > 2 { usageError(cmd, "Must provide resource or a resource and name as command line params") } @@ -167,7 +168,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM usageError(cmd, "Must provide resource or a resource and name as command line params") } - namespace = GetKubeNamespace(cmd) + namespace = cmdNamespace if len(args) == 2 { name = args[1] if len(name) == 0 { @@ -178,8 +179,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM defaultVersion, kind, err := mapper.VersionAndKindForResource(resource) checkErr(err) - version := GetFlagString(cmd, "api-version") - mapping, err = mapper.RESTMapping(kind, version, defaultVersion) + mapping, err = mapper.RESTMapping(kind, cmdVersion, defaultVersion) checkErr(err) return @@ -188,7 +188,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM // ResourceFromFile retrieves the name and namespace from a valid file. If the file does not // resolve to a known type an error is returned. The returned mapping can be used to determine // the correct REST endpoint to modify this resource with. -func ResourceFromFile(cmd *cobra.Command, filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema) (mapping *meta.RESTMapping, namespace, name string, data []byte) { +func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string, data []byte) { configData, err := ReadConfigData(filename) checkErr(err) data = configData @@ -218,20 +218,18 @@ func ResourceFromFile(cmd *cobra.Command, filename string, typer runtime.ObjectT checkErr(err) // if the preferred API version differs, get a different mapper - version := GetFlagString(cmd, "api-version") - if version != objVersion { - mapping, err = mapper.RESTMapping(kind, version) + if cmdVersion != objVersion { + mapping, err = mapper.RESTMapping(kind, cmdVersion) checkErr(err) } return } -// CompareNamespaceFromFile returns an error if the namespace the user has provided on the CLI +// CompareNamespace returns an error if the namespace the user has provided on the CLI // or via the default namespace file does not match the namespace of an input file. This // prevents a user from unintentionally updating the wrong namespace. -func CompareNamespaceFromFile(cmd *cobra.Command, namespace string) error { - defaultNamespace := GetKubeNamespace(cmd) +func CompareNamespace(defaultNamespace, namespace string) error { if len(namespace) > 0 { if defaultNamespace != namespace { return fmt.Errorf("the namespace from the provided file %q does not match the namespace %q. You must pass '--namespace=%s' to perform this operation.", namespace, defaultNamespace, namespace) diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index b5e25f509a..4cf57c565b 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -61,8 +61,13 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f - oldName := args[0] schema, err := f.Validator(cmd) checkErr(err) + + clientConfig, err := f.ClientConfig(cmd) + checkErr(err) + cmdApiVersion := clientConfig.Version + mapper, typer := f.Object(cmd) - mapping, namespace, newName, data := ResourceFromFile(cmd, filename, typer, mapper, schema) + mapping, namespace, newName, data := ResourceFromFile(filename, typer, mapper, schema, cmdApiVersion) if mapping.Kind != "ReplicationController" { usageError(cmd, "%s does not specify a valid ReplicationController", filename) } @@ -70,7 +75,10 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f - usageError(cmd, "%s cannot have the same name as the existing ReplicationController %s", filename, oldName) } - err = CompareNamespaceFromFile(cmd, namespace) + + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + err = CompareNamespace(cmdNamespace, namespace) checkErr(err) client, err := f.Client(cmd) diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 234599d0dd..05a2d9056c 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -49,7 +49,9 @@ Examples: usageError(cmd, " is required for run") } - namespace := GetKubeNamespace(cmd) + namespace, err := f.DefaultNamespace(cmd) + checkErr(err) + client, err := f.Client(cmd) checkErr(err) diff --git a/pkg/kubectl/cmd/update.go b/pkg/kubectl/cmd/update.go index b606681552..6b191533f8 100644 --- a/pkg/kubectl/cmd/update.go +++ b/pkg/kubectl/cmd/update.go @@ -56,6 +56,7 @@ Examples: } else { name = updateWithPatch(cmd, args, f, patch) } + fmt.Fprintf(out, "%s\n", name) }, } @@ -65,8 +66,11 @@ Examples: } func updateWithPatch(cmd *cobra.Command, args []string, f *Factory, patch string) string { + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + mapper, _ := f.Object(cmd) - mapping, namespace, name := ResourceFromArgs(cmd, args, mapper) + mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace) client, err := f.RESTClient(cmd, mapping) checkErr(err) @@ -89,12 +93,18 @@ func updateWithFile(cmd *cobra.Command, f *Factory, filename string) string { checkErr(err) mapper, typer := f.Object(cmd) - mapping, namespace, name, data := ResourceFromFile(cmd, filename, typer, mapper, schema) + clientConfig, err := f.ClientConfig(cmd) + checkErr(err) + cmdApiVersion := clientConfig.Version + + mapping, namespace, name, data := ResourceFromFile(filename, typer, mapper, schema, cmdApiVersion) client, err := f.RESTClient(cmd, mapping) checkErr(err) - err = CompareNamespaceFromFile(cmd, namespace) + cmdNamespace, err := f.DefaultNamespace(cmd) + checkErr(err) + err = CompareNamespace(cmdNamespace, namespace) checkErr(err) err = resource.NewHelper(client, mapping).Update(namespace, name, true, data)