Merge pull request #6680 from deads2k/deads-change-kubeconfig-chain

change kubeconfig loading chain
This commit is contained in:
Jeff Lowdermilk 2015-04-16 13:57:31 -07:00
commit 97e4549170
50 changed files with 446 additions and 409 deletions

View File

@ -776,12 +776,9 @@ _kubectl_config()
flags_with_completion=() flags_with_completion=()
flags_completion=() flags_completion=()
flags+=("--envvar")
flags+=("--global")
flags+=("--help") flags+=("--help")
flags+=("-h") flags+=("-h")
flags+=("--kubeconfig=") flags+=("--kubeconfig=")
flags+=("--local")
must_have_one_flag=() must_have_one_flag=()
must_have_one_noun=() must_have_one_noun=()

View File

@ -84,15 +84,20 @@ If the contents of the kubernetes_auth file conflict with entries in .kubeconfig
## Loading and merging rules ## 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: 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. 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. 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. 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 Otherwise, use HomeDirectoryLocation (~/.kube/config) with no merging.
1. HomeDirectoryLocation = ~/.kube/.kubeconfig
1. Determine the context to use based on the first hit in this chain 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. command line argument - the value of the `context` command line option
1. current-context from the merged kubeconfig file 1. current-context from the merged kubeconfig file

View File

@ -66,4 +66,4 @@ kubectl
* [kubectl update](kubectl_update.md) - Update a resource by filename or stdin. * [kubectl update](kubectl_update.md) - Update a resource by filename or stdin.
* [kubectl version](kubectl_version.md) - Print the client and server version information. * [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

View File

@ -50,4 +50,4 @@ kubectl api-versions
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -50,4 +50,4 @@ kubectl cluster-info
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -7,6 +7,12 @@ config modifies kubeconfig files
config modifies kubeconfig files using subcommands like "kubectl config set current-context my-context" 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 kubectl config SUBCOMMAND
``` ```
@ -14,11 +20,8 @@ kubectl config SUBCOMMAND
### Options ### Options
``` ```
--envvar=false: use the kubeconfig from $KUBECONFIG
--global=false: use the kubeconfig from /home/username/.kube/.kubeconfig
-h, --help=false: help for config -h, --help=false: help for config
--kubeconfig="": use a particular kubeconfig file --kubeconfig="": use a particular kubeconfig file
--local=false: use the kubeconfig in the current directory
``` ```
### Options inherrited from parent commands ### 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 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. * [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

View File

@ -45,10 +45,7 @@ $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true
--client-key="": Path to a client key file for TLS. --client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use --cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context 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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --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 ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -38,11 +38,8 @@ $ kubectl config set-context gce --user=cluster-admin
--client-certificate="": Path to a client key file for TLS. --client-certificate="": Path to a client key file for TLS.
--client-key="": 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 --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. --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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --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 ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -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. --certificate-authority="": Path to a cert. file for the certificate authority.
--cluster="": The name of the kubeconfig cluster to use --cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context 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. --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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --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 ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -30,11 +30,8 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE
--client-key="": Path to a client key file for TLS. --client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use --cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context 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. --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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --log_flush_frequency=5s: Maximum number of seconds between log flushes
@ -55,4 +52,4 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -29,11 +29,8 @@ kubectl config unset PROPERTY_NAME
--client-key="": Path to a client key file for TLS. --client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use --cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context 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. --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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --log_flush_frequency=5s: Maximum number of seconds between log flushes
@ -54,4 +51,4 @@ kubectl config unset PROPERTY_NAME
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -28,11 +28,8 @@ kubectl config use-context CONTEXT_NAME
--client-key="": Path to a client key file for TLS. --client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use --cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context 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. --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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --log_flush_frequency=5s: Maximum number of seconds between log flushes
@ -53,4 +50,4 @@ kubectl config use-context CONTEXT_NAME
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -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. --client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use --cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context 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. --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 --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_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_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes --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 ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [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

View File

@ -63,4 +63,4 @@ $ cat pod.json | kubectl create -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -81,4 +81,4 @@ $ kubectl delete pods --all
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -53,4 +53,4 @@ kubectl describe RESOURCE ID
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -64,4 +64,4 @@ $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -82,4 +82,4 @@ $ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -85,4 +85,4 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -81,4 +81,4 @@ $ kubectl label pods foo bar-
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -62,4 +62,4 @@ $ kubectl log -f 123456-7890 ruby-container
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -53,4 +53,4 @@ kubectl namespace [namespace]
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -68,4 +68,4 @@ $ kubectl port-forward -p mypod 0:5000
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -65,4 +65,4 @@ $ kubectl proxy --api-prefix=k8s-api
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -68,4 +68,4 @@ $ kubectl resize --current-replicas=2 --replicas=3 replicationcontrollers foo
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -68,4 +68,4 @@ $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -78,4 +78,4 @@ $ kubectl run-container nginx --image=nginx --overrides='{ "apiVersion": "v1beta
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -72,4 +72,4 @@ $ kubectl stop -f path/to/resources
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -67,4 +67,4 @@ $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState":
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -51,4 +51,4 @@ kubectl version
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [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

View File

@ -68,22 +68,10 @@ Specifying a name that already exists will merge new fields on top of existing v
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-kubeconfig\fP="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -64,14 +64,6 @@ Specifying a name that already exists will merge new fields on top of existing v
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false \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. 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="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -87,14 +87,6 @@ Bearer token and basic auth are mutually exclusive.
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false \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. 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="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -57,14 +57,6 @@ PROPERTY\_VALUE is the new value you wish to set.
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false \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. 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="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -56,14 +56,6 @@ PROPERTY\_NAME is a dot delimited name where each token represents either a attr
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false \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. 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="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -55,14 +55,6 @@ Sets the current\-context in a kubeconfig file
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false \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. 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="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -87,14 +87,6 @@ You can use \-\-output=template \-\-template=TEMPLATE to extract specific values
\fB\-\-context\fP="" \fB\-\-context\fP=""
The name of the kubeconfig context to use 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 .PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false \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. 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="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.PP .PP
\fB\-\-log\_backtrace\_at\fP=:0 \fB\-\-log\_backtrace\_at\fP=:0
when logging hits line file:N, emit a stack trace when logging hits line file:N, emit a stack trace

View File

@ -15,16 +15,14 @@ kubectl config \- config modifies kubeconfig files
.PP .PP
config modifies kubeconfig files using subcommands like "kubectl config set current\-context my\-context" 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 .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 .PP
\fB\-h\fP, \fB\-\-help\fP=false \fB\-h\fP, \fB\-\-help\fP=false
help for config help for config
@ -33,10 +31,6 @@ config modifies kubeconfig files using subcommands like "kubectl config set curr
\fB\-\-kubeconfig\fP="" \fB\-\-kubeconfig\fP=""
use a particular kubeconfig file use a particular kubeconfig file
.PP
\fB\-\-local\fP=false
use the kubeconfig in the current directory
.SH OPTIONS INHERITED FROM PARENT COMMANDS .SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP .PP

View File

@ -18,8 +18,10 @@ package clientcmd
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
@ -33,22 +35,24 @@ import (
const ( const (
RecommendedConfigPathFlag = "kubeconfig" RecommendedConfigPathFlag = "kubeconfig"
RecommendedConfigPathEnvVar = "KUBECONFIG" RecommendedConfigPathEnvVar = "KUBECONFIG"
RecommendedHomeFileName = "/.kube/config"
DefaultEnvVarIndex = 0
DefaultCurrentDirIndex = 1
DefaultHomeDirIndex = 2
) )
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 // 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: // Callers can put the chain together however they want, but we'd recommend:
// [0] = EnvVarPath // EnvVarPathFiles if set (a list of files if set) OR the HomeDirectoryPath
// [1] = CurrentDirectoryPath
// [2] = 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 // 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 { type ClientConfigLoadingRules struct {
ExplicitPath string ExplicitPath string
Precedence []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 // 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. // that a default object that doesn't set this will usually get the behavior it wants.
DoNotResolvePaths bool DoNotResolvePaths bool
@ -57,14 +61,29 @@ type ClientConfigLoadingRules struct {
// NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to // NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to
// use this constructor // use this constructor
func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules { 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{ 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. // Load starts by running the MigrationRules and then
// 1. ExplicitPath // takes the loading rules and returns a Config object based on following rules.
// 2. Precedence slice // 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. // 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. // 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. // 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 // Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder
// and only absolute file paths are returned. // and only absolute file paths are returned.
func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
if err := rules.Migrate(); err != nil {
return nil, err
}
errlist := []error{} errlist := []error{}
kubeConfigFiles := []string{}
// Make sure a file we were explicitly told to use exists // Make sure a file we were explicitly told to use exists
if len(rules.ExplicitPath) > 0 { if len(rules.ExplicitPath) > 0 {
if _, err := os.Stat(rules.ExplicitPath); os.IsNotExist(err) { 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} } else {
kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...) kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...)
}
// first merge all of our maps // first merge all of our maps
mapConfig := clientcmdapi.NewConfig() mapConfig := clientcmdapi.NewConfig()
@ -120,6 +147,53 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
return config, errors.NewAggregate(errlist) 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 { func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) error {
if len(filename) == 0 { if len(filename) == 0 {
// no work to do // no work to do

View File

@ -22,6 +22,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
@ -209,8 +210,7 @@ func TestResolveRelativePaths(t *testing.T) {
WriteToFile(pathResolutionConfig2, configFile2) WriteToFile(pathResolutionConfig2, configFile2)
loadingRules := ClientConfigLoadingRules{ loadingRules := ClientConfigLoadingRules{
ExplicitPath: configFile1, Precedence: []string{configFile1, configFile2},
Precedence: []string{configFile2},
} }
mergedConfig, err := loadingRules.Load() 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("", "") commandLineFile, _ := ioutil.TempFile("", "")
defer os.Remove(commandLineFile.Name()) defer os.Remove(commandLineFile.Name())
envVarFile, _ := ioutil.TempFile("", "") envVarFile, _ := ioutil.TempFile("", "")
@ -299,6 +378,52 @@ func ExampleMergingSomeWithConflict() {
fmt.Printf("Unexpected error: %v", err) 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)) fmt.Printf("%v", string(output))
// Output: // Output:
// apiVersion: v1 // apiVersion: v1
@ -344,8 +469,7 @@ func ExampleMergingEverythingNoConflicts() {
WriteToFile(testConfigDelta, homeDirFile.Name()) WriteToFile(testConfigDelta, homeDirFile.Name())
loadingRules := ClientConfigLoadingRules{ loadingRules := ClientConfigLoadingRules{
ExplicitPath: commandLineFile.Name(), Precedence: []string{commandLineFile.Name(), envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()},
Precedence: []string{envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()},
} }
mergedConfig, err := loadingRules.Load() mergedConfig, err := loadingRules.Load()

View File

@ -78,10 +78,7 @@ func testWriteAuthInfoFile(auth clientauth.Info, filename string) error {
} }
func testBindClientConfig(cmd *cobra.Command) ClientConfig { func testBindClientConfig(cmd *cobra.Command) ClientConfig {
loadingRules := NewDefaultClientConfigLoadingRules() loadingRules := &ClientConfigLoadingRules{}
loadingRules.Precedence[DefaultEnvVarIndex] = ""
loadingRules.Precedence[DefaultCurrentDirIndex] = ""
loadingRules.Precedence[DefaultHomeDirIndex] = ""
cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
overrides := &ConfigOverrides{} overrides := &ConfigOverrides{}

View File

@ -18,10 +18,10 @@ package config
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"path" "path"
"path/filepath"
"reflect" "reflect"
"strconv" "strconv"
@ -33,41 +33,54 @@ import (
) )
type PathOptions struct { type PathOptions struct {
Local bool // GlobalFile is the full path to the file to load as the global (final) option
Global bool
UseEnvVar bool
LocalFile string
GlobalFile string GlobalFile string
EnvVarFile string // EnvVar is the env var name that points to the list of kubeconfig files to load
EnvVar string
EnvVar string // ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
ExplicitFileFlag string ExplicitFileFlag string
// GlobalFileSubpath is an optional value used for displaying help
GlobalFileSubpath string
LoadingRules *clientcmd.ClientConfigLoadingRules 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 { func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command {
if len(pathOptions.ExplicitFileFlag) == 0 { if len(pathOptions.ExplicitFileFlag) == 0 {
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
} }
if len(pathOptions.EnvVar) > 0 {
pathOptions.EnvVarFile = os.Getenv(pathOptions.EnvVar)
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "config SUBCOMMAND", Use: "config SUBCOMMAND",
Short: "config modifies kubeconfig files", 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) { Run: func(cmd *cobra.Command, args []string) {
cmd.Help() cmd.Help()
}, },
} }
// file paths are common to all sub commands // 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.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
cmd.AddCommand(NewCmdConfigView(out, pathOptions)) cmd.AddCommand(NewCmdConfigView(out, pathOptions))
@ -83,13 +96,12 @@ func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command {
func NewDefaultPathOptions() *PathOptions { func NewDefaultPathOptions() *PathOptions {
ret := &PathOptions{ ret := &PathOptions{
LocalFile: ".kubeconfig", GlobalFile: clientcmd.RecommendedHomeFile,
GlobalFile: path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig"), EnvVar: clientcmd.RecommendedConfigPathEnvVar,
EnvVar: clientcmd.RecommendedConfigPathEnvVar,
EnvVarFile: os.Getenv(clientcmd.RecommendedConfigPathEnvVar),
ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag, ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag,
GlobalFileSubpath: clientcmd.RecommendedHomeFileName,
LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(), LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(),
} }
ret.LoadingRules.DoNotResolvePaths = true ret.LoadingRules.DoNotResolvePaths = true
@ -97,125 +109,78 @@ func NewDefaultPathOptions() *PathOptions {
return ret return ret
} }
func (o PathOptions) Validate() error { func (o *PathOptions) GetEnvVarFiles() []string {
if len(o.LoadingRules.ExplicitPath) > 0 { if len(o.EnvVar) == 0 {
if o.Global { return []string{}
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")
}
} }
if o.Global { envVarValue := os.Getenv(o.EnvVar)
if o.Local { if len(envVarValue) == 0 {
return errors.New("cannot specify both --global and --local") return []string{}
}
if o.UseEnvVar {
return errors.New("cannot specify both --global and --envvar")
}
} }
if o.Local { return filepath.SplitList(envVarValue)
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
} }
func (o *PathOptions) getStartingConfig() (*clientcmdapi.Config, error) { func (o *PathOptions) GetLoadingPrecedence() []string {
if err := o.Validate(); err != nil { 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 return nil, err
} }
config := clientcmdapi.NewConfig() return &rawConfig, nil
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
} }
// 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 { func (o *PathOptions) GetDefaultFilename() string {
if o.IsExplicitFile() { if o.IsExplicitFile() {
return o.GetExplicitFile() return o.GetExplicitFile()
} }
if len(o.EnvVarFile) > 0 { if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
return o.EnvVarFile if len(envVarFiles) == 1 {
} return envVarFiles[0]
}
if _, err := os.Stat(o.LocalFile); err == nil { // if any of the envvar files already exists, return it
return o.LocalFile 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 return o.GlobalFile
} }
func (o *PathOptions) IsExplicitFile() bool { func (o *PathOptions) IsExplicitFile() bool {
switch { if len(o.LoadingRules.ExplicitPath) > 0 {
case len(o.LoadingRules.ExplicitPath) > 0 ||
o.Global ||
o.UseEnvVar ||
o.Local:
return true return true
} }
return false return false
} }
func (o *PathOptions) GetExplicitFile() string { func (o *PathOptions) GetExplicitFile() string {
if !o.IsExplicitFile() { return o.LoadingRules.ExplicitPath
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 ""
} }
// ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or // 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 // 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, // (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. // that means that this code will only write into a single file.
func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error { func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) error {
startingConfig, err := o.getStartingConfig() startingConfig, err := configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
@ -237,12 +202,12 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
// nothing to do // nothing to do
case startingConfig.CurrentContext != newConfig.CurrentContext: case startingConfig.CurrentContext != newConfig.CurrentContext:
if err := o.writeCurrentContext(newConfig.CurrentContext); err != nil { if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
return err return err
} }
case !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences): 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 return err
} }
@ -253,7 +218,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
if !reflect.DeepEqual(cluster, startingCluster) || !exists { if !reflect.DeepEqual(cluster, startingCluster) || !exists {
destinationFile := cluster.LocationOfOrigin destinationFile := cluster.LocationOfOrigin
if len(destinationFile) == 0 { if len(destinationFile) == 0 {
destinationFile = o.GetDefaultFilename() destinationFile = configAccess.GetDefaultFilename()
} }
configToWrite := getConfigFromFileOrDie(destinationFile) configToWrite := getConfigFromFileOrDie(destinationFile)
@ -270,7 +235,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
if !reflect.DeepEqual(context, startingContext) || !exists { if !reflect.DeepEqual(context, startingContext) || !exists {
destinationFile := context.LocationOfOrigin destinationFile := context.LocationOfOrigin
if len(destinationFile) == 0 { if len(destinationFile) == 0 {
destinationFile = o.GetDefaultFilename() destinationFile = configAccess.GetDefaultFilename()
} }
configToWrite := getConfigFromFileOrDie(destinationFile) configToWrite := getConfigFromFileOrDie(destinationFile)
@ -287,7 +252,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists { if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
destinationFile := authInfo.LocationOfOrigin destinationFile := authInfo.LocationOfOrigin
if len(destinationFile) == 0 { if len(destinationFile) == 0 {
destinationFile = o.GetDefaultFilename() destinationFile = configAccess.GetDefaultFilename()
} }
configToWrite := getConfigFromFileOrDie(destinationFile) configToWrite := getConfigFromFileOrDie(destinationFile)
@ -303,7 +268,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
if _, exists := newConfig.Clusters[key]; !exists { if _, exists := newConfig.Clusters[key]; !exists {
destinationFile := cluster.LocationOfOrigin destinationFile := cluster.LocationOfOrigin
if len(destinationFile) == 0 { if len(destinationFile) == 0 {
destinationFile = o.GetDefaultFilename() destinationFile = configAccess.GetDefaultFilename()
} }
configToWrite := getConfigFromFileOrDie(destinationFile) configToWrite := getConfigFromFileOrDie(destinationFile)
@ -319,7 +284,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
if _, exists := newConfig.Contexts[key]; !exists { if _, exists := newConfig.Contexts[key]; !exists {
destinationFile := context.LocationOfOrigin destinationFile := context.LocationOfOrigin
if len(destinationFile) == 0 { if len(destinationFile) == 0 {
destinationFile = o.GetDefaultFilename() destinationFile = configAccess.GetDefaultFilename()
} }
configToWrite := getConfigFromFileOrDie(destinationFile) configToWrite := getConfigFromFileOrDie(destinationFile)
@ -335,7 +300,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
if _, exists := newConfig.AuthInfos[key]; !exists { if _, exists := newConfig.AuthInfos[key]; !exists {
destinationFile := authInfo.LocationOfOrigin destinationFile := authInfo.LocationOfOrigin
if len(destinationFile) == 0 { if len(destinationFile) == 0 {
destinationFile = o.GetDefaultFilename() destinationFile = configAccess.GetDefaultFilename()
} }
configToWrite := getConfigFromFileOrDie(destinationFile) 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 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 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 // 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 { func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
if startingConfig, err := o.getStartingConfig(); err != nil { if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
return err return err
} else if startingConfig.CurrentContext == newCurrentContext { } else if startingConfig.CurrentContext == newCurrentContext {
return nil 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 { if len(newCurrentContext) > 0 {
destinationFile := o.GetDefaultFilename() destinationFile := configAccess.GetDefaultFilename()
config := getConfigFromFileOrDie(destinationFile) config := getConfigFromFileOrDie(destinationFile)
config.CurrentContext = newCurrentContext config.CurrentContext = newCurrentContext
@ -375,46 +351,34 @@ func (o *PathOptions) writeCurrentContext(newCurrentContext string) error {
return nil return nil
} }
if o.IsExplicitFile() { // 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
file := o.GetExplicitFile() for _, file := range configAccess.GetLoadingPrecedence() {
currConfig := getConfigFromFileOrDie(file) if _, err := os.Stat(file); err == nil {
currConfig.CurrentContext = newCurrentContext currConfig := getConfigFromFileOrDie(file)
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
return err
}
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) return nil
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 errors.New("no config found to write context")
} }
func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error { func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
if startingConfig, err := o.getStartingConfig(); err != nil { if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
return err return err
} else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) { } else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
return nil return nil
} }
if o.IsExplicitFile() { if configAccess.IsExplicitFile() {
file := o.GetExplicitFile() file := configAccess.GetExplicitFile()
currConfig := getConfigFromFileOrDie(file) currConfig := getConfigFromFileOrDie(file)
currConfig.Preferences = newPrefs currConfig.Preferences = newPrefs
if err := clientcmd.WriteToFile(*currConfig, file); err != nil { if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
@ -424,11 +388,7 @@ func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error
return nil return nil
} }
filesToCheck := make([]string, 0, len(o.LoadingRules.Precedence)+1) for _, file := range configAccess.GetLoadingPrecedence() {
filesToCheck = append(filesToCheck, o.LoadingRules.ExplicitPath)
filesToCheck = append(filesToCheck, o.LoadingRules.Precedence...)
for _, file := range filesToCheck {
currConfig := getConfigFromFileOrDie(file) currConfig := getConfigFromFileOrDie(file)
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) { 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 // 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

View File

@ -31,7 +31,7 @@ import (
) )
type createAuthInfoOptions struct { type createAuthInfoOptions struct {
pathOptions *PathOptions configAccess ConfigAccess
name string name string
authPath util.StringFlag authPath util.StringFlag
clientCertificate 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 // Embed client certificate data in the "cluster-admin" entry
$ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true` $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true`
func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *PathOptions) *cobra.Command { func NewCmdConfigSetAuthInfo(out io.Writer, configAccess ConfigAccess) *cobra.Command {
options := &createAuthInfoOptions{pathOptions: pathOptions} options := &createAuthInfoOptions{configAccess: configAccess}
cmd := &cobra.Command{ 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), 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 return err
} }
config, err := o.pathOptions.getStartingConfig() config, err := o.configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
@ -112,7 +112,7 @@ func (o createAuthInfoOptions) run() error {
authInfo := o.modifyAuthInfo(config.AuthInfos[o.name]) authInfo := o.modifyAuthInfo(config.AuthInfos[o.name])
config.AuthInfos[o.name] = authInfo config.AuthInfos[o.name] = authInfo
if err := o.pathOptions.ModifyConfig(*config); err != nil { if err := ModifyConfig(o.configAccess, *config); err != nil {
return err return err
} }
@ -225,5 +225,5 @@ func (o createAuthInfoOptions) validate() error {
} }
} }
return o.pathOptions.Validate() return nil
} }

View File

@ -30,7 +30,7 @@ import (
) )
type createClusterOptions struct { type createClusterOptions struct {
pathOptions *PathOptions configAccess ConfigAccess
name string name string
server util.StringFlag server util.StringFlag
apiVersion 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` $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true`
) )
func NewCmdConfigSetCluster(out io.Writer, pathOptions *PathOptions) *cobra.Command { func NewCmdConfigSetCluster(out io.Writer, configAccess ConfigAccess) *cobra.Command {
options := &createClusterOptions{pathOptions: pathOptions} options := &createClusterOptions{configAccess: configAccess}
cmd := &cobra.Command{ 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), 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 return err
} }
config, err := o.pathOptions.getStartingConfig() config, err := o.configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
@ -97,7 +97,7 @@ func (o createClusterOptions) run() error {
cluster := o.modifyCluster(config.Clusters[o.name]) cluster := o.modifyCluster(config.Clusters[o.name])
config.Clusters[o.name] = cluster config.Clusters[o.name] = cluster
if err := o.pathOptions.ModifyConfig(*config); err != nil { if err := ModifyConfig(o.configAccess, *config); err != nil {
return err return err
} }
@ -169,5 +169,5 @@ func (o createClusterOptions) validate() error {
} }
} }
return o.pathOptions.Validate() return nil
} }

View File

@ -29,11 +29,11 @@ import (
) )
type createContextOptions struct { type createContextOptions struct {
pathOptions *PathOptions configAccess ConfigAccess
name string name string
cluster util.StringFlag cluster util.StringFlag
authInfo util.StringFlag authInfo util.StringFlag
namespace util.StringFlag namespace util.StringFlag
} }
const ( 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` $ kubectl config set-context gce --user=cluster-admin`
) )
func NewCmdConfigSetContext(out io.Writer, pathOptions *PathOptions) *cobra.Command { func NewCmdConfigSetContext(out io.Writer, configAccess ConfigAccess) *cobra.Command {
options := &createContextOptions{pathOptions: pathOptions} options := &createContextOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: fmt.Sprintf("set-context NAME [--%v=cluster_nickname] [--%v=user_nickname] [--%v=namespace]", clientcmd.FlagClusterName, clientcmd.FlagAuthInfoName, clientcmd.FlagNamespace), 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 return err
} }
config, err := o.pathOptions.getStartingConfig() config, err := o.configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
@ -84,7 +84,7 @@ func (o createContextOptions) run() error {
context := o.modifyContext(config.Contexts[o.name]) context := o.modifyContext(config.Contexts[o.name])
config.Contexts[o.name] = context config.Contexts[o.name] = context
if err := o.pathOptions.ModifyConfig(*config); err != nil { if err := ModifyConfig(o.configAccess, *config); err != nil {
return err return err
} }
@ -123,5 +123,5 @@ func (o createContextOptions) validate() error {
return errors.New("You must specify a non-empty context name") return errors.New("You must specify a non-empty context name")
} }
return o.pathOptions.Validate() return nil
} }

View File

@ -32,7 +32,7 @@ const (
) )
type setOptions struct { type setOptions struct {
pathOptions *PathOptions configAccess ConfigAccess
propertyName string propertyName string
propertyValue 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_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.` PROPERTY_VALUE is the new value you wish to set.`
func NewCmdConfigSet(out io.Writer, pathOptions *PathOptions) *cobra.Command { func NewCmdConfigSet(out io.Writer, configAccess ConfigAccess) *cobra.Command {
options := &setOptions{pathOptions: pathOptions} options := &setOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "set PROPERTY_NAME PROPERTY_VALUE", Use: "set PROPERTY_NAME PROPERTY_VALUE",
@ -69,7 +69,7 @@ func (o setOptions) run() error {
return err return err
} }
config, err := o.pathOptions.getStartingConfig() config, err := o.configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
@ -82,7 +82,7 @@ func (o setOptions) run() error {
return err return err
} }
if err := o.pathOptions.ModifyConfig(*config); err != nil { if err := ModifyConfig(o.configAccess, *config); err != nil {
return err return err
} }
@ -110,7 +110,7 @@ func (o setOptions) validate() error {
return errors.New("You must specify a property") 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 { func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue string, unset bool) error {

View File

@ -26,15 +26,15 @@ import (
) )
type unsetOptions struct { type unsetOptions struct {
pathOptions *PathOptions configAccess ConfigAccess
propertyName string propertyName string
} }
const unset_long = `Unsets an individual value in a kubeconfig file 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.` 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 { func NewCmdConfigUnset(out io.Writer, configAccess ConfigAccess) *cobra.Command {
options := &unsetOptions{pathOptions: pathOptions} options := &unsetOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "unset PROPERTY_NAME", Use: "unset PROPERTY_NAME",
@ -61,7 +61,7 @@ func (o unsetOptions) run() error {
return err return err
} }
config, err := o.pathOptions.getStartingConfig() config, err := o.configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
@ -75,7 +75,7 @@ func (o unsetOptions) run() error {
return err return err
} }
if err := o.pathOptions.ModifyConfig(*config); err != nil { if err := ModifyConfig(o.configAccess, *config); err != nil {
return err return err
} }
@ -98,5 +98,5 @@ func (o unsetOptions) validate() error {
return errors.New("You must specify a property") return errors.New("You must specify a property")
} }
return o.pathOptions.Validate() return nil
} }

View File

@ -25,12 +25,12 @@ import (
) )
type useContextOptions struct { type useContextOptions struct {
pathOptions *PathOptions configAccess ConfigAccess
contextName string contextName string
} }
func NewCmdConfigUseContext(out io.Writer, pathOptions *PathOptions) *cobra.Command { func NewCmdConfigUseContext(out io.Writer, configAccess ConfigAccess) *cobra.Command {
options := &useContextOptions{pathOptions: pathOptions} options := &useContextOptions{configAccess: configAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "use-context CONTEXT_NAME", Use: "use-context CONTEXT_NAME",
@ -57,14 +57,14 @@ func (o useContextOptions) run() error {
return err return err
} }
config, err := o.pathOptions.getStartingConfig() config, err := o.configAccess.GetStartingConfig()
if err != nil { if err != nil {
return err return err
} }
config.CurrentContext = o.contextName config.CurrentContext = o.contextName
if err := o.pathOptions.ModifyConfig(*config); err != nil { if err := ModifyConfig(o.configAccess, *config); err != nil {
return err return err
} }
@ -87,5 +87,5 @@ func (o useContextOptions) validate() error {
return errors.New("You must specify a current-context") return errors.New("You must specify a current-context")
} }
return o.pathOptions.Validate() return nil
} }

View File

@ -32,10 +32,10 @@ import (
) )
type ViewOptions struct { type ViewOptions struct {
PathOptions *PathOptions ConfigAccess ConfigAccess
Merge util.BoolFlag Merge util.BoolFlag
Flatten bool Flatten bool
Minify bool Minify bool
} }
const ( 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}}'` $ 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 { func NewCmdConfigView(out io.Writer, ConfigAccess ConfigAccess) *cobra.Command {
options := &ViewOptions{PathOptions: PathOptions} options := &ViewOptions{ConfigAccess: ConfigAccess}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "view", Use: "view",
@ -116,7 +116,7 @@ func (o ViewOptions) Run(out io.Writer, printer kubectl.ResourcePrinter) error {
func (o *ViewOptions) Complete() bool { 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 --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() { if !o.Merge.Provided() {
o.Merge.Set("false") o.Merge.Set("false")
} }
@ -136,32 +136,20 @@ func (o ViewOptions) loadConfig() (*clientcmdapi.Config, error) {
} }
func (o ViewOptions) Validate() 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 // 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) { func (o *ViewOptions) getStartingConfig() (*clientcmdapi.Config, error) {
switch { switch {
case !o.Merge.Value(): case !o.Merge.Value():
switch { return clientcmd.LoadFromFile(o.ConfigAccess.GetExplicitFile())
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")
}
default: default:
return o.PathOptions.getStartingConfig() return o.ConfigAccess.GetStartingConfig()
} }
} }

View File

@ -286,11 +286,11 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
// DefaultClientConfig creates a clientcmd.ClientConfig with the following hierarchy: // 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. 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. 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 // 1. CommandLineLocation - this parsed from the command line, so it must be late bound. If you specify this,
// 2. EnvVarLocation // then no other kubeconfig files are merged. This file must exist.
// 3. CurrentDirectoryLocation // 2. If $KUBECONFIG is set, then it is treated as a list of files that should be merged.
// 4. HomeDirectoryLocation // 3. HomeDirectoryLocation
// Empty filenames are ignored. Files with non-deserializable content produced errors. // 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. // 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 // This means that the first file to set CurrentContext will have its context preserved. It also means