diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index c4a3963bd9..238b289c03 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -776,12 +776,9 @@ _kubectl_config() flags_with_completion=() flags_completion=() - flags+=("--envvar") - flags+=("--global") flags+=("--help") flags+=("-h") flags+=("--kubeconfig=") - flags+=("--local") must_have_one_flag=() must_have_one_noun=() diff --git a/docs/kubeconfig-file.md b/docs/kubeconfig-file.md index 63496216b6..275890bef1 100644 --- a/docs/kubeconfig-file.md +++ b/docs/kubeconfig-file.md @@ -84,15 +84,20 @@ If the contents of the kubernetes_auth file conflict with entries in .kubeconfig ## Loading and merging rules The rules for loading and merging the .kubeconfig files are straightforward, but there are a lot of them. The final config is built in this order: - 1. Merge together the kubeconfig itself. This is done with the following hierarchy and merge rules: + 1. Get the kubeconfig from disk. This is done with the following hierarchy and merge rules: + + If the CommandLineLocation (the value of the `kubeconfig` command line option) is set, use this file only. No merging. Only one instance of this flag is allowed. + + + Else, if EnvVarLocation (the value of $KUBECONFIG) is available, use it as a list of files that should be merged. + Merge files together based on the following rules. Empty filenames are ignored. Files with non-deserializable content produced errors. The first file to set a particular value or map key wins and the value or map key is never changed. This means that the first file to set CurrentContext will have its context preserved. It also means that if two files specify a "red-user", only values from the first file's red-user are used. Even non-conflicting entries from the second file's "red-user" are discarded. - 1. CommandLineLocation - the value of the `kubeconfig` command line option - 1. EnvVarLocation - the value of $KUBECONFIG - 1. CurrentDirectoryLocation - ``pwd``/.kubeconfig - 1. HomeDirectoryLocation = ~/.kube/.kubeconfig + + + Otherwise, use HomeDirectoryLocation (~/.kube/config) with no merging. 1. Determine the context to use based on the first hit in this chain 1. command line argument - the value of the `context` command line option 1. current-context from the merged kubeconfig file diff --git a/docs/kubectl.md b/docs/kubectl.md index 081d485c76..cc5d7e8a99 100644 --- a/docs/kubectl.md +++ b/docs/kubectl.md @@ -66,4 +66,4 @@ kubectl * [kubectl update](kubectl_update.md) - Update a resource by filename or stdin. * [kubectl version](kubectl_version.md) - Print the client and server version information. -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.641789142 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392549632 +0000 UTC diff --git a/docs/kubectl_api-versions.md b/docs/kubectl_api-versions.md index 864b501c4f..d7bbe180f3 100644 --- a/docs/kubectl_api-versions.md +++ b/docs/kubectl_api-versions.md @@ -50,4 +50,4 @@ kubectl api-versions ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.641051929 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.39227534 +0000 UTC diff --git a/docs/kubectl_cluster-info.md b/docs/kubectl_cluster-info.md index 36d5890762..61d3559bb6 100644 --- a/docs/kubectl_cluster-info.md +++ b/docs/kubectl_cluster-info.md @@ -50,4 +50,4 @@ kubectl cluster-info ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.640723377 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392162759 +0000 UTC diff --git a/docs/kubectl_config.md b/docs/kubectl_config.md index 70309a1bf7..19649d52d6 100644 --- a/docs/kubectl_config.md +++ b/docs/kubectl_config.md @@ -7,6 +7,12 @@ config modifies kubeconfig files config modifies kubeconfig files using subcommands like "kubectl config set current-context my-context" +The loading order follows these rules: + 1. If the --kubeconfig flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. + 2. If $KUBECONFIG environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged together. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. + 3. Otherwise, ${HOME}/.kube/config is used and no merging takes place. + + ``` kubectl config SUBCOMMAND ``` @@ -14,11 +20,8 @@ kubectl config SUBCOMMAND ### Options ``` - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig -h, --help=false: help for config --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory ``` ### Options inherrited from parent commands @@ -60,4 +63,4 @@ kubectl config SUBCOMMAND * [kubectl config use-context](kubectl_config_use-context.md) - Sets the current-context in a kubeconfig file * [kubectl config view](kubectl_config_view.md) - displays Merged kubeconfig settings or a specified kubeconfig file. -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.640323684 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392043616 +0000 UTC diff --git a/docs/kubectl_config_set-cluster.md b/docs/kubectl_config_set-cluster.md index b3aa67a92d..309b22920f 100644 --- a/docs/kubectl_config_set-cluster.md +++ b/docs/kubectl_config_set-cluster.md @@ -45,10 +45,7 @@ $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true --client-key="": Path to a client key file for TLS. --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -68,4 +65,4 @@ $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.637680508 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.39119629 +0000 UTC diff --git a/docs/kubectl_config_set-context.md b/docs/kubectl_config_set-context.md index 689d851130..15cb76d9f2 100644 --- a/docs/kubectl_config_set-context.md +++ b/docs/kubectl_config_set-context.md @@ -38,11 +38,8 @@ $ kubectl config set-context gce --user=cluster-admin --client-certificate="": Path to a client key file for TLS. --client-key="": Path to a client key file for TLS. --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -61,4 +58,4 @@ $ kubectl config set-context gce --user=cluster-admin ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.638371771 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391488399 +0000 UTC diff --git a/docs/kubectl_config_set-credentials.md b/docs/kubectl_config_set-credentials.md index 2ca6e7b83b..0a9969ae0c 100644 --- a/docs/kubectl_config_set-credentials.md +++ b/docs/kubectl_config_set-credentials.md @@ -59,11 +59,8 @@ $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt - --certificate-authority="": Path to a cert. file for the certificate authority. --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -81,4 +78,4 @@ $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt - ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.638019407 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391323192 +0000 UTC diff --git a/docs/kubectl_config_set.md b/docs/kubectl_config_set.md index 12601a4270..63a5230f64 100644 --- a/docs/kubectl_config_set.md +++ b/docs/kubectl_config_set.md @@ -30,11 +30,8 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE --client-key="": Path to a client key file for TLS. --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -55,4 +52,4 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.638724317 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391618859 +0000 UTC diff --git a/docs/kubectl_config_unset.md b/docs/kubectl_config_unset.md index 3c9e8d1a60..4559d34440 100644 --- a/docs/kubectl_config_unset.md +++ b/docs/kubectl_config_unset.md @@ -29,11 +29,8 @@ kubectl config unset PROPERTY_NAME --client-key="": Path to a client key file for TLS. --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -54,4 +51,4 @@ kubectl config unset PROPERTY_NAME ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.639325332 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391735806 +0000 UTC diff --git a/docs/kubectl_config_use-context.md b/docs/kubectl_config_use-context.md index 600c319d3e..9ef37418dd 100644 --- a/docs/kubectl_config_use-context.md +++ b/docs/kubectl_config_use-context.md @@ -28,11 +28,8 @@ kubectl config use-context CONTEXT_NAME --client-key="": Path to a client key file for TLS. --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -53,4 +50,4 @@ kubectl config use-context CONTEXT_NAME ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.639859272 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391848246 +0000 UTC diff --git a/docs/kubectl_config_view.md b/docs/kubectl_config_view.md index 22dd4f41e1..0bd157be1b 100644 --- a/docs/kubectl_config_view.md +++ b/docs/kubectl_config_view.md @@ -50,11 +50,8 @@ $ kubectl config view -o template --template='{{range .users}}{{ if eq .name "e2 --client-key="": Path to a client key file for TLS. --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use - --envvar=false: use the kubeconfig from $KUBECONFIG - --global=false: use the kubeconfig from /home/username/.kube/.kubeconfig --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": use a particular kubeconfig file - --local=false: use the kubeconfig in the current directory --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -75,4 +72,4 @@ $ kubectl config view -o template --template='{{range .users}}{{ if eq .name "e2 ### SEE ALSO * [kubectl config](kubectl_config.md) - config modifies kubeconfig files -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.637312729 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391073075 +0000 UTC diff --git a/docs/kubectl_create.md b/docs/kubectl_create.md index 83dcc1d3f2..82f121037f 100644 --- a/docs/kubectl_create.md +++ b/docs/kubectl_create.md @@ -63,4 +63,4 @@ $ cat pod.json | kubectl create -f - ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.629142674 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.388588064 +0000 UTC diff --git a/docs/kubectl_delete.md b/docs/kubectl_delete.md index 0be8a70529..2f2f11249f 100644 --- a/docs/kubectl_delete.md +++ b/docs/kubectl_delete.md @@ -81,4 +81,4 @@ $ kubectl delete pods --all ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.630884641 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389412973 +0000 UTC diff --git a/docs/kubectl_describe.md b/docs/kubectl_describe.md index 545a915278..527e57977a 100644 --- a/docs/kubectl_describe.md +++ b/docs/kubectl_describe.md @@ -53,4 +53,4 @@ kubectl describe RESOURCE ID ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.628735136 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.388410556 +0000 UTC diff --git a/docs/kubectl_exec.md b/docs/kubectl_exec.md index e48b80570f..8339368116 100644 --- a/docs/kubectl_exec.md +++ b/docs/kubectl_exec.md @@ -64,4 +64,4 @@ $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.632991694 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390127525 +0000 UTC diff --git a/docs/kubectl_expose.md b/docs/kubectl_expose.md index 4aee0a61e2..de10086a0d 100644 --- a/docs/kubectl_expose.md +++ b/docs/kubectl_expose.md @@ -82,4 +82,4 @@ $ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.636399596 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390792874 +0000 UTC diff --git a/docs/kubectl_get.md b/docs/kubectl_get.md index 8fab6a29ae..86e4b6aefb 100644 --- a/docs/kubectl_get.md +++ b/docs/kubectl_get.md @@ -85,4 +85,4 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7 ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.620151758 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.387483074 +0000 UTC diff --git a/docs/kubectl_label.md b/docs/kubectl_label.md index f441d299b8..83d688ab8a 100644 --- a/docs/kubectl_label.md +++ b/docs/kubectl_label.md @@ -81,4 +81,4 @@ $ kubectl label pods foo bar- ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.636842283 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390937166 +0000 UTC diff --git a/docs/kubectl_log.md b/docs/kubectl_log.md index 8363949c67..5f7bb2f064 100644 --- a/docs/kubectl_log.md +++ b/docs/kubectl_log.md @@ -62,4 +62,4 @@ $ kubectl log -f 123456-7890 ruby-container ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.631637812 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389728881 +0000 UTC diff --git a/docs/kubectl_namespace.md b/docs/kubectl_namespace.md index b2e464211a..3fbb10002d 100644 --- a/docs/kubectl_namespace.md +++ b/docs/kubectl_namespace.md @@ -53,4 +53,4 @@ kubectl namespace [namespace] ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.63128386 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389609191 +0000 UTC diff --git a/docs/kubectl_port-forward.md b/docs/kubectl_port-forward.md index 6ca9e5268e..da2f9fd11d 100644 --- a/docs/kubectl_port-forward.md +++ b/docs/kubectl_port-forward.md @@ -68,4 +68,4 @@ $ kubectl port-forward -p mypod 0:5000 ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.633423694 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390241417 +0000 UTC diff --git a/docs/kubectl_proxy.md b/docs/kubectl_proxy.md index 5e5084c4a4..745cff9d3c 100644 --- a/docs/kubectl_proxy.md +++ b/docs/kubectl_proxy.md @@ -65,4 +65,4 @@ $ kubectl proxy --api-prefix=k8s-api ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.633829265 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390360738 +0000 UTC diff --git a/docs/kubectl_resize.md b/docs/kubectl_resize.md index 39419535a1..bc24c391b3 100644 --- a/docs/kubectl_resize.md +++ b/docs/kubectl_resize.md @@ -68,4 +68,4 @@ $ kubectl resize --current-replicas=2 --replicas=3 replicationcontrollers foo ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.632560634 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389989377 +0000 UTC diff --git a/docs/kubectl_rolling-update.md b/docs/kubectl_rolling-update.md index c835992adb..3d7dcf28d8 100644 --- a/docs/kubectl_rolling-update.md +++ b/docs/kubectl_rolling-update.md @@ -68,4 +68,4 @@ $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f - ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.632023561 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.38985117 +0000 UTC diff --git a/docs/kubectl_run-container.md b/docs/kubectl_run-container.md index d510e374f3..91cf6e0dba 100644 --- a/docs/kubectl_run-container.md +++ b/docs/kubectl_run-container.md @@ -78,4 +78,4 @@ $ kubectl run-container nginx --image=nginx --overrides='{ "apiVersion": "v1beta ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.634391744 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390501802 +0000 UTC diff --git a/docs/kubectl_stop.md b/docs/kubectl_stop.md index d79c40bdaa..17849c8f27 100644 --- a/docs/kubectl_stop.md +++ b/docs/kubectl_stop.md @@ -72,4 +72,4 @@ $ kubectl stop -f path/to/resources ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.635920945 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390631789 +0000 UTC diff --git a/docs/kubectl_update.md b/docs/kubectl_update.md index 04e93aed7c..352ce0f68e 100644 --- a/docs/kubectl_update.md +++ b/docs/kubectl_update.md @@ -67,4 +67,4 @@ $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState": ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.630218109 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.388743178 +0000 UTC diff --git a/docs/kubectl_version.md b/docs/kubectl_version.md index 6bd810e61b..7b79e8a885 100644 --- a/docs/kubectl_version.md +++ b/docs/kubectl_version.md @@ -51,4 +51,4 @@ kubectl version ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-04-14 19:53:16.641381587 +0000 UTC +###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392395408 +0000 UTC diff --git a/docs/man/man1/kubectl-config-set-cluster.1 b/docs/man/man1/kubectl-config-set-cluster.1 index ccaf234dce..a63c4c3c54 100644 --- a/docs/man/man1/kubectl-config-set-cluster.1 +++ b/docs/man/man1/kubectl-config-set-cluster.1 @@ -68,22 +68,10 @@ Specifying a name that already exists will merge new fields on top of existing v \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config-set-context.1 b/docs/man/man1/kubectl-config-set-context.1 index 822a8c248a..19043acb03 100644 --- a/docs/man/man1/kubectl-config-set-context.1 +++ b/docs/man/man1/kubectl-config-set-context.1 @@ -64,14 +64,6 @@ Specifying a name that already exists will merge new fields on top of existing v \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-insecure\-skip\-tls\-verify\fP=false If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. @@ -80,10 +72,6 @@ Specifying a name that already exists will merge new fields on top of existing v \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config-set-credentials.1 b/docs/man/man1/kubectl-config-set-credentials.1 index 6136aa476c..77afc1832d 100644 --- a/docs/man/man1/kubectl-config-set-credentials.1 +++ b/docs/man/man1/kubectl-config-set-credentials.1 @@ -87,14 +87,6 @@ Bearer token and basic auth are mutually exclusive. \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-insecure\-skip\-tls\-verify\fP=false If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. @@ -103,10 +95,6 @@ Bearer token and basic auth are mutually exclusive. \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config-set.1 b/docs/man/man1/kubectl-config-set.1 index fed5524d0e..ce977f677e 100644 --- a/docs/man/man1/kubectl-config-set.1 +++ b/docs/man/man1/kubectl-config-set.1 @@ -57,14 +57,6 @@ PROPERTY\_VALUE is the new value you wish to set. \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-insecure\-skip\-tls\-verify\fP=false If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. @@ -73,10 +65,6 @@ PROPERTY\_VALUE is the new value you wish to set. \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config-unset.1 b/docs/man/man1/kubectl-config-unset.1 index 1a3c6f6a37..4bfb6f77c6 100644 --- a/docs/man/man1/kubectl-config-unset.1 +++ b/docs/man/man1/kubectl-config-unset.1 @@ -56,14 +56,6 @@ PROPERTY\_NAME is a dot delimited name where each token represents either a attr \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-insecure\-skip\-tls\-verify\fP=false If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. @@ -72,10 +64,6 @@ PROPERTY\_NAME is a dot delimited name where each token represents either a attr \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config-use-context.1 b/docs/man/man1/kubectl-config-use-context.1 index febd1fe0a3..12814efc69 100644 --- a/docs/man/man1/kubectl-config-use-context.1 +++ b/docs/man/man1/kubectl-config-use-context.1 @@ -55,14 +55,6 @@ Sets the current\-context in a kubeconfig file \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-insecure\-skip\-tls\-verify\fP=false If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. @@ -71,10 +63,6 @@ Sets the current\-context in a kubeconfig file \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config-view.1 b/docs/man/man1/kubectl-config-view.1 index 47b4efdd60..0d80c5b786 100644 --- a/docs/man/man1/kubectl-config-view.1 +++ b/docs/man/man1/kubectl-config-view.1 @@ -87,14 +87,6 @@ You can use \-\-output=template \-\-template=TEMPLATE to extract specific values \fB\-\-context\fP="" The name of the kubeconfig context to use -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-\-insecure\-skip\-tls\-verify\fP=false If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. @@ -103,10 +95,6 @@ You can use \-\-output=template \-\-template=TEMPLATE to extract specific values \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .PP \fB\-\-log\_backtrace\_at\fP=:0 when logging hits line file:N, emit a stack trace diff --git a/docs/man/man1/kubectl-config.1 b/docs/man/man1/kubectl-config.1 index 8dc96aa6f0..e99d2bece1 100644 --- a/docs/man/man1/kubectl-config.1 +++ b/docs/man/man1/kubectl-config.1 @@ -15,16 +15,14 @@ kubectl config \- config modifies kubeconfig files .PP config modifies kubeconfig files using subcommands like "kubectl config set current\-context my\-context" +.PP +The loading order follows these rules: + 1. If the \-\-kubeconfig flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. + 2. If $KUBECONFIG environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged together. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. + 3. Otherwise, $\{HOME\}/.kube/config is used and no merging takes place. + .SH OPTIONS -.PP -\fB\-\-envvar\fP=false - use the kubeconfig from $KUBECONFIG - -.PP -\fB\-\-global\fP=false - use the kubeconfig from /home/username/.kube/.kubeconfig - .PP \fB\-h\fP, \fB\-\-help\fP=false help for config @@ -33,10 +31,6 @@ config modifies kubeconfig files using subcommands like "kubectl config set curr \fB\-\-kubeconfig\fP="" use a particular kubeconfig file -.PP -\fB\-\-local\fP=false - use the kubeconfig in the current directory - .SH OPTIONS INHERITED FROM PARENT COMMANDS .PP diff --git a/pkg/client/clientcmd/loader.go b/pkg/client/clientcmd/loader.go index b2151594ae..e3d6e90ff8 100644 --- a/pkg/client/clientcmd/loader.go +++ b/pkg/client/clientcmd/loader.go @@ -18,8 +18,10 @@ package clientcmd import ( "fmt" + "io" "io/ioutil" "os" + "path" "path/filepath" "github.com/ghodss/yaml" @@ -33,22 +35,24 @@ import ( const ( RecommendedConfigPathFlag = "kubeconfig" RecommendedConfigPathEnvVar = "KUBECONFIG" - - DefaultEnvVarIndex = 0 - DefaultCurrentDirIndex = 1 - DefaultHomeDirIndex = 2 + RecommendedHomeFileName = "/.kube/config" ) +var OldRecommendedHomeFile = path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig") +var RecommendedHomeFile = path.Join(os.Getenv("HOME"), RecommendedHomeFileName) + // ClientConfigLoadingRules is an ExplicitPath and string slice of specific locations that are used for merging together a Config // Callers can put the chain together however they want, but we'd recommend: -// [0] = EnvVarPath -// [1] = CurrentDirectoryPath -// [2] = HomeDirectoryPath +// EnvVarPathFiles if set (a list of files if set) OR the HomeDirectoryPath // ExplicitPath is special, because if a user specifically requests a certain file be used and error is reported if thie file is not present type ClientConfigLoadingRules struct { ExplicitPath string Precedence []string + // MigrationRules is a map of destination files to source files. If a destination file is not present, then the source file is checked. + // If the source file is present, then it is copied to the destination file BEFORE any further loading happens. + MigrationRules map[string]string + // DoNotResolvePaths indicates whether or not to resolve paths with respect to the originating files. This is phrased as a negative so // that a default object that doesn't set this will usually get the behavior it wants. DoNotResolvePaths bool @@ -57,14 +61,29 @@ type ClientConfigLoadingRules struct { // NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to // use this constructor func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules { + chain := []string{} + migrationRules := map[string]string{} + + envVarFiles := os.Getenv(RecommendedConfigPathEnvVar) + if len(envVarFiles) != 0 { + chain = append(chain, filepath.SplitList(envVarFiles)...) + + } else { + chain = append(chain, RecommendedHomeFile) + migrationRules[RecommendedHomeFile] = OldRecommendedHomeFile + + } + return &ClientConfigLoadingRules{ - Precedence: []string{os.Getenv(RecommendedConfigPathEnvVar), ".kubeconfig", os.Getenv("HOME") + "/.kube/.kubeconfig"}, + Precedence: chain, + MigrationRules: migrationRules, } } -// Load takes the loading rules and merges together a Config object based on following order. -// 1. ExplicitPath -// 2. Precedence slice +// Load starts by running the MigrationRules and then +// takes the loading rules and returns a Config object based on following rules. +// if the ExplicitPath, return the unmerged explicit file +// Otherwise, return a merged config based on the Precedence slice // A missing ExplicitPath file produces an error. Empty filenames or other missing files are ignored. // Read errors or files with non-deserializable content produce errors. // The first file to set a particular map key wins and map key's value is never changed. @@ -75,17 +94,25 @@ func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules { // Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder // and only absolute file paths are returned. func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { + if err := rules.Migrate(); err != nil { + return nil, err + } + errlist := []error{} + kubeConfigFiles := []string{} + // Make sure a file we were explicitly told to use exists if len(rules.ExplicitPath) > 0 { if _, err := os.Stat(rules.ExplicitPath); os.IsNotExist(err) { - errlist = append(errlist, fmt.Errorf("The config file %v does not exist", rules.ExplicitPath)) + return nil, err } - } + kubeConfigFiles = append(kubeConfigFiles, rules.ExplicitPath) - kubeConfigFiles := []string{rules.ExplicitPath} - kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...) + } else { + kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...) + + } // first merge all of our maps mapConfig := clientcmdapi.NewConfig() @@ -120,6 +147,53 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { return config, errors.NewAggregate(errlist) } +// Migrate uses the MigrationRules map. If a destination file is not present, then the source file is checked. +// If the source file is present, then it is copied to the destination file BEFORE any further loading happens. +func (rules *ClientConfigLoadingRules) Migrate() error { + if rules.MigrationRules == nil { + return nil + } + + for destination, source := range rules.MigrationRules { + if _, err := os.Stat(destination); err == nil { + // if the destination already exists, do nothing + continue + } else if !os.IsNotExist(err) { + // if we had an error other than non-existence, fail + return err + } + + if sourceInfo, err := os.Stat(source); err != nil { + if os.IsNotExist(err) { + // if the source file doesn't exist, there's no work to do. + continue + } + + // if we had an error other than non-existence, fail + return err + } else if sourceInfo.IsDir() { + return fmt.Errorf("cannot migrate %v to %v because it is a directory", source, destination) + } + + in, err := os.Open(source) + if err != nil { + return err + } + defer in.Close() + out, err := os.Create(destination) + if err != nil { + return err + } + defer out.Close() + + if _, err = io.Copy(out, in); err != nil { + return err + } + } + + return nil +} + func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) error { if len(filename) == 0 { // no work to do diff --git a/pkg/client/clientcmd/loader_test.go b/pkg/client/clientcmd/loader_test.go index d9f30ba26f..f17a5ab618 100644 --- a/pkg/client/clientcmd/loader_test.go +++ b/pkg/client/clientcmd/loader_test.go @@ -22,6 +22,7 @@ import ( "os" "path" "path/filepath" + "reflect" "strings" "testing" @@ -209,8 +210,7 @@ func TestResolveRelativePaths(t *testing.T) { WriteToFile(pathResolutionConfig2, configFile2) loadingRules := ClientConfigLoadingRules{ - ExplicitPath: configFile1, - Precedence: []string{configFile2}, + Precedence: []string{configFile1, configFile2}, } mergedConfig, err := loadingRules.Load() @@ -274,7 +274,86 @@ func TestResolveRelativePaths(t *testing.T) { } -func ExampleMergingSomeWithConflict() { +func TestMigratingFile(t *testing.T) { + sourceFile, _ := ioutil.TempFile("", "") + defer os.Remove(sourceFile.Name()) + destinationFile, _ := ioutil.TempFile("", "") + // delete the file so that we'll write to it + os.Remove(destinationFile.Name()) + + WriteToFile(testConfigAlfa, sourceFile.Name()) + + loadingRules := ClientConfigLoadingRules{ + MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()}, + } + + if _, err := loadingRules.Load(); err != nil { + t.Errorf("unexpected error %v", err) + } + + // the load should have recreated this file + defer os.Remove(destinationFile.Name()) + + sourceContent, err := ioutil.ReadFile(sourceFile.Name()) + if err != nil { + t.Errorf("unexpected error %v", err) + } + destinationContent, err := ioutil.ReadFile(destinationFile.Name()) + if err != nil { + t.Errorf("unexpected error %v", err) + } + + if !reflect.DeepEqual(sourceContent, destinationContent) { + t.Errorf("source and destination do not match") + } +} + +func TestMigratingFileLeaveExistingFileAlone(t *testing.T) { + sourceFile, _ := ioutil.TempFile("", "") + defer os.Remove(sourceFile.Name()) + destinationFile, _ := ioutil.TempFile("", "") + defer os.Remove(destinationFile.Name()) + + WriteToFile(testConfigAlfa, sourceFile.Name()) + + loadingRules := ClientConfigLoadingRules{ + MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()}, + } + + if _, err := loadingRules.Load(); err != nil { + t.Errorf("unexpected error %v", err) + } + + destinationContent, err := ioutil.ReadFile(destinationFile.Name()) + if err != nil { + t.Errorf("unexpected error %v", err) + } + + if len(destinationContent) > 0 { + t.Errorf("destination should not have been touched") + } +} + +func TestMigratingFileSourceMissingSkip(t *testing.T) { + sourceFilename := "some-missing-file" + destinationFile, _ := ioutil.TempFile("", "") + // delete the file so that we'll write to it + os.Remove(destinationFile.Name()) + + loadingRules := ClientConfigLoadingRules{ + MigrationRules: map[string]string{destinationFile.Name(): sourceFilename}, + } + + if _, err := loadingRules.Load(); err != nil { + t.Errorf("unexpected error %v", err) + } + + if _, err := os.Stat(destinationFile.Name()); !os.IsNotExist(err) { + t.Errorf("destination should not exist") + } +} + +func ExampleNoMergingOnExplicitPaths() { commandLineFile, _ := ioutil.TempFile("", "") defer os.Remove(commandLineFile.Name()) envVarFile, _ := ioutil.TempFile("", "") @@ -299,6 +378,52 @@ func ExampleMergingSomeWithConflict() { fmt.Printf("Unexpected error: %v", err) } + fmt.Printf("%v", string(output)) + // Output: + // apiVersion: v1 + // clusters: + // - cluster: + // server: http://cow.org:8080 + // name: cow-cluster + // contexts: + // - context: + // cluster: cow-cluster + // namespace: hammer-ns + // user: red-user + // name: federal-context + // current-context: "" + // kind: Config + // preferences: {} + // users: + // - name: red-user + // user: + // token: red-token +} + +func ExampleMergingSomeWithConflict() { + commandLineFile, _ := ioutil.TempFile("", "") + defer os.Remove(commandLineFile.Name()) + envVarFile, _ := ioutil.TempFile("", "") + defer os.Remove(envVarFile.Name()) + + WriteToFile(testConfigAlfa, commandLineFile.Name()) + WriteToFile(testConfigConflictAlfa, envVarFile.Name()) + + loadingRules := ClientConfigLoadingRules{ + Precedence: []string{commandLineFile.Name(), envVarFile.Name()}, + } + + mergedConfig, err := loadingRules.Load() + + json, err := clientcmdlatest.Codec.Encode(mergedConfig) + if err != nil { + fmt.Printf("Unexpected error: %v", err) + } + output, err := yaml.JSONToYAML(json) + if err != nil { + fmt.Printf("Unexpected error: %v", err) + } + fmt.Printf("%v", string(output)) // Output: // apiVersion: v1 @@ -344,8 +469,7 @@ func ExampleMergingEverythingNoConflicts() { WriteToFile(testConfigDelta, homeDirFile.Name()) loadingRules := ClientConfigLoadingRules{ - ExplicitPath: commandLineFile.Name(), - Precedence: []string{envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()}, + Precedence: []string{commandLineFile.Name(), envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()}, } mergedConfig, err := loadingRules.Load() diff --git a/pkg/client/clientcmd/merged_client_builder_test.go b/pkg/client/clientcmd/merged_client_builder_test.go index 266bd3d386..6a87fdf572 100644 --- a/pkg/client/clientcmd/merged_client_builder_test.go +++ b/pkg/client/clientcmd/merged_client_builder_test.go @@ -78,10 +78,7 @@ func testWriteAuthInfoFile(auth clientauth.Info, filename string) error { } func testBindClientConfig(cmd *cobra.Command) ClientConfig { - loadingRules := NewDefaultClientConfigLoadingRules() - loadingRules.Precedence[DefaultEnvVarIndex] = "" - loadingRules.Precedence[DefaultCurrentDirIndex] = "" - loadingRules.Precedence[DefaultHomeDirIndex] = "" + loadingRules := &ClientConfigLoadingRules{} cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") overrides := &ConfigOverrides{} diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 203095e46c..b7576ad19c 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -18,10 +18,10 @@ package config import ( "errors" - "fmt" "io" "os" "path" + "path/filepath" "reflect" "strconv" @@ -33,41 +33,54 @@ import ( ) type PathOptions struct { - Local bool - Global bool - UseEnvVar bool - - LocalFile string + // GlobalFile is the full path to the file to load as the global (final) option GlobalFile string - EnvVarFile string - - EnvVar string + // EnvVar is the env var name that points to the list of kubeconfig files to load + EnvVar string + // ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file ExplicitFileFlag string + // GlobalFileSubpath is an optional value used for displaying help + GlobalFileSubpath string + LoadingRules *clientcmd.ClientConfigLoadingRules } +// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files +type ConfigAccess interface { + // GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config + GetLoadingPrecedence() []string + // GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules + GetStartingConfig() (*clientcmdapi.Config, error) + // GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create a new stanza as opposed to updating an existing one. + GetDefaultFilename() string + // IsExplicitFile indicates whether or not this command is interested in exactly one file. This implementation only ever does that via a flag, but implementations that handle local, global, and flags may have more + IsExplicitFile() bool + // GetExplicitFile returns the particular file this command is operating against. This implementation only ever has one, but implementations that handle local, global, and flags may have more + GetExplicitFile() string +} + func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command { if len(pathOptions.ExplicitFileFlag) == 0 { pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag } - if len(pathOptions.EnvVar) > 0 { - pathOptions.EnvVarFile = os.Getenv(pathOptions.EnvVar) - } cmd := &cobra.Command{ Use: "config SUBCOMMAND", Short: "config modifies kubeconfig files", - Long: `config modifies kubeconfig files using subcommands like "kubectl config set current-context my-context"`, + Long: `config modifies kubeconfig files using subcommands like "kubectl config set current-context my-context" + +The loading order follows these rules: + 1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. + 2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged together. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. + 3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place. +`, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, } // file paths are common to all sub commands - cmd.PersistentFlags().BoolVar(&pathOptions.Local, "local", pathOptions.Local, "use the kubeconfig in the current directory") - cmd.PersistentFlags().BoolVar(&pathOptions.Global, "global", pathOptions.Global, "use the kubeconfig from "+pathOptions.GlobalFile) - cmd.PersistentFlags().BoolVar(&pathOptions.UseEnvVar, "envvar", pathOptions.UseEnvVar, "use the kubeconfig from $"+pathOptions.EnvVar) cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file") cmd.AddCommand(NewCmdConfigView(out, pathOptions)) @@ -83,13 +96,12 @@ func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command { func NewDefaultPathOptions() *PathOptions { ret := &PathOptions{ - LocalFile: ".kubeconfig", - GlobalFile: path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig"), - EnvVar: clientcmd.RecommendedConfigPathEnvVar, - EnvVarFile: os.Getenv(clientcmd.RecommendedConfigPathEnvVar), - + GlobalFile: clientcmd.RecommendedHomeFile, + EnvVar: clientcmd.RecommendedConfigPathEnvVar, ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag, + GlobalFileSubpath: clientcmd.RecommendedHomeFileName, + LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(), } ret.LoadingRules.DoNotResolvePaths = true @@ -97,125 +109,78 @@ func NewDefaultPathOptions() *PathOptions { return ret } -func (o PathOptions) Validate() error { - if len(o.LoadingRules.ExplicitPath) > 0 { - if o.Global { - return errors.New("cannot specify both --" + o.ExplicitFileFlag + " and --global") - } - if o.Local { - return errors.New("cannot specify both --" + o.ExplicitFileFlag + " and --local") - } - if o.UseEnvVar { - return errors.New("cannot specify both --" + o.ExplicitFileFlag + " and --envvar") - } +func (o *PathOptions) GetEnvVarFiles() []string { + if len(o.EnvVar) == 0 { + return []string{} } - if o.Global { - if o.Local { - return errors.New("cannot specify both --global and --local") - } - if o.UseEnvVar { - return errors.New("cannot specify both --global and --envvar") - } + envVarValue := os.Getenv(o.EnvVar) + if len(envVarValue) == 0 { + return []string{} } - if o.Local { - if o.UseEnvVar { - return errors.New("cannot specify both --local and --envvar") - } - } - - if o.UseEnvVar { - if len(o.EnvVarFile) == 0 { - return fmt.Errorf("environment variable %v does not have a value", o.EnvVar) - } - - } - - return nil + return filepath.SplitList(envVarValue) } -func (o *PathOptions) getStartingConfig() (*clientcmdapi.Config, error) { - if err := o.Validate(); err != nil { +func (o *PathOptions) GetLoadingPrecedence() []string { + if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 { + return envVarFiles + } + + return []string{o.GlobalFile} +} + +func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) { + // don't mutate the original + loadingRules := *o.LoadingRules + loadingRules.Precedence = o.GetLoadingPrecedence() + + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &clientcmd.ConfigOverrides{}) + rawConfig, err := clientConfig.RawConfig() + if os.IsNotExist(err) { + return clientcmdapi.NewConfig(), nil + } + if err != nil { return nil, err } - config := clientcmdapi.NewConfig() - - switch { - case o.Global: - config = getConfigFromFileOrDie(o.GlobalFile) - - case o.UseEnvVar: - config = getConfigFromFileOrDie(o.EnvVarFile) - - case o.Local: - config = getConfigFromFileOrDie(o.LocalFile) - - // no specific flag was set, load according to the loading rules - default: - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(o.LoadingRules, &clientcmd.ConfigOverrides{}) - rawConfig, err := clientConfig.RawConfig() - if err != nil { - return nil, err - } - config = &rawConfig - - } - - return config, nil + return &rawConfig, nil } -// GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create -// a new stanza as opposed to updating an existing one. func (o *PathOptions) GetDefaultFilename() string { if o.IsExplicitFile() { return o.GetExplicitFile() } - if len(o.EnvVarFile) > 0 { - return o.EnvVarFile - } + if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 { + if len(envVarFiles) == 1 { + return envVarFiles[0] + } - if _, err := os.Stat(o.LocalFile); err == nil { - return o.LocalFile + // if any of the envvar files already exists, return it + for _, envVarFile := range envVarFiles { + if _, err := os.Stat(envVarFile); err == nil { + return envVarFile + } + } + + // otherwise, return the last one in the list + return envVarFiles[len(envVarFiles)-1] } return o.GlobalFile - } func (o *PathOptions) IsExplicitFile() bool { - switch { - case len(o.LoadingRules.ExplicitPath) > 0 || - o.Global || - o.UseEnvVar || - o.Local: + if len(o.LoadingRules.ExplicitPath) > 0 { return true } + return false } func (o *PathOptions) GetExplicitFile() string { - if !o.IsExplicitFile() { - return "" - } - - switch { - case len(o.LoadingRules.ExplicitPath) > 0: - return o.LoadingRules.ExplicitPath - - case o.Global: - return o.GlobalFile - - case o.UseEnvVar: - return o.EnvVarFile - - case o.Local: - return o.LocalFile - } - - return "" + return o.LoadingRules.ExplicitPath } // ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or @@ -223,8 +188,8 @@ func (o *PathOptions) GetExplicitFile() string { // Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values // (no nil strings), we're forced have separate handling for them. In all the currently known cases, newConfig should have, at most, one difference, // that means that this code will only write into a single file. -func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { - startingConfig, err := o.getStartingConfig() +func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) error { + startingConfig, err := configAccess.GetStartingConfig() if err != nil { return err } @@ -237,12 +202,12 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { // nothing to do case startingConfig.CurrentContext != newConfig.CurrentContext: - if err := o.writeCurrentContext(newConfig.CurrentContext); err != nil { + if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil { return err } case !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences): - if err := o.writePreferences(newConfig.Preferences); err != nil { + if err := writePreferences(configAccess, newConfig.Preferences); err != nil { return err } @@ -253,7 +218,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { if !reflect.DeepEqual(cluster, startingCluster) || !exists { destinationFile := cluster.LocationOfOrigin if len(destinationFile) == 0 { - destinationFile = o.GetDefaultFilename() + destinationFile = configAccess.GetDefaultFilename() } configToWrite := getConfigFromFileOrDie(destinationFile) @@ -270,7 +235,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { if !reflect.DeepEqual(context, startingContext) || !exists { destinationFile := context.LocationOfOrigin if len(destinationFile) == 0 { - destinationFile = o.GetDefaultFilename() + destinationFile = configAccess.GetDefaultFilename() } configToWrite := getConfigFromFileOrDie(destinationFile) @@ -287,7 +252,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists { destinationFile := authInfo.LocationOfOrigin if len(destinationFile) == 0 { - destinationFile = o.GetDefaultFilename() + destinationFile = configAccess.GetDefaultFilename() } configToWrite := getConfigFromFileOrDie(destinationFile) @@ -303,7 +268,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { if _, exists := newConfig.Clusters[key]; !exists { destinationFile := cluster.LocationOfOrigin if len(destinationFile) == 0 { - destinationFile = o.GetDefaultFilename() + destinationFile = configAccess.GetDefaultFilename() } configToWrite := getConfigFromFileOrDie(destinationFile) @@ -319,7 +284,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { if _, exists := newConfig.Contexts[key]; !exists { destinationFile := context.LocationOfOrigin if len(destinationFile) == 0 { - destinationFile = o.GetDefaultFilename() + destinationFile = configAccess.GetDefaultFilename() } configToWrite := getConfigFromFileOrDie(destinationFile) @@ -335,7 +300,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { if _, exists := newConfig.AuthInfos[key]; !exists { destinationFile := authInfo.LocationOfOrigin if len(destinationFile) == 0 { - destinationFile = o.GetDefaultFilename() + destinationFile = configAccess.GetDefaultFilename() } configToWrite := getConfigFromFileOrDie(destinationFile) @@ -356,15 +321,26 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { // If newCurrentContext is the same as the startingConfig's current context, then we exit. // If newCurrentContext has a value, then that value is written into the default destination file. // If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file -func (o *PathOptions) writeCurrentContext(newCurrentContext string) error { - if startingConfig, err := o.getStartingConfig(); err != nil { +func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error { + if startingConfig, err := configAccess.GetStartingConfig(); err != nil { return err } else if startingConfig.CurrentContext == newCurrentContext { return nil } + if configAccess.IsExplicitFile() { + file := configAccess.GetExplicitFile() + currConfig := getConfigFromFileOrDie(file) + currConfig.CurrentContext = newCurrentContext + if err := clientcmd.WriteToFile(*currConfig, file); err != nil { + return err + } + + return nil + } + if len(newCurrentContext) > 0 { - destinationFile := o.GetDefaultFilename() + destinationFile := configAccess.GetDefaultFilename() config := getConfigFromFileOrDie(destinationFile) config.CurrentContext = newCurrentContext @@ -375,46 +351,34 @@ func (o *PathOptions) writeCurrentContext(newCurrentContext string) error { return nil } - if o.IsExplicitFile() { - file := o.GetExplicitFile() - currConfig := getConfigFromFileOrDie(file) - currConfig.CurrentContext = newCurrentContext - if err := clientcmd.WriteToFile(*currConfig, file); err != nil { - return err - } + // we're supposed to be clearing the current context. We need to find the first spot in the chain that is setting it and clear it + for _, file := range configAccess.GetLoadingPrecedence() { + if _, err := os.Stat(file); err == nil { + currConfig := getConfigFromFileOrDie(file) - return nil - } + if len(currConfig.CurrentContext) > 0 { + currConfig.CurrentContext = newCurrentContext + if err := clientcmd.WriteToFile(*currConfig, file); err != nil { + return err + } - filesToCheck := make([]string, 0, len(o.LoadingRules.Precedence)+1) - filesToCheck = append(filesToCheck, o.LoadingRules.ExplicitPath) - filesToCheck = append(filesToCheck, o.LoadingRules.Precedence...) - - for _, file := range filesToCheck { - currConfig := getConfigFromFileOrDie(file) - - if len(currConfig.CurrentContext) > 0 { - currConfig.CurrentContext = newCurrentContext - if err := clientcmd.WriteToFile(*currConfig, file); err != nil { - return err + return nil } - - return nil } } - return nil + return errors.New("no config found to write context") } -func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error { - if startingConfig, err := o.getStartingConfig(); err != nil { +func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error { + if startingConfig, err := configAccess.GetStartingConfig(); err != nil { return err } else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) { return nil } - if o.IsExplicitFile() { - file := o.GetExplicitFile() + if configAccess.IsExplicitFile() { + file := configAccess.GetExplicitFile() currConfig := getConfigFromFileOrDie(file) currConfig.Preferences = newPrefs if err := clientcmd.WriteToFile(*currConfig, file); err != nil { @@ -424,11 +388,7 @@ func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error return nil } - filesToCheck := make([]string, 0, len(o.LoadingRules.Precedence)+1) - filesToCheck = append(filesToCheck, o.LoadingRules.ExplicitPath) - filesToCheck = append(filesToCheck, o.LoadingRules.Precedence...) - - for _, file := range filesToCheck { + for _, file := range configAccess.GetLoadingPrecedence() { currConfig := getConfigFromFileOrDie(file) if !reflect.DeepEqual(currConfig.Preferences, newPrefs) { @@ -441,7 +401,7 @@ func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error } } - return nil + return errors.New("no config found to write preferences") } // getConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit diff --git a/pkg/kubectl/cmd/config/create_authinfo.go b/pkg/kubectl/cmd/config/create_authinfo.go index 47f7a8e932..ebe5b46f96 100644 --- a/pkg/kubectl/cmd/config/create_authinfo.go +++ b/pkg/kubectl/cmd/config/create_authinfo.go @@ -31,7 +31,7 @@ import ( ) type createAuthInfoOptions struct { - pathOptions *PathOptions + configAccess ConfigAccess name string authPath util.StringFlag clientCertificate util.StringFlag @@ -67,8 +67,8 @@ $ kubectl set-credentials cluster-admin --username=admin --password=uXFGweU9l35q // Embed client certificate data in the "cluster-admin" entry $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true` -func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *PathOptions) *cobra.Command { - options := &createAuthInfoOptions{pathOptions: pathOptions} +func NewCmdConfigSetAuthInfo(out io.Writer, configAccess ConfigAccess) *cobra.Command { + options := &createAuthInfoOptions{configAccess: configAccess} cmd := &cobra.Command{ Use: fmt.Sprintf("set-credentials NAME [--%v=/path/to/authfile] [--%v=path/to/certfile] [--%v=path/to/keyfile] [--%v=bearer_token] [--%v=basic_user] [--%v=basic_password]", clientcmd.FlagAuthPath, clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword), @@ -104,7 +104,7 @@ func (o createAuthInfoOptions) run() error { return err } - config, err := o.pathOptions.getStartingConfig() + config, err := o.configAccess.GetStartingConfig() if err != nil { return err } @@ -112,7 +112,7 @@ func (o createAuthInfoOptions) run() error { authInfo := o.modifyAuthInfo(config.AuthInfos[o.name]) config.AuthInfos[o.name] = authInfo - if err := o.pathOptions.ModifyConfig(*config); err != nil { + if err := ModifyConfig(o.configAccess, *config); err != nil { return err } @@ -225,5 +225,5 @@ func (o createAuthInfoOptions) validate() error { } } - return o.pathOptions.Validate() + return nil } diff --git a/pkg/kubectl/cmd/config/create_cluster.go b/pkg/kubectl/cmd/config/create_cluster.go index 389c4429ca..c91681720a 100644 --- a/pkg/kubectl/cmd/config/create_cluster.go +++ b/pkg/kubectl/cmd/config/create_cluster.go @@ -30,7 +30,7 @@ import ( ) type createClusterOptions struct { - pathOptions *PathOptions + configAccess ConfigAccess name string server util.StringFlag apiVersion util.StringFlag @@ -52,8 +52,8 @@ $ kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/kubernetes. $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true` ) -func NewCmdConfigSetCluster(out io.Writer, pathOptions *PathOptions) *cobra.Command { - options := &createClusterOptions{pathOptions: pathOptions} +func NewCmdConfigSetCluster(out io.Writer, configAccess ConfigAccess) *cobra.Command { + options := &createClusterOptions{configAccess: configAccess} cmd := &cobra.Command{ Use: fmt.Sprintf("set-cluster NAME [--%v=server] [--%v=path/to/certficate/authority] [--%v=apiversion] [--%v=true]", clientcmd.FlagAPIServer, clientcmd.FlagCAFile, clientcmd.FlagAPIVersion, clientcmd.FlagInsecure), @@ -89,7 +89,7 @@ func (o createClusterOptions) run() error { return err } - config, err := o.pathOptions.getStartingConfig() + config, err := o.configAccess.GetStartingConfig() if err != nil { return err } @@ -97,7 +97,7 @@ func (o createClusterOptions) run() error { cluster := o.modifyCluster(config.Clusters[o.name]) config.Clusters[o.name] = cluster - if err := o.pathOptions.ModifyConfig(*config); err != nil { + if err := ModifyConfig(o.configAccess, *config); err != nil { return err } @@ -169,5 +169,5 @@ func (o createClusterOptions) validate() error { } } - return o.pathOptions.Validate() + return nil } diff --git a/pkg/kubectl/cmd/config/create_context.go b/pkg/kubectl/cmd/config/create_context.go index 49f7d66f80..f10b9d23d0 100644 --- a/pkg/kubectl/cmd/config/create_context.go +++ b/pkg/kubectl/cmd/config/create_context.go @@ -29,11 +29,11 @@ import ( ) type createContextOptions struct { - pathOptions *PathOptions - name string - cluster util.StringFlag - authInfo util.StringFlag - namespace util.StringFlag + configAccess ConfigAccess + name string + cluster util.StringFlag + authInfo util.StringFlag + namespace util.StringFlag } const ( @@ -43,8 +43,8 @@ Specifying a name that already exists will merge new fields on top of existing v $ kubectl config set-context gce --user=cluster-admin` ) -func NewCmdConfigSetContext(out io.Writer, pathOptions *PathOptions) *cobra.Command { - options := &createContextOptions{pathOptions: pathOptions} +func NewCmdConfigSetContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { + options := &createContextOptions{configAccess: configAccess} cmd := &cobra.Command{ Use: fmt.Sprintf("set-context NAME [--%v=cluster_nickname] [--%v=user_nickname] [--%v=namespace]", clientcmd.FlagClusterName, clientcmd.FlagAuthInfoName, clientcmd.FlagNamespace), @@ -76,7 +76,7 @@ func (o createContextOptions) run() error { return err } - config, err := o.pathOptions.getStartingConfig() + config, err := o.configAccess.GetStartingConfig() if err != nil { return err } @@ -84,7 +84,7 @@ func (o createContextOptions) run() error { context := o.modifyContext(config.Contexts[o.name]) config.Contexts[o.name] = context - if err := o.pathOptions.ModifyConfig(*config); err != nil { + if err := ModifyConfig(o.configAccess, *config); err != nil { return err } @@ -123,5 +123,5 @@ func (o createContextOptions) validate() error { return errors.New("You must specify a non-empty context name") } - return o.pathOptions.Validate() + return nil } diff --git a/pkg/kubectl/cmd/config/set.go b/pkg/kubectl/cmd/config/set.go index 1eaf69b1ca..0c0faad17d 100644 --- a/pkg/kubectl/cmd/config/set.go +++ b/pkg/kubectl/cmd/config/set.go @@ -32,7 +32,7 @@ const ( ) type setOptions struct { - pathOptions *PathOptions + configAccess ConfigAccess propertyName string propertyValue string } @@ -41,8 +41,8 @@ const set_long = `Sets an individual value in a kubeconfig file PROPERTY_NAME is a dot delimited name where each token represents either a attribute name or a map key. Map keys may not contain dots. PROPERTY_VALUE is the new value you wish to set.` -func NewCmdConfigSet(out io.Writer, pathOptions *PathOptions) *cobra.Command { - options := &setOptions{pathOptions: pathOptions} +func NewCmdConfigSet(out io.Writer, configAccess ConfigAccess) *cobra.Command { + options := &setOptions{configAccess: configAccess} cmd := &cobra.Command{ Use: "set PROPERTY_NAME PROPERTY_VALUE", @@ -69,7 +69,7 @@ func (o setOptions) run() error { return err } - config, err := o.pathOptions.getStartingConfig() + config, err := o.configAccess.GetStartingConfig() if err != nil { return err } @@ -82,7 +82,7 @@ func (o setOptions) run() error { return err } - if err := o.pathOptions.ModifyConfig(*config); err != nil { + if err := ModifyConfig(o.configAccess, *config); err != nil { return err } @@ -110,7 +110,7 @@ func (o setOptions) validate() error { return errors.New("You must specify a property") } - return o.pathOptions.Validate() + return nil } func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue string, unset bool) error { diff --git a/pkg/kubectl/cmd/config/unset.go b/pkg/kubectl/cmd/config/unset.go index 59057b1e87..c04ed914e2 100644 --- a/pkg/kubectl/cmd/config/unset.go +++ b/pkg/kubectl/cmd/config/unset.go @@ -26,15 +26,15 @@ import ( ) type unsetOptions struct { - pathOptions *PathOptions + configAccess ConfigAccess propertyName string } const unset_long = `Unsets an individual value in a kubeconfig file PROPERTY_NAME is a dot delimited name where each token represents either a attribute name or a map key. Map keys may not contain dots.` -func NewCmdConfigUnset(out io.Writer, pathOptions *PathOptions) *cobra.Command { - options := &unsetOptions{pathOptions: pathOptions} +func NewCmdConfigUnset(out io.Writer, configAccess ConfigAccess) *cobra.Command { + options := &unsetOptions{configAccess: configAccess} cmd := &cobra.Command{ Use: "unset PROPERTY_NAME", @@ -61,7 +61,7 @@ func (o unsetOptions) run() error { return err } - config, err := o.pathOptions.getStartingConfig() + config, err := o.configAccess.GetStartingConfig() if err != nil { return err } @@ -75,7 +75,7 @@ func (o unsetOptions) run() error { return err } - if err := o.pathOptions.ModifyConfig(*config); err != nil { + if err := ModifyConfig(o.configAccess, *config); err != nil { return err } @@ -98,5 +98,5 @@ func (o unsetOptions) validate() error { return errors.New("You must specify a property") } - return o.pathOptions.Validate() + return nil } diff --git a/pkg/kubectl/cmd/config/use_context.go b/pkg/kubectl/cmd/config/use_context.go index 98a50167e0..b1442f92e7 100644 --- a/pkg/kubectl/cmd/config/use_context.go +++ b/pkg/kubectl/cmd/config/use_context.go @@ -25,12 +25,12 @@ import ( ) type useContextOptions struct { - pathOptions *PathOptions - contextName string + configAccess ConfigAccess + contextName string } -func NewCmdConfigUseContext(out io.Writer, pathOptions *PathOptions) *cobra.Command { - options := &useContextOptions{pathOptions: pathOptions} +func NewCmdConfigUseContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { + options := &useContextOptions{configAccess: configAccess} cmd := &cobra.Command{ Use: "use-context CONTEXT_NAME", @@ -57,14 +57,14 @@ func (o useContextOptions) run() error { return err } - config, err := o.pathOptions.getStartingConfig() + config, err := o.configAccess.GetStartingConfig() if err != nil { return err } config.CurrentContext = o.contextName - if err := o.pathOptions.ModifyConfig(*config); err != nil { + if err := ModifyConfig(o.configAccess, *config); err != nil { return err } @@ -87,5 +87,5 @@ func (o useContextOptions) validate() error { return errors.New("You must specify a current-context") } - return o.pathOptions.Validate() + return nil } diff --git a/pkg/kubectl/cmd/config/view.go b/pkg/kubectl/cmd/config/view.go index 46b81b7b49..e8bb6e2eaa 100644 --- a/pkg/kubectl/cmd/config/view.go +++ b/pkg/kubectl/cmd/config/view.go @@ -32,10 +32,10 @@ import ( ) type ViewOptions struct { - PathOptions *PathOptions - Merge util.BoolFlag - Flatten bool - Minify bool + ConfigAccess ConfigAccess + Merge util.BoolFlag + Flatten bool + Minify bool } const ( @@ -52,8 +52,8 @@ $ kubectl config view --local $ kubectl config view -o template --template='{{range .users}}{{ if eq .name "e2e" }}{{ index .user.password }}{{end}}{{end}}'` ) -func NewCmdConfigView(out io.Writer, PathOptions *PathOptions) *cobra.Command { - options := &ViewOptions{PathOptions: PathOptions} +func NewCmdConfigView(out io.Writer, ConfigAccess ConfigAccess) *cobra.Command { + options := &ViewOptions{ConfigAccess: ConfigAccess} cmd := &cobra.Command{ Use: "view", @@ -116,7 +116,7 @@ func (o ViewOptions) Run(out io.Writer, printer kubectl.ResourcePrinter) error { func (o *ViewOptions) Complete() bool { // if --kubeconfig, --global, or --local is specified, then merging doesn't make sense since you're declaring precise intent - if o.PathOptions.Global || o.PathOptions.Local || o.PathOptions.UseEnvVar { + if o.ConfigAccess.IsExplicitFile() { if !o.Merge.Provided() { o.Merge.Set("false") } @@ -136,32 +136,20 @@ func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) { } func (o ViewOptions) Validate() error { - return o.PathOptions.Validate() + if !o.Merge.Value() && !o.ConfigAccess.IsExplicitFile() { + return errors.New("if merge==false a precise file must to specified") + } + + return nil } // getStartingConfig returns the Config object built from the sources specified by the options, the filename read (only if it was a single file), and an error if something goes wrong func (o *ViewOptions) getStartingConfig() (*clientcmdapi.Config, error) { switch { case !o.Merge.Value(): - switch { - case len(o.PathOptions.LoadingRules.ExplicitPath) > 0: - return clientcmd.LoadFromFile(o.PathOptions.LoadingRules.ExplicitPath) - - case o.PathOptions.Global: - return clientcmd.LoadFromFile(o.PathOptions.GlobalFile) - - case o.PathOptions.UseEnvVar: - return clientcmd.LoadFromFile(o.PathOptions.EnvVarFile) - - case o.PathOptions.Local: - return clientcmd.LoadFromFile(o.PathOptions.LocalFile) - - default: - return nil, errors.New("if Merge==false a precise file must to specified") - - } + return clientcmd.LoadFromFile(o.ConfigAccess.GetExplicitFile()) default: - return o.PathOptions.getStartingConfig() + return o.ConfigAccess.GetStartingConfig() } } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 0f712a4e86..baeafc1687 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -286,11 +286,11 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { // DefaultClientConfig creates a clientcmd.ClientConfig with the following hierarchy: // 1. Use the kubeconfig builder. The number of merges and overrides here gets a little crazy. Stay with me. -// 1. Merge together the kubeconfig itself. This is done with the following hierarchy and merge rules: -// 1. CommandLineLocation - this parsed from the command line, so it must be late bound -// 2. EnvVarLocation -// 3. CurrentDirectoryLocation -// 4. HomeDirectoryLocation +// 1. Merge together the kubeconfig itself. This is done with the following hierarchy rules: +// 1. CommandLineLocation - this parsed from the command line, so it must be late bound. If you specify this, +// then no other kubeconfig files are merged. This file must exist. +// 2. If $KUBECONFIG is set, then it is treated as a list of files that should be merged. +// 3. HomeDirectoryLocation // Empty filenames are ignored. Files with non-deserializable content produced errors. // The first file to set a particular value or map key wins and the value or map key is never changed. // This means that the first file to set CurrentContext will have its context preserved. It also means