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

change kubeconfig loading chain
pull/6/head
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_completion=()
flags+=("--envvar")
flags+=("--global")
flags+=("--help")
flags+=("-h")
flags+=("--kubeconfig=")
flags+=("--local")
must_have_one_flag=()
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
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

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.
--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

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-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

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.
--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

View File

@ -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

View File

@ -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

View File

@ -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

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.
--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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,18 +94,26 @@ 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}
} else {
kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...)
}
// first merge all of our maps
mapConfig := clientcmdapi.NewConfig()
for _, file := range kubeConfigFiles {
@ -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

View File

@ -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()

View File

@ -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{}

View File

@ -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 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"),
GlobalFile: clientcmd.RecommendedHomeFile,
EnvVar: clientcmd.RecommendedConfigPathEnvVar,
EnvVarFile: os.Getenv(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")
}
return filepath.SplitList(envVarValue)
}
if o.UseEnvVar {
if len(o.EnvVarFile) == 0 {
return fmt.Errorf("environment variable %v does not have a value", o.EnvVar)
func (o *PathOptions) GetLoadingPrecedence() []string {
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
return envVarFiles
}
return []string{o.GlobalFile}
}
return nil
}
func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
// don't mutate the original
loadingRules := *o.LoadingRules
loadingRules.Precedence = o.GetLoadingPrecedence()
func (o *PathOptions) getStartingConfig() (*clientcmdapi.Config, error) {
if err := o.Validate(); 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{})
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 = &rawConfig
return &rawConfig, nil
}
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 {
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 ""
}
// 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,22 +351,9 @@ 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
}
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 {
// 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)
if len(currConfig.CurrentContext) > 0 {
@ -402,19 +365,20 @@ func (o *PathOptions) writeCurrentContext(newCurrentContext string) error {
return nil
}
}
return nil
}
func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error {
if startingConfig, err := o.getStartingConfig(); err != nil {
return errors.New("no config found to write context")
}
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

View File

@ -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
}

View File

@ -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
}

View File

@ -29,7 +29,7 @@ import (
)
type createContextOptions struct {
pathOptions *PathOptions
configAccess ConfigAccess
name string
cluster util.StringFlag
authInfo util.StringFlag
@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -25,12 +25,12 @@ import (
)
type useContextOptions struct {
pathOptions *PathOptions
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
}

View File

@ -32,7 +32,7 @@ import (
)
type ViewOptions struct {
PathOptions *PathOptions
ConfigAccess ConfigAccess
Merge util.BoolFlag
Flatten bool
Minify bool
@ -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)
return clientcmd.LoadFromFile(o.ConfigAccess.GetExplicitFile())
default:
return nil, errors.New("if Merge==false a precise file must to specified")
}
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:
// 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