mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #4517 from liggitt/kubeconfig_cert_data
Let .kubeconfig be a single-source config for API clients
This commit is contained in:
commit
703b642886
@ -32,10 +32,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -73,10 +75,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -140,10 +144,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -179,10 +185,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -227,10 +235,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -279,10 +289,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -345,10 +357,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -366,7 +380,7 @@ Usage:
|
|||||||
Available Commands:
|
Available Commands:
|
||||||
view displays merged .kubeconfig settings or a specified .kubeconfig file.
|
view displays merged .kubeconfig settings or a specified .kubeconfig file.
|
||||||
set-cluster name [--server=server] [--certificate-authority=path/to/certficate/authority] [--api-version=apiversion] [--insecure-skip-tls-verify=true] Sets a cluster entry in .kubeconfig
|
set-cluster name [--server=server] [--certificate-authority=path/to/certficate/authority] [--api-version=apiversion] [--insecure-skip-tls-verify=true] Sets a cluster entry in .kubeconfig
|
||||||
set-credentials name [--auth-path=path/to/auth/file] [--client-certificate=path/to/certficate/file] [--client-key=path/to/key/file] [--token=bearer_token_string] Sets a user entry in .kubeconfig
|
set-credentials name [--auth-path=authfile] [--client-certificate=certfile] [--client-key=keyfile] [--token=bearer_token] [--username=basic_user] [--password=basic_password] Sets a user entry in .kubeconfig
|
||||||
set-context name [--cluster=cluster-nickname] [--user=user-nickname] [--namespace=namespace] Sets a context entry in .kubeconfig
|
set-context name [--cluster=cluster-nickname] [--user=user-nickname] [--namespace=namespace] Sets a context entry in .kubeconfig
|
||||||
set property-name property-value Sets an individual value in a .kubeconfig file
|
set property-name property-value Sets an individual value in a .kubeconfig file
|
||||||
unset property-name Unsets an individual value in a .kubeconfig file
|
unset property-name Unsets an individual value in a .kubeconfig file
|
||||||
@ -394,10 +408,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -464,10 +480,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -508,10 +526,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -520,15 +540,28 @@ Global Flags:
|
|||||||
|
|
||||||
#### config set-credentials
|
#### config set-credentials
|
||||||
Sets a user entry in .kubeconfig
|
Sets a user entry in .kubeconfig
|
||||||
Specifying a name that already exists will merge new fields on top of existing values for those fields.
|
|
||||||
e.g.
|
Specifying a name that already exists will merge new fields on top of existing
|
||||||
kubectl config set-credentials cluster-admin --client-key=~/.kube/cluster-admin/.kubecfg.key
|
values. For example, the following only sets the "client-key" field on the
|
||||||
only sets the client-key field on the cluster-admin user entry without touching other values.
|
"cluster-admin" entry, without touching other values:
|
||||||
|
|
||||||
|
set-credentials cluster-admin --client-key=~/.kube/admin.key
|
||||||
|
|
||||||
|
Client-certificate flags:
|
||||||
|
--client-certificate=certfile --client-key=keyfile
|
||||||
|
|
||||||
|
Bearer token flags:
|
||||||
|
--token=bearer_token
|
||||||
|
|
||||||
|
Basic auth flags:
|
||||||
|
--username=basic_user --password=basic_password
|
||||||
|
|
||||||
|
Bearer token and basic auth are mutually exclusive.
|
||||||
|
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
```
|
```
|
||||||
kubectl config set-credentials name [--auth-path=path/to/auth/file] [--client-certificate=path/to/certficate/file] [--client-key=path/to/key/file] [--token=bearer_token_string] [flags]
|
kubectl config set-credentials name [--auth-path=authfile] [--client-certificate=certfile] [--client-key=keyfile] [--token=bearer_token] [--username=basic_user] [--password=basic_password] [flags]
|
||||||
|
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
@ -552,10 +585,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -596,10 +631,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -640,10 +677,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -682,10 +721,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -721,10 +762,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -760,10 +803,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -806,10 +851,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -859,10 +906,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -912,10 +961,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -975,10 +1026,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -1027,10 +1080,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -1089,10 +1144,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
@ -1149,10 +1206,12 @@ Global Flags:
|
|||||||
--logtostderr=true: log to standard error instead of files
|
--logtostderr=true: log to standard error instead of files
|
||||||
--match-server-version=false: Require server version to match client version
|
--match-server-version=false: Require server version to match client version
|
||||||
--namespace="": If present, the namespace scope for this CLI request.
|
--namespace="": If present, the namespace scope for this CLI request.
|
||||||
|
--password="": Password for basic authentication to the API server.
|
||||||
-s, --server="": The address and port of the Kubernetes API server
|
-s, --server="": The address and port of the Kubernetes API server
|
||||||
--stderrthreshold=2: logs at or above this threshold go to stderr
|
--stderrthreshold=2: logs at or above this threshold go to stderr
|
||||||
--token="": Bearer token for authentication to the API server.
|
--token="": Bearer token for authentication to the API server.
|
||||||
--user="": The name of the kubeconfig user to use
|
--user="": The name of the kubeconfig user to use
|
||||||
|
--username="": Username for basic authentication to the API server.
|
||||||
--v=0: log level for V logs
|
--v=0: log level for V logs
|
||||||
--validate=false: If true, use a schema to validate the input before sending it
|
--validate=false: If true, use a schema to validate the input before sending it
|
||||||
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
|
@ -57,6 +57,8 @@ type Cluster struct {
|
|||||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||||
|
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||||
|
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||||
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
||||||
}
|
}
|
||||||
@ -67,10 +69,18 @@ type AuthInfo struct {
|
|||||||
AuthPath string `json:"auth-path,omitempty"`
|
AuthPath string `json:"auth-path,omitempty"`
|
||||||
// ClientCertificate is the path to a client cert file for TLS.
|
// ClientCertificate is the path to a client cert file for TLS.
|
||||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||||
|
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||||
|
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||||
// ClientKey is the path to a client key file for TLS.
|
// ClientKey is the path to a client key file for TLS.
|
||||||
ClientKey string `json:"client-key,omitempty"`
|
ClientKey string `json:"client-key,omitempty"`
|
||||||
|
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||||
|
ClientKeyData []byte `json:"client-key-data,omitempty"`
|
||||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
|
// Username is the username for basic authentication to the kubernetes cluster.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
// Password is the password for basic authentication to the kubernetes cluster.
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||||
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,8 @@ type Cluster struct {
|
|||||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||||
|
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||||
|
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||||
}
|
}
|
||||||
@ -67,10 +69,18 @@ type AuthInfo struct {
|
|||||||
AuthPath string `json:"auth-path,omitempty"`
|
AuthPath string `json:"auth-path,omitempty"`
|
||||||
// ClientCertificate is the path to a client cert file for TLS.
|
// ClientCertificate is the path to a client cert file for TLS.
|
||||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||||
|
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||||
|
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||||
// ClientKey is the path to a client key file for TLS.
|
// ClientKey is the path to a client key file for TLS.
|
||||||
ClientKey string `json:"client-key,omitempty"`
|
ClientKey string `json:"client-key,omitempty"`
|
||||||
|
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||||
|
ClientKeyData []byte `json:"client-key-data,omitempty"`
|
||||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
|
// Username is the username for basic authentication to the kubernetes cluster.
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
// Password is the password for basic authentication to the kubernetes cluster.
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
|
|||||||
// configClusterInfo holds the information identify the server provided by .kubeconfig
|
// configClusterInfo holds the information identify the server provided by .kubeconfig
|
||||||
configClientConfig := &client.Config{}
|
configClientConfig := &client.Config{}
|
||||||
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
||||||
|
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
||||||
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
||||||
mergo.Merge(mergedConfig, configClientConfig)
|
mergo.Merge(mergedConfig, configClientConfig)
|
||||||
|
|
||||||
@ -169,9 +170,15 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa
|
|||||||
if len(configAuthInfo.Token) > 0 {
|
if len(configAuthInfo.Token) > 0 {
|
||||||
mergedConfig.BearerToken = configAuthInfo.Token
|
mergedConfig.BearerToken = configAuthInfo.Token
|
||||||
}
|
}
|
||||||
if len(configAuthInfo.ClientCertificate) > 0 {
|
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||||
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
||||||
|
mergedConfig.CertData = configAuthInfo.ClientCertificateData
|
||||||
mergedConfig.KeyFile = configAuthInfo.ClientKey
|
mergedConfig.KeyFile = configAuthInfo.ClientKey
|
||||||
|
mergedConfig.KeyData = configAuthInfo.ClientKeyData
|
||||||
|
}
|
||||||
|
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
||||||
|
mergedConfig.Username = configAuthInfo.Username
|
||||||
|
mergedConfig.Password = configAuthInfo.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there isn't sufficient information to authenticate the user to the server, merge in ~/.kubernetes_auth.
|
// if there isn't sufficient information to authenticate the user to the server, merge in ~/.kubernetes_auth.
|
||||||
@ -228,7 +235,7 @@ func makeServerIdentificationConfig(info clientauth.Info) client.Config {
|
|||||||
|
|
||||||
func canIdentifyUser(config client.Config) bool {
|
func canIdentifyUser(config client.Config) bool {
|
||||||
return len(config.Username) > 0 ||
|
return len(config.Username) > 0 ||
|
||||||
len(config.CertFile) > 0 ||
|
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
||||||
len(config.BearerToken) > 0
|
len(config.BearerToken) > 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,71 @@ func TestMergeContext(t *testing.T) {
|
|||||||
matchStringArg(namespace, actual, t)
|
matchStringArg(namespace, actual, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCertificateData(t *testing.T) {
|
||||||
|
caData := []byte("ca-data")
|
||||||
|
certData := []byte("cert-data")
|
||||||
|
keyData := []byte("key-data")
|
||||||
|
|
||||||
|
config := clientcmdapi.NewConfig()
|
||||||
|
config.Clusters["clean"] = clientcmdapi.Cluster{
|
||||||
|
Server: "https://localhost:8443",
|
||||||
|
APIVersion: latest.Version,
|
||||||
|
CertificateAuthorityData: caData,
|
||||||
|
}
|
||||||
|
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||||
|
ClientCertificateData: certData,
|
||||||
|
ClientKeyData: keyData,
|
||||||
|
}
|
||||||
|
config.Contexts["clean"] = clientcmdapi.Context{
|
||||||
|
Cluster: "clean",
|
||||||
|
AuthInfo: "clean",
|
||||||
|
}
|
||||||
|
config.CurrentContext = "clean"
|
||||||
|
|
||||||
|
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
|
||||||
|
|
||||||
|
clientConfig, err := clientBuilder.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure cert data gets into config (will override file paths)
|
||||||
|
matchByteArg(caData, clientConfig.TLSClientConfig.CAData, t)
|
||||||
|
matchByteArg(certData, clientConfig.TLSClientConfig.CertData, t)
|
||||||
|
matchByteArg(keyData, clientConfig.TLSClientConfig.KeyData, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthData(t *testing.T) {
|
||||||
|
username := "myuser"
|
||||||
|
password := "mypass"
|
||||||
|
|
||||||
|
config := clientcmdapi.NewConfig()
|
||||||
|
config.Clusters["clean"] = clientcmdapi.Cluster{
|
||||||
|
Server: "https://localhost:8443",
|
||||||
|
APIVersion: latest.Version,
|
||||||
|
}
|
||||||
|
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
config.Contexts["clean"] = clientcmdapi.Context{
|
||||||
|
Cluster: "clean",
|
||||||
|
AuthInfo: "clean",
|
||||||
|
}
|
||||||
|
config.CurrentContext = "clean"
|
||||||
|
|
||||||
|
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
|
||||||
|
|
||||||
|
clientConfig, err := clientBuilder.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure basic auth data gets into config
|
||||||
|
matchStringArg(username, clientConfig.Username, t)
|
||||||
|
matchStringArg(password, clientConfig.Password, t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateClean(t *testing.T) {
|
func TestCreateClean(t *testing.T) {
|
||||||
config := createValidTestConfig()
|
config := createValidTestConfig()
|
||||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
|
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})
|
||||||
@ -123,3 +188,9 @@ func matchStringArg(expected, got string, t *testing.T) {
|
|||||||
t.Errorf("Expected %v, got %v", expected, got)
|
t.Errorf("Expected %v, got %v", expected, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchByteArg(expected, got []byte, t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(expected, got) {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -173,36 +173,46 @@ func resolveLocalPath(startingDir, path string) string {
|
|||||||
|
|
||||||
// LoadFromFile takes a filename and deserializes the contents into Config object
|
// LoadFromFile takes a filename and deserializes the contents into Config object
|
||||||
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
|
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||||
config := &clientcmdapi.Config{}
|
|
||||||
|
|
||||||
kubeconfigBytes, err := ioutil.ReadFile(filename)
|
kubeconfigBytes, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return Load(kubeconfigBytes)
|
||||||
if err := clientcmdlatest.Codec.DecodeInto(kubeconfigBytes, config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load takes a byte slice and deserializes the contents into Config object.
|
||||||
|
// Encapsulates deserialization without assuming the source is a file.
|
||||||
|
func Load(data []byte) (*clientcmdapi.Config, error) {
|
||||||
|
config := &clientcmdapi.Config{}
|
||||||
|
if err := clientcmdlatest.Codec.DecodeInto(data, config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToFile serializes the config to yaml and writes it out to a file. If no present, it creates the file with 0644. If it is present
|
// WriteToFile serializes the config to yaml and writes it out to a file. If not present, it creates the file with the mode 0600. If it is present
|
||||||
// it stomps the contents
|
// it stomps the contents
|
||||||
func WriteToFile(config clientcmdapi.Config, filename string) error {
|
func WriteToFile(config clientcmdapi.Config, filename string) error {
|
||||||
json, err := clientcmdlatest.Codec.Encode(&config)
|
content, err := Write(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := ioutil.WriteFile(filename, content, 0600); err != nil {
|
||||||
content, err := yaml.JSONToYAML(json)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(filename, content, 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write serializes the config to yaml.
|
||||||
|
// Encapsulates serialization without assuming the destination is a file.
|
||||||
|
func Write(config clientcmdapi.Config) ([]byte, error) {
|
||||||
|
json, err := clientcmdlatest.Codec.Encode(&config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
content, err := yaml.JSONToYAML(json)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
@ -47,6 +47,8 @@ type AuthOverrideFlags struct {
|
|||||||
ClientCertificate string
|
ClientCertificate string
|
||||||
ClientKey string
|
ClientKey string
|
||||||
Token string
|
Token string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextOverrideFlags holds the flag names to be used for binding command line flags for Cluster objects
|
// ContextOverrideFlags holds the flag names to be used for binding command line flags for Cluster objects
|
||||||
@ -80,6 +82,8 @@ const (
|
|||||||
FlagKeyFile = "client-key"
|
FlagKeyFile = "client-key"
|
||||||
FlagCAFile = "certificate-authority"
|
FlagCAFile = "certificate-authority"
|
||||||
FlagBearerToken = "token"
|
FlagBearerToken = "token"
|
||||||
|
FlagUsername = "username"
|
||||||
|
FlagPassword = "password"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
// RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||||
@ -89,6 +93,8 @@ func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags {
|
|||||||
ClientCertificate: prefix + FlagCertFile,
|
ClientCertificate: prefix + FlagCertFile,
|
||||||
ClientKey: prefix + FlagKeyFile,
|
ClientKey: prefix + FlagKeyFile,
|
||||||
Token: prefix + FlagBearerToken,
|
Token: prefix + FlagBearerToken,
|
||||||
|
Username: prefix + FlagUsername,
|
||||||
|
Password: prefix + FlagPassword,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +133,8 @@ func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, fl
|
|||||||
flags.StringVar(&authInfo.ClientCertificate, flagNames.ClientCertificate, "", "Path to a client key file for TLS.")
|
flags.StringVar(&authInfo.ClientCertificate, flagNames.ClientCertificate, "", "Path to a client key file for TLS.")
|
||||||
flags.StringVar(&authInfo.ClientKey, flagNames.ClientKey, "", "Path to a client key file for TLS.")
|
flags.StringVar(&authInfo.ClientKey, flagNames.ClientKey, "", "Path to a client key file for TLS.")
|
||||||
flags.StringVar(&authInfo.Token, flagNames.Token, "", "Bearer token for authentication to the API server.")
|
flags.StringVar(&authInfo.Token, flagNames.Token, "", "Bearer token for authentication to the API server.")
|
||||||
|
flags.StringVar(&authInfo.Username, flagNames.Username, "", "Username for basic authentication to the API server.")
|
||||||
|
flags.StringVar(&authInfo.Password, flagNames.Password, "", "Password for basic authentication to the API server.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindClusterFlags is a convenience method to bind the specified flags to their associated variables
|
// BindClusterFlags is a convenience method to bind the specified flags to their associated variables
|
||||||
|
@ -109,6 +109,10 @@ func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) [
|
|||||||
if len(clusterInfo.Server) == 0 {
|
if len(clusterInfo.Server) == 0 {
|
||||||
validationErrors = append(validationErrors, fmt.Errorf("no server found for %v", clusterName))
|
validationErrors = append(validationErrors, fmt.Errorf("no server found for %v", clusterName))
|
||||||
}
|
}
|
||||||
|
// Make sure CA data and CA file aren't both specified
|
||||||
|
if len(clusterInfo.CertificateAuthority) != 0 && len(clusterInfo.CertificateAuthorityData) != 0 {
|
||||||
|
validationErrors = append(validationErrors, fmt.Errorf("certificate-authority-data and certificate-authority are both specified for %v. certificate-authority-data will override.", clusterName))
|
||||||
|
}
|
||||||
if len(clusterInfo.CertificateAuthority) != 0 {
|
if len(clusterInfo.CertificateAuthority) != 0 {
|
||||||
clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
|
clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
|
||||||
defer clientCertCA.Close()
|
defer clientCertCA.Close()
|
||||||
@ -129,6 +133,9 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err
|
|||||||
if len(authInfo.Token) != 0 {
|
if len(authInfo.Token) != 0 {
|
||||||
methods = append(methods, "token")
|
methods = append(methods, "token")
|
||||||
}
|
}
|
||||||
|
if len(authInfo.Username) != 0 || len(authInfo.Password) != 0 {
|
||||||
|
methods = append(methods, "basicAuth")
|
||||||
|
}
|
||||||
if len(authInfo.AuthPath) != 0 {
|
if len(authInfo.AuthPath) != 0 {
|
||||||
usingAuthPath = true
|
usingAuthPath = true
|
||||||
methods = append(methods, "authFile")
|
methods = append(methods, "authFile")
|
||||||
@ -140,20 +147,36 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err
|
|||||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read auth-path %v for %v due to %v", authInfo.AuthPath, authInfoName, err))
|
validationErrors = append(validationErrors, fmt.Errorf("unable to read auth-path %v for %v due to %v", authInfo.AuthPath, authInfoName, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(authInfo.ClientCertificate) != 0 {
|
|
||||||
methods = append(methods, "clientCert")
|
|
||||||
|
|
||||||
|
if len(authInfo.ClientCertificate) != 0 || len(authInfo.ClientCertificateData) != 0 {
|
||||||
|
// Make sure cert data and file aren't both specified
|
||||||
|
if len(authInfo.ClientCertificate) != 0 && len(authInfo.ClientCertificateData) != 0 {
|
||||||
|
validationErrors = append(validationErrors, fmt.Errorf("client-cert-data and client-cert are both specified for %v. client-cert-data will override.", authInfoName))
|
||||||
|
}
|
||||||
|
// Make sure key data and file aren't both specified
|
||||||
|
if len(authInfo.ClientKey) != 0 && len(authInfo.ClientKeyData) != 0 {
|
||||||
|
validationErrors = append(validationErrors, fmt.Errorf("client-key-data and client-key are both specified for %v. client-key-data will override.", authInfoName))
|
||||||
|
}
|
||||||
|
// Make sure a key is specified
|
||||||
|
if len(authInfo.ClientKey) == 0 && len(authInfo.ClientKeyData) == 0 {
|
||||||
|
validationErrors = append(validationErrors, fmt.Errorf("client-key-data or client-key must be specified for %v to use the clientCert authentication method.", authInfoName))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authInfo.ClientCertificate) != 0 {
|
||||||
clientCertFile, err := os.Open(authInfo.ClientCertificate)
|
clientCertFile, err := os.Open(authInfo.ClientCertificate)
|
||||||
defer clientCertFile.Close()
|
defer clientCertFile.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %v", authInfo.ClientCertificate, authInfoName, err))
|
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %v", authInfo.ClientCertificate, authInfoName, err))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if len(authInfo.ClientKey) != 0 {
|
||||||
clientKeyFile, err := os.Open(authInfo.ClientKey)
|
clientKeyFile, err := os.Open(authInfo.ClientKey)
|
||||||
defer clientKeyFile.Close()
|
defer clientKeyFile.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %v", authInfo.ClientKey, authInfoName, err))
|
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %v", authInfo.ClientKey, authInfoName, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
|
// authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
|
||||||
if (len(methods) > 1) && (!usingAuthPath) {
|
if (len(methods) > 1) && (!usingAuthPath) {
|
||||||
|
@ -244,6 +244,25 @@ func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
|
|||||||
test.testAuthInfo("error", t)
|
test.testAuthInfo("error", t)
|
||||||
test.testConfig(t)
|
test.testConfig(t)
|
||||||
}
|
}
|
||||||
|
func TestValidateCertDataOverridesFiles(t *testing.T) {
|
||||||
|
tempFile, _ := ioutil.TempFile("", "")
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
config := clientcmdapi.NewConfig()
|
||||||
|
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||||
|
ClientCertificate: tempFile.Name(),
|
||||||
|
ClientCertificateData: []byte("certdata"),
|
||||||
|
ClientKey: tempFile.Name(),
|
||||||
|
ClientKeyData: []byte("keydata"),
|
||||||
|
}
|
||||||
|
test := configValidationTest{
|
||||||
|
config: config,
|
||||||
|
expectedErrorSubstring: []string{"client-cert-data and client-cert are both specified", "client-key-data and client-key are both specified"},
|
||||||
|
}
|
||||||
|
|
||||||
|
test.testAuthInfo("clean", t)
|
||||||
|
test.testConfig(t)
|
||||||
|
}
|
||||||
func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
|
func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
|
||||||
tempFile, _ := ioutil.TempFile("", "")
|
tempFile, _ := ioutil.TempFile("", "")
|
||||||
defer os.Remove(tempFile.Name())
|
defer os.Remove(tempFile.Name())
|
||||||
@ -288,6 +307,21 @@ func TestValidateCleanTokenAuthInfo(t *testing.T) {
|
|||||||
test.testConfig(t)
|
test.testConfig(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
|
||||||
|
config := clientcmdapi.NewConfig()
|
||||||
|
config.AuthInfos["error"] = clientcmdapi.AuthInfo{
|
||||||
|
Token: "token",
|
||||||
|
Username: "username",
|
||||||
|
}
|
||||||
|
test := configValidationTest{
|
||||||
|
config: config,
|
||||||
|
expectedErrorSubstring: []string{"more than one authentication method", "token", "basicAuth"},
|
||||||
|
}
|
||||||
|
|
||||||
|
test.testAuthInfo("error", t)
|
||||||
|
test.testConfig(t)
|
||||||
|
}
|
||||||
|
|
||||||
type configValidationTest struct {
|
type configValidationTest struct {
|
||||||
config *clientcmdapi.Config
|
config *clientcmdapi.Config
|
||||||
expectedErrorSubstring []string
|
expectedErrorSubstring []string
|
||||||
|
@ -354,7 +354,9 @@ func IsConfigTransportTLS(config Config) bool {
|
|||||||
func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
func defaultServerUrlFor(config *Config) (*url.URL, error) {
|
||||||
// TODO: move the default to secure when the apiserver supports TLS by default
|
// TODO: move the default to secure when the apiserver supports TLS by default
|
||||||
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
|
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
|
||||||
defaultTLS := config.CertFile != "" || config.Insecure
|
hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
|
||||||
|
hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
|
||||||
|
defaultTLS := hasCA || hasCert || config.Insecure
|
||||||
host := config.Host
|
host := config.Host
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||||
@ -43,6 +44,7 @@ type configCommandTest struct {
|
|||||||
args []string
|
args []string
|
||||||
startingConfig clientcmdapi.Config
|
startingConfig clientcmdapi.Config
|
||||||
expectedConfig clientcmdapi.Config
|
expectedConfig clientcmdapi.Config
|
||||||
|
expectedOutputs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetCurrentContext(t *testing.T) {
|
func TestSetCurrentContext(t *testing.T) {
|
||||||
@ -154,11 +156,10 @@ func TestAdditionalAuth(t *testing.T) {
|
|||||||
expectedConfig := newRedFederalCowHammerConfig()
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
authInfo := clientcmdapi.NewAuthInfo()
|
authInfo := clientcmdapi.NewAuthInfo()
|
||||||
authInfo.AuthPath = "auth-path"
|
authInfo.AuthPath = "auth-path"
|
||||||
authInfo.ClientKey = "client-key"
|
|
||||||
authInfo.Token = "token"
|
authInfo.Token = "token"
|
||||||
expectedConfig.AuthInfos["another-user"] = *authInfo
|
expectedConfig.AuthInfos["another-user"] = *authInfo
|
||||||
test := configCommandTest{
|
test := configCommandTest{
|
||||||
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagAuthPath + "=auth-path", "--" + clientcmd.FlagKeyFile + "=client-key", "--" + clientcmd.FlagBearerToken + "=token"},
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagAuthPath + "=auth-path", "--" + clientcmd.FlagBearerToken + "=token"},
|
||||||
startingConfig: newRedFederalCowHammerConfig(),
|
startingConfig: newRedFederalCowHammerConfig(),
|
||||||
expectedConfig: expectedConfig,
|
expectedConfig: expectedConfig,
|
||||||
}
|
}
|
||||||
@ -166,6 +167,225 @@ func TestAdditionalAuth(t *testing.T) {
|
|||||||
test.run(t)
|
test.run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmptyTokenAndCertAllowed(t *testing.T) {
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
authInfo := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfo.ClientCertificate = "cert-file"
|
||||||
|
expectedConfig.AuthInfos["another-user"] = *authInfo
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert-file", "--" + clientcmd.FlagBearerToken + "="},
|
||||||
|
startingConfig: newRedFederalCowHammerConfig(),
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenAndCertAllowed(t *testing.T) {
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
authInfo := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfo.Token = "token"
|
||||||
|
authInfo.ClientCertificate = "cert-file"
|
||||||
|
expectedConfig.AuthInfos["another-user"] = *authInfo
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert-file", "--" + clientcmd.FlagBearerToken + "=token"},
|
||||||
|
startingConfig: newRedFederalCowHammerConfig(),
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenAndBasicDisallowed(t *testing.T) {
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagBearerToken + "=token"},
|
||||||
|
startingConfig: newRedFederalCowHammerConfig(),
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
expectedOutputs: []string{"--token", "--username"},
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicClearsToken(t *testing.T) {
|
||||||
|
authInfoWithToken := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithToken.Token = "token"
|
||||||
|
|
||||||
|
authInfoWithBasic := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithBasic.Username = "myuser"
|
||||||
|
authInfoWithBasic.Password = "mypass"
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.AuthInfos["another-user"] = *authInfoWithToken
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.AuthInfos["another-user"] = *authInfoWithBasic
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagPassword + "=mypass"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenClearsBasic(t *testing.T) {
|
||||||
|
authInfoWithBasic := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithBasic.Username = "myuser"
|
||||||
|
authInfoWithBasic.Password = "mypass"
|
||||||
|
|
||||||
|
authInfoWithToken := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithToken.Token = "token"
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.AuthInfos["another-user"] = *authInfoWithBasic
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.AuthInfos["another-user"] = *authInfoWithToken
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenLeavesCert(t *testing.T) {
|
||||||
|
authInfoWithCerts := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithCerts.ClientCertificate = "cert"
|
||||||
|
authInfoWithCerts.ClientCertificateData = []byte("certdata")
|
||||||
|
authInfoWithCerts.ClientKey = "key"
|
||||||
|
authInfoWithCerts.ClientKeyData = []byte("keydata")
|
||||||
|
|
||||||
|
authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithTokenAndCerts.Token = "token"
|
||||||
|
authInfoWithTokenAndCerts.ClientCertificate = "cert"
|
||||||
|
authInfoWithTokenAndCerts.ClientCertificateData = []byte("certdata")
|
||||||
|
authInfoWithTokenAndCerts.ClientKey = "key"
|
||||||
|
authInfoWithTokenAndCerts.ClientKeyData = []byte("keydata")
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.AuthInfos["another-user"] = *authInfoWithCerts
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.AuthInfos["another-user"] = *authInfoWithTokenAndCerts
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCertLeavesToken(t *testing.T) {
|
||||||
|
authInfoWithToken := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithToken.Token = "token"
|
||||||
|
|
||||||
|
authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
|
||||||
|
authInfoWithTokenAndCerts.Token = "token"
|
||||||
|
authInfoWithTokenAndCerts.ClientCertificate = "cert"
|
||||||
|
authInfoWithTokenAndCerts.ClientKey = "key"
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.AuthInfos["another-user"] = *authInfoWithToken
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.AuthInfos["another-user"] = *authInfoWithTokenAndCerts
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert", "--" + clientcmd.FlagKeyFile + "=key"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCAClearsInsecure(t *testing.T) {
|
||||||
|
clusterInfoWithInsecure := clientcmdapi.NewCluster()
|
||||||
|
clusterInfoWithInsecure.InsecureSkipTLSVerify = true
|
||||||
|
|
||||||
|
clusterInfoWithCA := clientcmdapi.NewCluster()
|
||||||
|
clusterInfoWithCA.CertificateAuthority = "cafile"
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.Clusters["another-cluster"] = *clusterInfoWithInsecure
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.Clusters["another-cluster"] = *clusterInfoWithCA
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCAClearsCAData(t *testing.T) {
|
||||||
|
clusterInfoWithCAData := clientcmdapi.NewCluster()
|
||||||
|
clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
|
||||||
|
|
||||||
|
clusterInfoWithCA := clientcmdapi.NewCluster()
|
||||||
|
clusterInfoWithCA.CertificateAuthority = "cafile"
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.Clusters["another-cluster"] = *clusterInfoWithCAData
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.Clusters["another-cluster"] = *clusterInfoWithCA
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=false"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsecureClearsCA(t *testing.T) {
|
||||||
|
clusterInfoWithInsecure := clientcmdapi.NewCluster()
|
||||||
|
clusterInfoWithInsecure.InsecureSkipTLSVerify = true
|
||||||
|
|
||||||
|
clusterInfoWithCA := clientcmdapi.NewCluster()
|
||||||
|
clusterInfoWithCA.CertificateAuthority = "cafile"
|
||||||
|
clusterInfoWithCA.CertificateAuthorityData = []byte("cadata")
|
||||||
|
|
||||||
|
startingConfig := newRedFederalCowHammerConfig()
|
||||||
|
startingConfig.Clusters["another-cluster"] = *clusterInfoWithCA
|
||||||
|
|
||||||
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
|
expectedConfig.Clusters["another-cluster"] = *clusterInfoWithInsecure
|
||||||
|
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagInsecure + "=true"},
|
||||||
|
startingConfig: startingConfig,
|
||||||
|
expectedConfig: expectedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCAAndInsecureDisallowed(t *testing.T) {
|
||||||
|
test := configCommandTest{
|
||||||
|
args: []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=true"},
|
||||||
|
startingConfig: newRedFederalCowHammerConfig(),
|
||||||
|
expectedConfig: newRedFederalCowHammerConfig(),
|
||||||
|
expectedOutputs: []string{"certificate", "insecure"},
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMergeExistingAuth(t *testing.T) {
|
func TestMergeExistingAuth(t *testing.T) {
|
||||||
expectedConfig := newRedFederalCowHammerConfig()
|
expectedConfig := newRedFederalCowHammerConfig()
|
||||||
authInfo := expectedConfig.AuthInfos["red-user"]
|
authInfo := expectedConfig.AuthInfos["red-user"]
|
||||||
@ -197,11 +417,11 @@ func TestAdditionalCluster(t *testing.T) {
|
|||||||
cluster := *clientcmdapi.NewCluster()
|
cluster := *clientcmdapi.NewCluster()
|
||||||
cluster.APIVersion = "v1beta1"
|
cluster.APIVersion = "v1beta1"
|
||||||
cluster.CertificateAuthority = "ca-location"
|
cluster.CertificateAuthority = "ca-location"
|
||||||
cluster.InsecureSkipTLSVerify = true
|
cluster.InsecureSkipTLSVerify = false
|
||||||
cluster.Server = "serverlocation"
|
cluster.Server = "serverlocation"
|
||||||
expectedConfig.Clusters["different-cluster"] = cluster
|
expectedConfig.Clusters["different-cluster"] = cluster
|
||||||
test := configCommandTest{
|
test := configCommandTest{
|
||||||
args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=true", "--" + clientcmd.FlagCAFile + "=ca-location", "--" + clientcmd.FlagAPIVersion + "=v1beta1"},
|
args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=ca-location", "--" + clientcmd.FlagAPIVersion + "=v1beta1"},
|
||||||
startingConfig: newRedFederalCowHammerConfig(),
|
startingConfig: newRedFederalCowHammerConfig(),
|
||||||
expectedConfig: expectedConfig,
|
expectedConfig: expectedConfig,
|
||||||
}
|
}
|
||||||
@ -321,7 +541,7 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config) (strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (test configCommandTest) run(t *testing.T) {
|
func (test configCommandTest) run(t *testing.T) {
|
||||||
_, actualConfig := testConfigCommand(test.args, test.startingConfig)
|
out, actualConfig := testConfigCommand(test.args, test.startingConfig)
|
||||||
|
|
||||||
testSetNilMapsToEmpties(reflect.ValueOf(&test.expectedConfig))
|
testSetNilMapsToEmpties(reflect.ValueOf(&test.expectedConfig))
|
||||||
testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig))
|
testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig))
|
||||||
@ -330,6 +550,12 @@ func (test configCommandTest) run(t *testing.T) {
|
|||||||
t.Errorf("diff: %v", util.ObjectDiff(test.expectedConfig, actualConfig))
|
t.Errorf("diff: %v", util.ObjectDiff(test.expectedConfig, actualConfig))
|
||||||
t.Errorf("expected: %#v\n actual: %#v", test.expectedConfig, actualConfig)
|
t.Errorf("expected: %#v\n actual: %#v", test.expectedConfig, actualConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, expectedOutput := range test.expectedOutputs {
|
||||||
|
if !strings.Contains(out, expectedOutput) {
|
||||||
|
t.Errorf("expected '%s' in output, got '%s'", expectedOutput, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func testSetNilMapsToEmpties(curr reflect.Value) {
|
func testSetNilMapsToEmpties(curr reflect.Value) {
|
||||||
actualCurrValue := curr
|
actualCurrValue := curr
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@ -35,20 +36,35 @@ type createAuthInfoOptions struct {
|
|||||||
clientCertificate util.StringFlag
|
clientCertificate util.StringFlag
|
||||||
clientKey util.StringFlag
|
clientKey util.StringFlag
|
||||||
token util.StringFlag
|
token util.StringFlag
|
||||||
|
username util.StringFlag
|
||||||
|
password util.StringFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||||
options := &createAuthInfoOptions{pathOptions: pathOptions}
|
options := &createAuthInfoOptions{pathOptions: pathOptions}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: fmt.Sprintf("set-credentials name [--%v=path/to/auth/file] [--%v=path/to/certficate/file] [--%v=path/to/key/file] [--%v=bearer_token_string]", clientcmd.FlagAuthPath, clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken),
|
Use: fmt.Sprintf("set-credentials name [--%v=authfile] [--%v=certfile] [--%v=keyfile] [--%v=bearer_token] [--%v=basic_user] [--%v=basic_password]", clientcmd.FlagAuthPath, clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword),
|
||||||
Short: "Sets a user entry in .kubeconfig",
|
Short: "Sets a user entry in .kubeconfig",
|
||||||
Long: `Sets a user entry in .kubeconfig
|
Long: fmt.Sprintf(`Sets a user entry in .kubeconfig
|
||||||
Specifying a name that already exists will merge new fields on top of existing values for those fields.
|
|
||||||
e.g.
|
Specifying a name that already exists will merge new fields on top of existing
|
||||||
kubectl config set-credentials cluster-admin --client-key=~/.kube/cluster-admin/.kubecfg.key
|
values. For example, the following only sets the "client-key" field on the
|
||||||
only sets the client-key field on the cluster-admin user entry without touching other values.
|
"cluster-admin" entry, without touching other values:
|
||||||
`,
|
|
||||||
|
set-credentials cluster-admin --client-key=~/.kube/admin.key
|
||||||
|
|
||||||
|
Client-certificate flags:
|
||||||
|
--%v=certfile --%v=keyfile
|
||||||
|
|
||||||
|
Bearer token flags:
|
||||||
|
--%v=bearer_token
|
||||||
|
|
||||||
|
Basic auth flags:
|
||||||
|
--%v=basic_user --%v=basic_password
|
||||||
|
|
||||||
|
Bearer token and basic auth are mutually exclusive.
|
||||||
|
`, clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if !options.complete(cmd) {
|
if !options.complete(cmd) {
|
||||||
return
|
return
|
||||||
@ -56,7 +72,7 @@ func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Com
|
|||||||
|
|
||||||
err := options.run()
|
err := options.run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Fprintf(out, "%v\n", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -65,6 +81,8 @@ func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Com
|
|||||||
cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, clientcmd.FlagCertFile+" for the user entry in .kubeconfig")
|
cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, clientcmd.FlagCertFile+" for the user entry in .kubeconfig")
|
||||||
cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, clientcmd.FlagKeyFile+" for the user entry in .kubeconfig")
|
cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, clientcmd.FlagKeyFile+" for the user entry in .kubeconfig")
|
||||||
cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in .kubeconfig")
|
cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in .kubeconfig")
|
||||||
|
cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in .kubeconfig")
|
||||||
|
cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in .kubeconfig")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -95,17 +113,48 @@ func (o createAuthInfoOptions) run() error {
|
|||||||
func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.AuthInfo) clientcmdapi.AuthInfo {
|
func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.AuthInfo) clientcmdapi.AuthInfo {
|
||||||
modifiedAuthInfo := existingAuthInfo
|
modifiedAuthInfo := existingAuthInfo
|
||||||
|
|
||||||
|
var setToken, setBasic bool
|
||||||
|
|
||||||
if o.authPath.Provided() {
|
if o.authPath.Provided() {
|
||||||
modifiedAuthInfo.AuthPath = o.authPath.Value()
|
modifiedAuthInfo.AuthPath = o.authPath.Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.clientCertificate.Provided() {
|
if o.clientCertificate.Provided() {
|
||||||
modifiedAuthInfo.ClientCertificate = o.clientCertificate.Value()
|
modifiedAuthInfo.ClientCertificate = o.clientCertificate.Value()
|
||||||
|
if len(modifiedAuthInfo.ClientCertificate) > 0 {
|
||||||
|
modifiedAuthInfo.ClientCertificateData = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if o.clientKey.Provided() {
|
if o.clientKey.Provided() {
|
||||||
modifiedAuthInfo.ClientKey = o.clientKey.Value()
|
modifiedAuthInfo.ClientKey = o.clientKey.Value()
|
||||||
|
if len(modifiedAuthInfo.ClientKey) > 0 {
|
||||||
|
modifiedAuthInfo.ClientKeyData = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if o.token.Provided() {
|
if o.token.Provided() {
|
||||||
modifiedAuthInfo.Token = o.token.Value()
|
modifiedAuthInfo.Token = o.token.Value()
|
||||||
|
setToken = len(modifiedAuthInfo.Token) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.username.Provided() {
|
||||||
|
modifiedAuthInfo.Username = o.username.Value()
|
||||||
|
setBasic = setBasic || len(modifiedAuthInfo.Username) > 0
|
||||||
|
}
|
||||||
|
if o.password.Provided() {
|
||||||
|
modifiedAuthInfo.Password = o.password.Value()
|
||||||
|
setBasic = setBasic || len(modifiedAuthInfo.Password) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any auth info was set, make sure any other existing auth types are cleared
|
||||||
|
if setToken || setBasic {
|
||||||
|
if !setToken {
|
||||||
|
modifiedAuthInfo.Token = ""
|
||||||
|
}
|
||||||
|
if !setBasic {
|
||||||
|
modifiedAuthInfo.Username = ""
|
||||||
|
modifiedAuthInfo.Password = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return modifiedAuthInfo
|
return modifiedAuthInfo
|
||||||
@ -126,6 +175,16 @@ func (o createAuthInfoOptions) validate() error {
|
|||||||
if len(o.name) == 0 {
|
if len(o.name) == 0 {
|
||||||
return errors.New("You must specify a non-empty user name")
|
return errors.New("You must specify a non-empty user name")
|
||||||
}
|
}
|
||||||
|
methods := []string{}
|
||||||
|
if len(o.token.Value()) > 0 {
|
||||||
|
methods = append(methods, fmt.Sprintf("--%v", clientcmd.FlagBearerToken))
|
||||||
|
}
|
||||||
|
if len(o.username.Value()) > 0 || len(o.password.Value()) > 0 {
|
||||||
|
methods = append(methods, fmt.Sprintf("--%v/--%v", clientcmd.FlagUsername, clientcmd.FlagPassword))
|
||||||
|
}
|
||||||
|
if len(methods) > 1 {
|
||||||
|
return fmt.Errorf("You cannot specify more than one authentication method at the same time: %v", strings.Join(methods, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func NewCmdConfigSetCluster(out io.Writer, pathOptions *pathOptions) *cobra.Comm
|
|||||||
|
|
||||||
err := options.run()
|
err := options.run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Fprintf(out, "%v\n", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -109,9 +109,19 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste
|
|||||||
}
|
}
|
||||||
if o.insecureSkipTLSVerify.Provided() {
|
if o.insecureSkipTLSVerify.Provided() {
|
||||||
modifiedCluster.InsecureSkipTLSVerify = o.insecureSkipTLSVerify.Value()
|
modifiedCluster.InsecureSkipTLSVerify = o.insecureSkipTLSVerify.Value()
|
||||||
|
// Specifying insecure mode clears any certificate authority
|
||||||
|
if modifiedCluster.InsecureSkipTLSVerify {
|
||||||
|
modifiedCluster.CertificateAuthority = ""
|
||||||
|
modifiedCluster.CertificateAuthorityData = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if o.certificateAuthority.Provided() {
|
if o.certificateAuthority.Provided() {
|
||||||
modifiedCluster.CertificateAuthority = o.certificateAuthority.Value()
|
modifiedCluster.CertificateAuthority = o.certificateAuthority.Value()
|
||||||
|
// Specifying a certificate authority file clears certificate authority data and insecure mode
|
||||||
|
if modifiedCluster.CertificateAuthority != "" {
|
||||||
|
modifiedCluster.CertificateAuthorityData = nil
|
||||||
|
modifiedCluster.InsecureSkipTLSVerify = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return modifiedCluster
|
return modifiedCluster
|
||||||
@ -132,6 +142,9 @@ func (o createClusterOptions) validate() error {
|
|||||||
if len(o.name) == 0 {
|
if len(o.name) == 0 {
|
||||||
return errors.New("You must specify a non-empty cluster name")
|
return errors.New("You must specify a non-empty cluster name")
|
||||||
}
|
}
|
||||||
|
if o.insecureSkipTLSVerify.Value() && o.certificateAuthority.Value() != "" {
|
||||||
|
return errors.New("You cannot specify a certificate authority and insecure mode at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user