mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 05:03:09 +00:00
add kubectl config
This commit is contained in:
parent
de2e298fa9
commit
b51a717f6e
@ -8,36 +8,46 @@ https://github.com/GoogleCloudPlatform/kubernetes/issues/1755
|
||||
|
||||
## Example .kubeconfig file
|
||||
```
|
||||
preferences:
|
||||
colors: true
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
cow-cluster:
|
||||
server: http://cow.org:8080
|
||||
- cluster:
|
||||
api-version: v1beta1
|
||||
horse-cluster:
|
||||
server: https://horse.org:4443
|
||||
server: http://cow.org:8080
|
||||
name: cow-cluster
|
||||
- cluster:
|
||||
certificate-authority: path/to/my/cafile
|
||||
pig-cluster:
|
||||
server: https://pig.org:443
|
||||
server: https://horse.org:4443
|
||||
name: horse-cluster
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: https://pig.org:443
|
||||
name: pig-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: horse-cluster
|
||||
namespace: chisel-ns
|
||||
user: green-user
|
||||
name: federal-context
|
||||
- context:
|
||||
cluster: pig-cluster
|
||||
namespace: saw-ns
|
||||
user: black-user
|
||||
name: queen-anne-context
|
||||
current-context: federal-context
|
||||
kind: Config
|
||||
preferences:
|
||||
colors: true
|
||||
users:
|
||||
black-user:
|
||||
auth-path: path/to/my/existing/.kubernetes_auth file
|
||||
blue-user:
|
||||
- name: black-user
|
||||
user:
|
||||
auth-path: path/to/my/existing/.kubernetes_auth_file
|
||||
- name: blue-user
|
||||
user:
|
||||
token: blue-token
|
||||
green-user:
|
||||
- name: green-user
|
||||
user:
|
||||
client-certificate: path/to/my/client/cert
|
||||
client-key: path/to/my/client/key
|
||||
contexts:
|
||||
queen-anne-context:
|
||||
cluster: pig-cluster
|
||||
user: black-user
|
||||
namespace: saw-ns
|
||||
federal-context:
|
||||
cluster: horse-cluster
|
||||
user: green-user
|
||||
namespace: chisel-ns
|
||||
current-context: federal-context
|
||||
```
|
||||
|
||||
## Loading and merging rules
|
||||
@ -67,3 +77,86 @@ The rules for loading and merging the .kubeconfig files are straightforward, but
|
||||
|
||||
The command line flags are: `auth-path`, `client-certificate`, `client-key`, and `token`. If there are two conflicting techniques, fail.
|
||||
1. For any information still missing, use default values and potentially prompt for authentication information
|
||||
|
||||
## Manipulation of .kubeconfig via `kubectl config <subcommand>`
|
||||
In order to more easily manipulate .kubeconfig files, there are a series of subcommands to `kubectl config` to help.
|
||||
```
|
||||
kubectl config set-credentials name --auth-path=path/to/authfile --client-certificate=path/to/cert --client-key=path/to/key --token=string
|
||||
Sets a user entry in .kubeconfig. If the referenced name already exists, it will be overwritten.
|
||||
kubectl config set-cluster name --server=server --skip-tls=bool --certificate-authority=path/to/ca --api-version=string
|
||||
Sets a cluster entry in .kubeconfig. If the referenced name already exists, it will be overwritten.
|
||||
kubectl config set-context name --user=string --cluster=string --namespace=string
|
||||
Sets a config entry in .kubeconfig. If the referenced name already exists, it will be overwritten.
|
||||
kubectl config use-context name
|
||||
Sets current-context to name
|
||||
kubectl config set property-name property-value
|
||||
Sets arbitrary value in .kubeconfig
|
||||
kubectl config unset property-name
|
||||
Unsets arbitrary value in .kubeconfig
|
||||
kubectl config view --local=true --global=false --kubeconfig=specific/filename --merged
|
||||
Displays the merged (or not) result of the specified .kubeconfig file
|
||||
|
||||
--local, --global, and --kubeconfig are valid flags for all of these operations.
|
||||
```
|
||||
|
||||
### Example
|
||||
```
|
||||
$kubectl config set-credentials myself --auth-path=path/to/my/existing/auth-file
|
||||
$kubectl config set-cluster local-server --server=http://localhost:8080
|
||||
$kubectl config set-context default-context --cluster=local-server --user=myself
|
||||
$kubectl config use-context default-context
|
||||
$kubectl config set contexts.default-context.namespace the-right-prefix
|
||||
$kubectl config view
|
||||
```
|
||||
produces this output
|
||||
```
|
||||
clusters:
|
||||
local-server:
|
||||
server: http://localhost:8080
|
||||
contexts:
|
||||
default-context:
|
||||
cluster: local-server
|
||||
namespace: the-right-prefix
|
||||
user: myself
|
||||
current-context: default-context
|
||||
preferences: {}
|
||||
users:
|
||||
myself:
|
||||
auth-path: path/to/my/existing/auth-file
|
||||
|
||||
```
|
||||
and a .kubeconfig file that looks like this
|
||||
```
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: http://localhost:8080
|
||||
name: local-server
|
||||
contexts:
|
||||
- context:
|
||||
cluster: local-server
|
||||
namespace: the-right-prefix
|
||||
user: myself
|
||||
name: default-context
|
||||
current-context: default-context
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: myself
|
||||
user:
|
||||
auth-path: path/to/my/existing/auth-file
|
||||
```
|
||||
|
||||
#### Commands for the example file
|
||||
```
|
||||
$kubectl config set preferences.colors true
|
||||
$kubectl config set-cluster cow-cluster --server=http://cow.org:8080 --api-version=v1beta1
|
||||
$kubectl config set-cluster horse-cluster --server=https://horse.org:4443 --certificate-authority=path/to/my/cafile
|
||||
$kubectl config set-cluster pig-cluster --server=https://pig.org:443 --insecure-skip-tls-verify=true
|
||||
$kubectl config set-credentials black-user --auth-path=path/to/my/existing/.kubernetes_auth_file
|
||||
$kubectl config set-credentials blue-user --token=blue-token
|
||||
$kubectl config set-credentials green-user --client-certificate=path/to/my/client/cert --client-key=path/to/my/client/key
|
||||
$kubectl config set-context queen-anne-context --cluster=pig-cluster --user=black-user --namespace=saw-ns
|
||||
$kubectl config set-context federal-context --cluster=horse-cluster --user=green-user --namespace=chisel-ns
|
||||
$kubectl config use-context federal-context
|
||||
```
|
40
pkg/client/clientcmd/api/latest/latest.go
Normal file
40
pkg/client/clientcmd/api/latest/latest.go
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package latest
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
// Version is the string that represents the current external default version.
|
||||
const Version = "v1"
|
||||
|
||||
// OldestVersion is the string that represents the oldest server version supported,
|
||||
// for client code that wants to hardcode the lowest common denominator.
|
||||
const OldestVersion = "v1"
|
||||
|
||||
// Versions is the list of versions that are recognized in code. The order provided
|
||||
// may be assumed to be least feature rich to most feature rich, and clients may
|
||||
// choose to prefer the latter items in the list over the former items when presented
|
||||
// with a set of versions to choose.
|
||||
var Versions = []string{"v1"}
|
||||
|
||||
// Codec is the default codec for serializing output that should use
|
||||
// the latest supported version. Use this Codec when writing to
|
||||
// disk, a data store that is not dynamically versioned, or in tests.
|
||||
// This codec can decode any object that Kubernetes is aware of.
|
||||
var Codec = v1.Codec
|
32
pkg/client/clientcmd/api/register.go
Normal file
32
pkg/client/clientcmd/api/register.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered.
|
||||
var Scheme = runtime.NewScheme()
|
||||
|
||||
func init() {
|
||||
Scheme.AddKnownTypes("",
|
||||
&Config{},
|
||||
)
|
||||
}
|
||||
|
||||
func (*Config) IsAnAPIObject() {}
|
119
pkg/client/clientcmd/api/types.go
Normal file
119
pkg/client/clientcmd/api/types.go
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
type Config struct {
|
||||
api.TypeMeta `json:",inline"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters map[string]Cluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos map[string]AuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts map[string]Context `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1beta1, v1beta2, v1beta3, etc).
|
||||
APIVersion string `json:"api-version,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// AuthPath is the path to a kubernetes auth file (~/.kubernetes_auth). If you provide an AuthPath, the other options specified are ignored
|
||||
AuthPath string `json:"auth-path,omitempty"`
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
Token string `json:"token,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Preferences: *NewPreferences(),
|
||||
Clusters: make(map[string]Cluster),
|
||||
AuthInfos: make(map[string]AuthInfo),
|
||||
Contexts: make(map[string]Context),
|
||||
Extensions: make(map[string]runtime.EmbeddedObject),
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewContext() *Context {
|
||||
return &Context{Extensions: make(map[string]runtime.EmbeddedObject)}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewCluster() *Cluster {
|
||||
return &Cluster{Extensions: make(map[string]runtime.EmbeddedObject)}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewAuthInfo() *AuthInfo {
|
||||
return &AuthInfo{Extensions: make(map[string]runtime.EmbeddedObject)}
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewPreferences() *Preferences {
|
||||
return &Preferences{Extensions: make(map[string]runtime.EmbeddedObject)}
|
||||
}
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package clientcmd
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/v2/yaml"
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func ExampleEmptyConfig() {
|
||||
@ -32,11 +32,11 @@ func ExampleEmptyConfig() {
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// preferences: {}
|
||||
// clusters: {}
|
||||
// users: {}
|
||||
// contexts: {}
|
||||
// current-context: ""
|
||||
// preferences: {}
|
||||
// users: {}
|
||||
}
|
||||
|
||||
func ExampleOfOptionsConfig() {
|
||||
@ -86,17 +86,30 @@ func ExampleOfOptionsConfig() {
|
||||
|
||||
fmt.Printf("%v", string(output))
|
||||
// Output:
|
||||
// preferences:
|
||||
// colors: true
|
||||
// clusters:
|
||||
// alfa:
|
||||
// server: https://alfa.org:8080
|
||||
// api-version: v1beta2
|
||||
// insecure-skip-tls-verify: true
|
||||
// certificate-authority: path/to/my/cert-ca-filename
|
||||
// insecure-skip-tls-verify: true
|
||||
// server: https://alfa.org:8080
|
||||
// bravo:
|
||||
// server: https://bravo.org:8080
|
||||
// api-version: v1beta1
|
||||
// server: https://bravo.org:8080
|
||||
// contexts:
|
||||
// alfa-as-black-mage:
|
||||
// cluster: alfa
|
||||
// namespace: zulu
|
||||
// user: black-mage-via-file
|
||||
// alfa-as-white-mage:
|
||||
// cluster: alfa
|
||||
// user: white-mage-via-cert
|
||||
// bravo-as-black-mage:
|
||||
// cluster: bravo
|
||||
// namespace: yankee
|
||||
// user: black-mage-via-file
|
||||
// current-context: alfa-as-white-mage
|
||||
// preferences:
|
||||
// colors: true
|
||||
// users:
|
||||
// black-mage-via-file:
|
||||
// auth-path: path/to/my/.kubernetes_auth
|
||||
@ -105,17 +118,4 @@ func ExampleOfOptionsConfig() {
|
||||
// white-mage-via-cert:
|
||||
// client-certificate: path/to/my/client-cert-filename
|
||||
// client-key: path/to/my/client-key-filename
|
||||
// contexts:
|
||||
// alfa-as-black-mage:
|
||||
// cluster: alfa
|
||||
// user: black-mage-via-file
|
||||
// namespace: zulu
|
||||
// alfa-as-white-mage:
|
||||
// cluster: alfa
|
||||
// user: white-mage-via-cert
|
||||
// bravo-as-black-mage:
|
||||
// cluster: bravo
|
||||
// user: black-mage-via-file
|
||||
// namespace: yankee
|
||||
// current-context: alfa-as-white-mage
|
||||
}
|
206
pkg/client/clientcmd/api/v1/conversion.go
Normal file
206
pkg/client/clientcmd/api/v1/conversion.go
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := newer.Scheme.AddConversionFuncs(
|
||||
func(in *Config, out *newer.Config, s conversion.Scope) error {
|
||||
out.CurrentContext = in.CurrentContext
|
||||
if err := s.Convert(&in.Preferences, &out.Preferences, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Clusters = make(map[string]newer.Cluster)
|
||||
if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.AuthInfos = make(map[string]newer.AuthInfo)
|
||||
if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Contexts = make(map[string]newer.Context)
|
||||
if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Extensions = make(map[string]runtime.EmbeddedObject)
|
||||
if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.Config, out *Config, s conversion.Scope) error {
|
||||
out.CurrentContext = in.CurrentContext
|
||||
if err := s.Convert(&in.Preferences, &out.Preferences, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Clusters = make([]NamedCluster, 0, 0)
|
||||
if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.AuthInfos = make([]NamedAuthInfo, 0, 0)
|
||||
if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Contexts = make([]NamedContext, 0, 0)
|
||||
if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Extensions = make([]NamedExtension, 0, 0)
|
||||
if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedCluster, out *map[string]newer.Cluster, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newCluster := newer.NewCluster()
|
||||
if err := s.Convert(&curr.Cluster, newCluster, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = *newCluster
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]newer.Cluster, out *[]NamedCluster, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newCluster := (*in)[key]
|
||||
oldCluster := &Cluster{}
|
||||
if err := s.Convert(&newCluster, oldCluster, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedCluster := NamedCluster{key, *oldCluster}
|
||||
*out = append(*out, namedCluster)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedAuthInfo, out *map[string]newer.AuthInfo, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newAuthInfo := newer.NewAuthInfo()
|
||||
if err := s.Convert(&curr.AuthInfo, newAuthInfo, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = *newAuthInfo
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]newer.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newAuthInfo := (*in)[key]
|
||||
oldAuthInfo := &AuthInfo{}
|
||||
if err := s.Convert(&newAuthInfo, oldAuthInfo, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedAuthInfo := NamedAuthInfo{key, *oldAuthInfo}
|
||||
*out = append(*out, namedAuthInfo)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedContext, out *map[string]newer.Context, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newContext := newer.NewContext()
|
||||
if err := s.Convert(&curr.Context, newContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = *newContext
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]newer.Context, out *[]NamedContext, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newContext := (*in)[key]
|
||||
oldContext := &Context{}
|
||||
if err := s.Convert(&newContext, oldContext, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedContext := NamedContext{key, *oldContext}
|
||||
*out = append(*out, namedContext)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *[]NamedExtension, out *map[string]runtime.EmbeddedObject, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newExtension := &runtime.EmbeddedObject{}
|
||||
if err := s.Convert(&curr.Extension, newExtension, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[curr.Name] = *newExtension
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(in *map[string]runtime.EmbeddedObject, out *[]NamedExtension, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newExtension := (*in)[key]
|
||||
oldExtension := &runtime.RawExtension{}
|
||||
if err := s.Convert(&newExtension, oldExtension, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namedExtension := NamedExtension{key, *oldExtension}
|
||||
*out = append(*out, namedExtension)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
}
|
33
pkg/client/clientcmd/api/v1/register.go
Normal file
33
pkg/client/clientcmd/api/v1/register.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Codec encodes internal objects to the v1 scheme
|
||||
var Codec = runtime.CodecFor(api.Scheme, "v1")
|
||||
|
||||
func init() {
|
||||
api.Scheme.AddKnownTypes("v1",
|
||||
&Config{},
|
||||
)
|
||||
}
|
||||
|
||||
func (*Config) IsAnAPIObject() {}
|
120
pkg/client/clientcmd/api/v1/types.go
Normal file
120
pkg/client/clientcmd/api/v1/types.go
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
type Config struct {
|
||||
v1beta3.TypeMeta `json:",inline"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters []NamedCluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos []NamedAuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts []NamedContext `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1beta1, v1beta2, v1beta3, etc).
|
||||
APIVersion string `json:"api-version,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// AuthPath is the path to a kubernetes auth file (~/.kubernetes_auth). If you provide an AuthPath, the other options specified are ignored
|
||||
AuthPath string `json:"auth-path,omitempty"`
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
Token string `json:"token,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// NamedCluster relates nicknames to cluster information
|
||||
type NamedCluster struct {
|
||||
// Name is the nickname for this Cluster
|
||||
Name string `json:"name"`
|
||||
// Cluster holds the cluster information
|
||||
Cluster Cluster `json:"cluster"`
|
||||
}
|
||||
|
||||
// NamedContext relates nicknames to context information
|
||||
type NamedContext struct {
|
||||
// Name is the nickname for this Context
|
||||
Name string `json:"name"`
|
||||
// Context holds the context information
|
||||
Context Context `json:"context"`
|
||||
}
|
||||
|
||||
// NamedAuthInfo relates nicknames to auth information
|
||||
type NamedAuthInfo struct {
|
||||
// Name is the nickname for this AuthInfo
|
||||
Name string `json:"name"`
|
||||
// AuthInfo holds the auth information
|
||||
AuthInfo AuthInfo `json:"user"`
|
||||
}
|
||||
|
||||
// NamedExtension relates nicknames to extension information
|
||||
type NamedExtension struct {
|
||||
// Name is the nickname for this Extension
|
||||
Name string `json:"name"`
|
||||
// Extension holds the extension information
|
||||
Extension runtime.RawExtension `json:"extension"`
|
||||
}
|
@ -23,45 +23,51 @@ import (
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: eventually apiserver should start on 443 and be secure by default
|
||||
defaultCluster = Cluster{Server: "http://localhost:8080"}
|
||||
envVarCluster = Cluster{Server: os.Getenv("KUBERNETES_MASTER")}
|
||||
defaultCluster = clientcmdapi.Cluster{Server: "http://localhost:8080"}
|
||||
envVarCluster = clientcmdapi.Cluster{Server: os.Getenv("KUBERNETES_MASTER")}
|
||||
)
|
||||
|
||||
// ClientConfig is used to make it easy to get an api server client
|
||||
type ClientConfig interface {
|
||||
RawConfig() (clientcmdapi.Config, error)
|
||||
// ClientConfig returns a complete client config
|
||||
ClientConfig() (*client.Config, error)
|
||||
}
|
||||
|
||||
// DirectClientConfig is a ClientConfig interface that is backed by a Config, options overrides, and an optional fallbackReader for auth information
|
||||
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
|
||||
type DirectClientConfig struct {
|
||||
config Config
|
||||
config clientcmdapi.Config
|
||||
contextName string
|
||||
overrides *ConfigOverrides
|
||||
fallbackReader io.Reader
|
||||
}
|
||||
|
||||
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
|
||||
func NewDefaultClientConfig(config Config, overrides *ConfigOverrides) ClientConfig {
|
||||
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
|
||||
return DirectClientConfig{config, config.CurrentContext, overrides, nil}
|
||||
}
|
||||
|
||||
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
|
||||
func NewNonInteractiveClientConfig(config Config, contextName string, overrides *ConfigOverrides) ClientConfig {
|
||||
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides) ClientConfig {
|
||||
return DirectClientConfig{config, contextName, overrides, nil}
|
||||
}
|
||||
|
||||
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
|
||||
func NewInteractiveClientConfig(config Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
|
||||
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
|
||||
return DirectClientConfig{config, contextName, overrides, fallbackReader}
|
||||
}
|
||||
|
||||
func (config DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return config.config, nil
|
||||
}
|
||||
|
||||
// ClientConfig implements ClientConfig
|
||||
func (config DirectClientConfig) ClientConfig() (*client.Config, error) {
|
||||
if err := config.ConfirmUsable(); err != nil {
|
||||
@ -102,7 +108,7 @@ func (config DirectClientConfig) ClientConfig() (*client.Config, error) {
|
||||
// 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
|
||||
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
||||
// 3. load the ~/.kubernetes_auth file as a default
|
||||
func getServerIdentificationPartialConfig(configAuthInfo AuthInfo, configClusterInfo Cluster) (*client.Config, error) {
|
||||
func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*client.Config, error) {
|
||||
mergedConfig := &client.Config{}
|
||||
|
||||
defaultAuthPathInfo, err := NewDefaultAuthLoader().LoadAuth(os.Getenv("HOME") + "/.kubernetes_auth")
|
||||
@ -140,7 +146,7 @@ func getServerIdentificationPartialConfig(configAuthInfo AuthInfo, configCluster
|
||||
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
||||
// 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file
|
||||
// 4. if there is not enough information to identify the user, prompt if possible
|
||||
func getUserIdentificationPartialConfig(configAuthInfo AuthInfo, fallbackReader io.Reader) (*client.Config, error) {
|
||||
func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader) (*client.Config, error) {
|
||||
mergedConfig := &client.Config{}
|
||||
|
||||
if len(configAuthInfo.AuthPath) > 0 {
|
||||
@ -255,15 +261,15 @@ func (config DirectClientConfig) getClusterName() string {
|
||||
return config.getContext().Cluster
|
||||
}
|
||||
|
||||
func (config DirectClientConfig) getContext() Context {
|
||||
func (config DirectClientConfig) getContext() clientcmdapi.Context {
|
||||
return config.config.Contexts[config.getContextName()]
|
||||
}
|
||||
|
||||
func (config DirectClientConfig) getAuthInfo() AuthInfo {
|
||||
func (config DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo {
|
||||
authInfos := config.config.AuthInfos
|
||||
authInfoName := config.getAuthInfoName()
|
||||
|
||||
var mergedAuthInfo AuthInfo
|
||||
var mergedAuthInfo clientcmdapi.AuthInfo
|
||||
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
||||
mergo.Merge(&mergedAuthInfo, configAuthInfo)
|
||||
}
|
||||
@ -272,11 +278,11 @@ func (config DirectClientConfig) getAuthInfo() AuthInfo {
|
||||
return mergedAuthInfo
|
||||
}
|
||||
|
||||
func (config DirectClientConfig) getCluster() Cluster {
|
||||
func (config DirectClientConfig) getCluster() clientcmdapi.Cluster {
|
||||
clusterInfos := config.config.Clusters
|
||||
clusterInfoName := config.getClusterName()
|
||||
|
||||
var mergedClusterInfo Cluster
|
||||
var mergedClusterInfo clientcmdapi.Cluster
|
||||
mergo.Merge(&mergedClusterInfo, defaultCluster)
|
||||
mergo.Merge(&mergedClusterInfo, envVarCluster)
|
||||
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
||||
|
@ -22,23 +22,24 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
func createValidTestConfig() *Config {
|
||||
func createValidTestConfig() *clientcmdapi.Config {
|
||||
const (
|
||||
server = "https://anything.com:8080"
|
||||
token = "the-token"
|
||||
)
|
||||
|
||||
config := NewConfig()
|
||||
config.Clusters["clean"] = Cluster{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = clientcmdapi.Cluster{
|
||||
Server: server,
|
||||
APIVersion: latest.Version,
|
||||
}
|
||||
config.AuthInfos["clean"] = AuthInfo{
|
||||
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||
Token: token,
|
||||
}
|
||||
config.Contexts["clean"] = Context{
|
||||
config.Contexts["clean"] = clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
|
@ -20,8 +20,11 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/imdario/mergo"
|
||||
"gopkg.in/v2/yaml"
|
||||
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
clientcmdlatest "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api/latest"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -56,8 +59,8 @@ func NewClientConfigLoadingRules() *ClientConfigLoadingRules {
|
||||
// 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.
|
||||
func (rules *ClientConfigLoadingRules) Load() (*Config, error) {
|
||||
config := NewConfig()
|
||||
func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
|
||||
mergeConfigWithFile(config, rules.CommandLinePath)
|
||||
mergeConfigWithFile(config, rules.EnvVarPath)
|
||||
@ -67,7 +70,7 @@ func (rules *ClientConfigLoadingRules) Load() (*Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func mergeConfigWithFile(startingConfig *Config, filename string) error {
|
||||
func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) error {
|
||||
if len(filename) == 0 {
|
||||
// no work to do
|
||||
return nil
|
||||
@ -84,16 +87,15 @@ func mergeConfigWithFile(startingConfig *Config, filename string) error {
|
||||
}
|
||||
|
||||
// LoadFromFile takes a filename and deserializes the contents into Config object
|
||||
func LoadFromFile(filename string) (*Config, error) {
|
||||
config := &Config{}
|
||||
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||
config := &clientcmdapi.Config{}
|
||||
|
||||
kubeconfigBytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(kubeconfigBytes, &config)
|
||||
if err != nil {
|
||||
if err := clientcmdlatest.Codec.DecodeInto(kubeconfigBytes, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -102,16 +104,20 @@ func LoadFromFile(filename string) (*Config, error) {
|
||||
|
||||
// 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
|
||||
// it stomps the contents
|
||||
func WriteToFile(config Config, filename string) error {
|
||||
content, err := yaml.Marshal(config)
|
||||
func WriteToFile(config clientcmdapi.Config, filename string) error {
|
||||
json, err := clientcmdlatest.Codec.Encode(&config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, content, 0644)
|
||||
content, err := yaml.JSONToYAML(json)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, content, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -21,47 +21,50 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"gopkg.in/v2/yaml"
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
clientcmdlatest "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api/latest"
|
||||
)
|
||||
|
||||
var (
|
||||
testConfigAlfa = Config{
|
||||
AuthInfos: map[string]AuthInfo{
|
||||
testConfigAlfa = clientcmdapi.Config{
|
||||
AuthInfos: map[string]clientcmdapi.AuthInfo{
|
||||
"red-user": {Token: "red-token"}},
|
||||
Clusters: map[string]Cluster{
|
||||
Clusters: map[string]clientcmdapi.Cluster{
|
||||
"cow-cluster": {Server: "http://cow.org:8080"}},
|
||||
Contexts: map[string]Context{
|
||||
Contexts: map[string]clientcmdapi.Context{
|
||||
"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}},
|
||||
}
|
||||
testConfigBravo = Config{
|
||||
AuthInfos: map[string]AuthInfo{
|
||||
testConfigBravo = clientcmdapi.Config{
|
||||
AuthInfos: map[string]clientcmdapi.AuthInfo{
|
||||
"black-user": {Token: "black-token"}},
|
||||
Clusters: map[string]Cluster{
|
||||
Clusters: map[string]clientcmdapi.Cluster{
|
||||
"pig-cluster": {Server: "http://pig.org:8080"}},
|
||||
Contexts: map[string]Context{
|
||||
Contexts: map[string]clientcmdapi.Context{
|
||||
"queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}},
|
||||
}
|
||||
testConfigCharlie = Config{
|
||||
AuthInfos: map[string]AuthInfo{
|
||||
testConfigCharlie = clientcmdapi.Config{
|
||||
AuthInfos: map[string]clientcmdapi.AuthInfo{
|
||||
"green-user": {Token: "green-token"}},
|
||||
Clusters: map[string]Cluster{
|
||||
Clusters: map[string]clientcmdapi.Cluster{
|
||||
"horse-cluster": {Server: "http://horse.org:8080"}},
|
||||
Contexts: map[string]Context{
|
||||
Contexts: map[string]clientcmdapi.Context{
|
||||
"shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}},
|
||||
}
|
||||
testConfigDelta = Config{
|
||||
AuthInfos: map[string]AuthInfo{
|
||||
testConfigDelta = clientcmdapi.Config{
|
||||
AuthInfos: map[string]clientcmdapi.AuthInfo{
|
||||
"blue-user": {Token: "blue-token"}},
|
||||
Clusters: map[string]Cluster{
|
||||
Clusters: map[string]clientcmdapi.Cluster{
|
||||
"chicken-cluster": {Server: "http://chicken.org:8080"}},
|
||||
Contexts: map[string]Context{
|
||||
Contexts: map[string]clientcmdapi.Context{
|
||||
"gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}},
|
||||
}
|
||||
testConfigConflictAlfa = Config{
|
||||
AuthInfos: map[string]AuthInfo{
|
||||
testConfigConflictAlfa = clientcmdapi.Config{
|
||||
AuthInfos: map[string]clientcmdapi.AuthInfo{
|
||||
"red-user": {Token: "a-different-red-token"},
|
||||
"yellow-user": {Token: "yellow-token"}},
|
||||
Clusters: map[string]Cluster{
|
||||
Clusters: map[string]clientcmdapi.Cluster{
|
||||
"cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true},
|
||||
"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}},
|
||||
CurrentContext: "federal-context",
|
||||
@ -84,31 +87,42 @@ func ExampleMergingSomeWithConflict() {
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
|
||||
output, err := yaml.Marshal(mergedConfig)
|
||||
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:
|
||||
// preferences: {}
|
||||
// apiVersion: v1
|
||||
// clusters:
|
||||
// cow-cluster:
|
||||
// - cluster:
|
||||
// server: http://cow.org:8080
|
||||
// donkey-cluster:
|
||||
// server: http://donkey.org:8080
|
||||
// name: cow-cluster
|
||||
// - cluster:
|
||||
// insecure-skip-tls-verify: true
|
||||
// users:
|
||||
// red-user:
|
||||
// token: red-token
|
||||
// yellow-user:
|
||||
// token: yellow-token
|
||||
// server: http://donkey.org:8080
|
||||
// name: donkey-cluster
|
||||
// contexts:
|
||||
// federal-context:
|
||||
// - context:
|
||||
// cluster: cow-cluster
|
||||
// user: red-user
|
||||
// namespace: hammer-ns
|
||||
// user: red-user
|
||||
// name: federal-context
|
||||
// current-context: federal-context
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: red-user
|
||||
// user:
|
||||
// token: red-token
|
||||
// - name: yellow-user
|
||||
// user:
|
||||
// token: yellow-token
|
||||
}
|
||||
|
||||
func ExampleMergingEverythingNoConflicts() {
|
||||
@ -135,48 +149,66 @@ func ExampleMergingEverythingNoConflicts() {
|
||||
|
||||
mergedConfig, err := loadingRules.Load()
|
||||
|
||||
output, err := yaml.Marshal(mergedConfig)
|
||||
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:
|
||||
// preferences: {}
|
||||
// apiVersion: v1
|
||||
// clusters:
|
||||
// chicken-cluster:
|
||||
// - cluster:
|
||||
// server: http://chicken.org:8080
|
||||
// cow-cluster:
|
||||
// name: chicken-cluster
|
||||
// - cluster:
|
||||
// server: http://cow.org:8080
|
||||
// horse-cluster:
|
||||
// name: cow-cluster
|
||||
// - cluster:
|
||||
// server: http://horse.org:8080
|
||||
// pig-cluster:
|
||||
// name: horse-cluster
|
||||
// - cluster:
|
||||
// server: http://pig.org:8080
|
||||
// users:
|
||||
// black-user:
|
||||
// token: black-token
|
||||
// blue-user:
|
||||
// token: blue-token
|
||||
// green-user:
|
||||
// token: green-token
|
||||
// red-user:
|
||||
// token: red-token
|
||||
// name: pig-cluster
|
||||
// contexts:
|
||||
// federal-context:
|
||||
// - context:
|
||||
// cluster: cow-cluster
|
||||
// user: red-user
|
||||
// namespace: hammer-ns
|
||||
// gothic-context:
|
||||
// user: red-user
|
||||
// name: federal-context
|
||||
// - context:
|
||||
// cluster: chicken-cluster
|
||||
// user: blue-user
|
||||
// namespace: plane-ns
|
||||
// queen-anne-context:
|
||||
// user: blue-user
|
||||
// name: gothic-context
|
||||
// - context:
|
||||
// cluster: pig-cluster
|
||||
// user: black-user
|
||||
// namespace: saw-ns
|
||||
// shaker-context:
|
||||
// user: black-user
|
||||
// name: queen-anne-context
|
||||
// - context:
|
||||
// cluster: horse-cluster
|
||||
// user: green-user
|
||||
// namespace: chisel-ns
|
||||
// user: green-user
|
||||
// name: shaker-context
|
||||
// current-context: ""
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: black-user
|
||||
// user:
|
||||
// token: black-token
|
||||
// - name: blue-user
|
||||
// user:
|
||||
// token: blue-token
|
||||
// - name: green-user
|
||||
// user:
|
||||
// token: green-token
|
||||
// - name: red-user
|
||||
// user:
|
||||
// token: red-token
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
// DeferredLoadingClientConfig is a ClientConfig interface that is backed by a set of loading rules
|
||||
@ -59,6 +60,15 @@ func (config DeferredLoadingClientConfig) createClientConfig() (ClientConfig, er
|
||||
return mergedClientConfig, nil
|
||||
}
|
||||
|
||||
func (config DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
mergedConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
|
||||
return mergedConfig.RawConfig()
|
||||
}
|
||||
|
||||
// ClientConfig implements ClientConfig
|
||||
func (config DeferredLoadingClientConfig) ClientConfig() (*client.Config, error) {
|
||||
mergedClientConfig, err := config.createClientConfig()
|
||||
|
@ -85,7 +85,7 @@ func testBindClientConfig(cmd *cobra.Command) ClientConfig {
|
||||
cmd.PersistentFlags().StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
|
||||
|
||||
overrides := &ConfigOverrides{}
|
||||
overrides.BindFlags(cmd.PersistentFlags(), RecommendedConfigOverrideFlags(""))
|
||||
BindOverrideFlags(overrides, cmd.PersistentFlags(), RecommendedConfigOverrideFlags(""))
|
||||
clientConfig := NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)
|
||||
|
||||
return clientConfig
|
||||
|
@ -18,13 +18,15 @@ package clientcmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
// ConfigOverrides holds values that should override whatever information is pulled from the actual Config object. You can't
|
||||
// simply use an actual Config object, because Configs hold maps, but overrides are restricted to "at most one"
|
||||
type ConfigOverrides struct {
|
||||
AuthInfo AuthInfo
|
||||
ClusterInfo Cluster
|
||||
AuthInfo clientcmdapi.AuthInfo
|
||||
ClusterInfo clientcmdapi.Cluster
|
||||
Namespace string
|
||||
CurrentContext string
|
||||
ClusterName string
|
||||
@ -105,8 +107,8 @@ func RecommendedConfigOverrideFlags(prefix string) ConfigOverrideFlags {
|
||||
}
|
||||
}
|
||||
|
||||
// BindFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func (authInfo *AuthInfo) BindFlags(flags *pflag.FlagSet, flagNames AuthOverrideFlags) {
|
||||
// BindAuthInfoFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, flagNames AuthOverrideFlags) {
|
||||
// TODO short flag names are impossible to prefix, decide whether to keep them or not
|
||||
flags.StringVarP(&authInfo.AuthPath, flagNames.AuthPath, "a", "", "Path to the auth info file. If missing, prompt the user. Only used if using https.")
|
||||
flags.StringVar(&authInfo.ClientCertificate, flagNames.ClientCertificate, "", "Path to a client key file for TLS.")
|
||||
@ -114,8 +116,8 @@ func (authInfo *AuthInfo) BindFlags(flags *pflag.FlagSet, flagNames AuthOverride
|
||||
flags.StringVar(&authInfo.Token, flagNames.Token, "", "Bearer token for authentication to the API server.")
|
||||
}
|
||||
|
||||
// BindFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func (clusterInfo *Cluster) BindFlags(flags *pflag.FlagSet, flagNames ClusterOverrideFlags) {
|
||||
// BindClusterFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, flagNames ClusterOverrideFlags) {
|
||||
// TODO short flag names are impossible to prefix, decide whether to keep them or not
|
||||
flags.StringVarP(&clusterInfo.Server, flagNames.APIServer, "s", "", "The address of the Kubernetes API server")
|
||||
flags.StringVar(&clusterInfo.APIVersion, flagNames.APIVersion, "", "The API version to use when talking to the server")
|
||||
@ -123,10 +125,10 @@ func (clusterInfo *Cluster) BindFlags(flags *pflag.FlagSet, flagNames ClusterOve
|
||||
flags.BoolVar(&clusterInfo.InsecureSkipTLSVerify, flagNames.InsecureSkipTLSVerify, false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
|
||||
}
|
||||
|
||||
// BindFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func (overrides *ConfigOverrides) BindFlags(flags *pflag.FlagSet, flagNames ConfigOverrideFlags) {
|
||||
(&overrides.AuthInfo).BindFlags(flags, flagNames.AuthOverrideFlags)
|
||||
(&overrides.ClusterInfo).BindFlags(flags, flagNames.ClusterOverrideFlags)
|
||||
// BindOverrideFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindOverrideFlags(overrides *ConfigOverrides, flags *pflag.FlagSet, flagNames ConfigOverrideFlags) {
|
||||
BindAuthInfoFlags(&overrides.AuthInfo, flags, flagNames.AuthOverrideFlags)
|
||||
BindClusterFlags(&overrides.ClusterInfo, flags, flagNames.ClusterOverrideFlags)
|
||||
// TODO not integrated yet
|
||||
// flags.StringVar(&overrides.Namespace, flagNames.Namespace, "", "If present, the namespace scope for this CLI request.")
|
||||
flags.StringVar(&overrides.CurrentContext, flagNames.CurrentContext, "", "The name of the kubeconfig context to use")
|
||||
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package clientcmd
|
||||
|
||||
import ()
|
||||
|
||||
// Where possible, yaml tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
type Config struct {
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `yaml:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters map[string]Cluster `yaml:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos map[string]AuthInfo `yaml:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts map[string]Context `yaml:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `yaml:"current-context"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
Colors bool `yaml:"colors,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `yaml:"server"`
|
||||
// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1beta1, v1beta2, v1beta3, etc).
|
||||
APIVersion string `yaml:"api-version,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
InsecureSkipTLSVerify bool `yaml:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
CertificateAuthority string `yaml:"certificate-authority,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// AuthPath is the path to a kubernetes auth file (~/.kubernetes_auth). If you provide an AuthPath, the other options specified are ignored
|
||||
AuthPath string `yaml:"auth-path,omitempty"`
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
ClientCertificate string `yaml:"client-certificate,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
ClientKey string `yaml:"client-key,omitempty"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
Token string `yaml:"token,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `yaml:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `yaml:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Clusters: make(map[string]Cluster),
|
||||
AuthInfos: make(map[string]AuthInfo),
|
||||
Contexts: make(map[string]Context),
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
utilerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
)
|
||||
@ -47,7 +48,7 @@ func IsContextNotFound(err error) bool {
|
||||
}
|
||||
|
||||
// Validate checks for errors in the Config. It does not return early so that it can find as many errors as possible.
|
||||
func Validate(config Config) error {
|
||||
func Validate(config clientcmdapi.Config) error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if len(config.CurrentContext) != 0 {
|
||||
@ -73,7 +74,7 @@ func Validate(config Config) error {
|
||||
|
||||
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
|
||||
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
|
||||
func ConfirmUsable(config Config, passedContextName string) error {
|
||||
func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
var contextName string
|
||||
@ -102,7 +103,7 @@ func ConfirmUsable(config Config, passedContextName string) error {
|
||||
}
|
||||
|
||||
// validateClusterInfo looks for conflicts and errors in the cluster info
|
||||
func validateClusterInfo(clusterName string, clusterInfo Cluster) []error {
|
||||
func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if len(clusterInfo.Server) == 0 {
|
||||
@ -120,7 +121,7 @@ func validateClusterInfo(clusterName string, clusterInfo Cluster) []error {
|
||||
}
|
||||
|
||||
// validateAuthInfo looks for conflicts and errors in the auth info
|
||||
func validateAuthInfo(authInfoName string, authInfo AuthInfo) []error {
|
||||
func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
usingAuthPath := false
|
||||
@ -163,7 +164,7 @@ func validateAuthInfo(authInfoName string, authInfo AuthInfo) []error {
|
||||
}
|
||||
|
||||
// validateContext looks for errors in the context. It is not transitive, so errors in the reference authInfo or cluster configs are not included in this return
|
||||
func validateContext(contextName string, context Context, config Config) []error {
|
||||
func validateContext(contextName string, context clientcmdapi.Context, config clientcmdapi.Config) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if len(context.AuthInfo) == 0 {
|
||||
|
@ -22,30 +22,31 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
)
|
||||
|
||||
func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.Clusters["missing ca"] = Cluster{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["missing ca"] = clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: "missing",
|
||||
}
|
||||
config.AuthInfos["error"] = AuthInfo{
|
||||
config.AuthInfos["error"] = clientcmdapi.AuthInfo{
|
||||
AuthPath: "anything",
|
||||
Token: "here",
|
||||
}
|
||||
config.Contexts["dirty"] = Context{
|
||||
config.Contexts["dirty"] = clientcmdapi.Context{
|
||||
Cluster: "missing ca",
|
||||
AuthInfo: "error",
|
||||
}
|
||||
config.Clusters["clean"] = Cluster{
|
||||
config.Clusters["clean"] = clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
}
|
||||
config.AuthInfos["clean"] = AuthInfo{
|
||||
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||
Token: "here",
|
||||
}
|
||||
config.Contexts["clean"] = Context{
|
||||
config.Contexts["clean"] = clientcmdapi.Context{
|
||||
Cluster: "clean",
|
||||
AuthInfo: "clean",
|
||||
}
|
||||
@ -62,16 +63,16 @@ func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
|
||||
badValidation.testConfig(t)
|
||||
}
|
||||
func TestConfirmUsableBadInfoConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.Clusters["missing ca"] = Cluster{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["missing ca"] = clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: "missing",
|
||||
}
|
||||
config.AuthInfos["error"] = AuthInfo{
|
||||
config.AuthInfos["error"] = clientcmdapi.AuthInfo{
|
||||
AuthPath: "anything",
|
||||
Token: "here",
|
||||
}
|
||||
config.Contexts["first"] = Context{
|
||||
config.Contexts["first"] = clientcmdapi.Context{
|
||||
Cluster: "missing ca",
|
||||
AuthInfo: "error",
|
||||
}
|
||||
@ -83,7 +84,7 @@ func TestConfirmUsableBadInfoConfig(t *testing.T) {
|
||||
test.testConfirmUsable("first", t)
|
||||
}
|
||||
func TestConfirmUsableEmptyConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"no context chosen"},
|
||||
@ -92,7 +93,7 @@ func TestConfirmUsableEmptyConfig(t *testing.T) {
|
||||
test.testConfirmUsable("", t)
|
||||
}
|
||||
func TestConfirmUsableMissingConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"context was not found for"},
|
||||
@ -101,7 +102,7 @@ func TestConfirmUsableMissingConfig(t *testing.T) {
|
||||
test.testConfirmUsable("not-here", t)
|
||||
}
|
||||
func TestValidateEmptyConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
@ -109,7 +110,7 @@ func TestValidateEmptyConfig(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateMissingCurrentContextConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
@ -119,7 +120,7 @@ func TestValidateMissingCurrentContextConfig(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestIsContextNotFound(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
|
||||
err := Validate(*config)
|
||||
@ -128,9 +129,9 @@ func TestIsContextNotFound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestValidateMissingReferencesConfig(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
config.Contexts["anything"] = Context{Cluster: "missing", AuthInfo: "missing"}
|
||||
config.Contexts["anything"] = clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"user, missing, was not found for Context anything", "cluster, missing, was not found for Context anything"},
|
||||
@ -140,9 +141,9 @@ func TestValidateMissingReferencesConfig(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateEmptyContext(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.CurrentContext = "anything"
|
||||
config.Contexts["anything"] = Context{}
|
||||
config.Contexts["anything"] = clientcmdapi.Context{}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"user was not specified for Context anything", "cluster was not specified for Context anything"},
|
||||
@ -153,8 +154,8 @@ func TestValidateEmptyContext(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateEmptyClusterInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.Clusters["empty"] = Cluster{}
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["empty"] = clientcmdapi.Cluster{}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
expectedErrorSubstring: []string{"no server found for"},
|
||||
@ -164,8 +165,8 @@ func TestValidateEmptyClusterInfo(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateMissingCAFileClusterInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.Clusters["missing ca"] = Cluster{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["missing ca"] = clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: "missing",
|
||||
}
|
||||
@ -178,8 +179,8 @@ func TestValidateMissingCAFileClusterInfo(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCleanClusterInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.Clusters["clean"] = Cluster{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
}
|
||||
test := configValidationTest{
|
||||
@ -193,8 +194,8 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
config := NewConfig()
|
||||
config.Clusters["clean"] = Cluster{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = clientcmdapi.Cluster{
|
||||
Server: "anything",
|
||||
CertificateAuthority: tempFile.Name(),
|
||||
}
|
||||
@ -207,8 +208,8 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateEmptyAuthInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.AuthInfos["error"] = AuthInfo{}
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["error"] = clientcmdapi.AuthInfo{}
|
||||
test := configValidationTest{
|
||||
config: config,
|
||||
}
|
||||
@ -217,8 +218,8 @@ func TestValidateEmptyAuthInfo(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidatePathNotFoundAuthInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.AuthInfos["error"] = AuthInfo{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["error"] = clientcmdapi.AuthInfo{
|
||||
AuthPath: "missing",
|
||||
}
|
||||
test := configValidationTest{
|
||||
@ -230,8 +231,8 @@ func TestValidatePathNotFoundAuthInfo(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.AuthInfos["error"] = AuthInfo{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["error"] = clientcmdapi.AuthInfo{
|
||||
ClientCertificate: "missing",
|
||||
ClientKey: "missing",
|
||||
}
|
||||
@ -247,8 +248,8 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
config := NewConfig()
|
||||
config.AuthInfos["clean"] = AuthInfo{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||
ClientCertificate: tempFile.Name(),
|
||||
ClientKey: tempFile.Name(),
|
||||
}
|
||||
@ -263,8 +264,8 @@ func TestValidateCleanPathAuthInfo(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
config := NewConfig()
|
||||
config.AuthInfos["clean"] = AuthInfo{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||
AuthPath: tempFile.Name(),
|
||||
}
|
||||
test := configValidationTest{
|
||||
@ -275,8 +276,8 @@ func TestValidateCleanPathAuthInfo(t *testing.T) {
|
||||
test.testConfig(t)
|
||||
}
|
||||
func TestValidateCleanTokenAuthInfo(t *testing.T) {
|
||||
config := NewConfig()
|
||||
config.AuthInfos["clean"] = AuthInfo{
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
|
||||
Token: "any-value",
|
||||
}
|
||||
test := configValidationTest{
|
||||
@ -288,7 +289,7 @@ func TestValidateCleanTokenAuthInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
type configValidationTest struct {
|
||||
config *Config
|
||||
config *clientcmdapi.Config
|
||||
expectedErrorSubstring []string
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
cmdconfig "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/config"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
@ -168,6 +169,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
|
||||
cmds.AddCommand(f.NewCmdUpdate(out))
|
||||
cmds.AddCommand(f.NewCmdDelete(out))
|
||||
|
||||
cmds.AddCommand(cmdconfig.NewCmdConfig(out))
|
||||
cmds.AddCommand(NewCmdNamespace(out))
|
||||
cmds.AddCommand(f.NewCmdLog(out))
|
||||
cmds.AddCommand(f.NewCmdRollingUpdate(out))
|
||||
@ -213,7 +215,7 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
|
||||
flags.StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
|
||||
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
overrides.BindFlags(flags, clientcmd.RecommendedConfigOverrideFlags(""))
|
||||
clientcmd.BindOverrideFlags(overrides, flags, clientcmd.RecommendedConfigOverrideFlags(""))
|
||||
clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)
|
||||
|
||||
return clientConfig
|
||||
|
111
pkg/kubectl/cmd/config/config.go
Normal file
111
pkg/kubectl/cmd/config/config.go
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
type pathOptions struct {
|
||||
local bool
|
||||
global bool
|
||||
specifiedFile string
|
||||
}
|
||||
|
||||
func NewCmdConfig(out io.Writer) *cobra.Command {
|
||||
pathOptions := &pathOptions{}
|
||||
|
||||
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"`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
// file paths are common to all sub commands
|
||||
cmd.PersistentFlags().BoolVar(&pathOptions.local, "local", true, "use the .kubeconfig in the currect directory")
|
||||
cmd.PersistentFlags().BoolVar(&pathOptions.global, "global", false, "use the .kubeconfig from "+os.Getenv("HOME"))
|
||||
cmd.PersistentFlags().StringVar(&pathOptions.specifiedFile, "kubeconfig", "", "use a particular .kubeconfig file")
|
||||
|
||||
cmd.AddCommand(NewCmdConfigView(out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetCluster(out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetAuthInfo(out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetContext(out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSet(out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigUnset(out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigUseContext(out, pathOptions))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *pathOptions) getStartingConfig() (*clientcmdapi.Config, string, error) {
|
||||
filename := ""
|
||||
config := clientcmdapi.NewConfig()
|
||||
switch {
|
||||
case o.global:
|
||||
filename = os.Getenv("HOME") + "/.kube/.kubeconfig"
|
||||
config = getConfigFromFileOrDie(filename)
|
||||
|
||||
case len(o.specifiedFile) > 0:
|
||||
filename = o.specifiedFile
|
||||
config = getConfigFromFileOrDie(filename)
|
||||
|
||||
case o.local:
|
||||
filename = ".kubeconfig"
|
||||
config = getConfigFromFileOrDie(filename)
|
||||
}
|
||||
|
||||
return config, filename, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
func getConfigFromFileOrDie(filename string) *clientcmdapi.Config {
|
||||
var err error
|
||||
config, err := clientcmd.LoadFromFile(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
glog.FatalDepth(1, err)
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
config = clientcmdapi.NewConfig()
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func toBool(propertyValue string) (bool, error) {
|
||||
boolValue := false
|
||||
if len(propertyValue) != 0 {
|
||||
var err error
|
||||
boolValue, err = strconv.ParseBool(propertyValue)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return boolValue, nil
|
||||
}
|
374
pkg/kubectl/cmd/config/config_test.go
Normal file
374
pkg/kubectl/cmd/config/config_test.go
Normal file
@ -0,0 +1,374 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
func newRedFederalCowHammerConfig() clientcmdapi.Config {
|
||||
return clientcmdapi.Config{
|
||||
AuthInfos: map[string]clientcmdapi.AuthInfo{
|
||||
"red-user": {Token: "red-token"}},
|
||||
Clusters: map[string]clientcmdapi.Cluster{
|
||||
"cow-cluster": {Server: "http://cow.org:8080"}},
|
||||
Contexts: map[string]clientcmdapi.Context{
|
||||
"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}},
|
||||
}
|
||||
}
|
||||
|
||||
type configCommandTest struct {
|
||||
args []string
|
||||
startingConfig clientcmdapi.Config
|
||||
expectedConfig clientcmdapi.Config
|
||||
}
|
||||
|
||||
func TestSetCurrentContext(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
expectedConfig.CurrentContext = "the-new-context"
|
||||
test := configCommandTest{
|
||||
args: []string{"use-context", "the-new-context"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestSetIntoExistingStruct(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
a := expectedConfig.AuthInfos["red-user"]
|
||||
authInfo := &a
|
||||
authInfo.AuthPath = "new-path-value"
|
||||
expectedConfig.AuthInfos["red-user"] = *authInfo
|
||||
test := configCommandTest{
|
||||
args: []string{"set", "users.red-user.auth-path", "new-path-value"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestUnsetStruct(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
delete(expectedConfig.AuthInfos, "red-user")
|
||||
test := configCommandTest{
|
||||
args: []string{"unset", "users.red-user"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestUnsetField(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
expectedConfig.AuthInfos["red-user"] = *clientcmdapi.NewAuthInfo()
|
||||
test := configCommandTest{
|
||||
args: []string{"unset", "users.red-user.token"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestSetIntoNewStruct(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
cluster := clientcmdapi.NewCluster()
|
||||
cluster.Server = "new-server-value"
|
||||
expectedConfig.Clusters["big-cluster"] = *cluster
|
||||
test := configCommandTest{
|
||||
args: []string{"set", "clusters.big-cluster.server", "new-server-value"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestSetBoolean(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
cluster := clientcmdapi.NewCluster()
|
||||
cluster.InsecureSkipTLSVerify = true
|
||||
expectedConfig.Clusters["big-cluster"] = *cluster
|
||||
test := configCommandTest{
|
||||
args: []string{"set", "clusters.big-cluster.insecure-skip-tls-verify", "true"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestSetIntoNewConfig(t *testing.T) {
|
||||
expectedConfig := *clientcmdapi.NewConfig()
|
||||
context := clientcmdapi.NewContext()
|
||||
context.AuthInfo = "fake-user"
|
||||
expectedConfig.Contexts["new-context"] = *context
|
||||
test := configCommandTest{
|
||||
args: []string{"set", "contexts.new-context.user", "fake-user"},
|
||||
startingConfig: *clientcmdapi.NewConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestNewEmptyAuth(t *testing.T) {
|
||||
expectedConfig := *clientcmdapi.NewConfig()
|
||||
expectedConfig.AuthInfos["the-user-name"] = *clientcmdapi.NewAuthInfo()
|
||||
test := configCommandTest{
|
||||
args: []string{"set-credentials", "the-user-name"},
|
||||
startingConfig: *clientcmdapi.NewConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestAdditionalAuth(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
authInfo := clientcmdapi.NewAuthInfo()
|
||||
authInfo.AuthPath = "auth-path"
|
||||
authInfo.ClientKey = "client-key"
|
||||
authInfo.Token = "token"
|
||||
expectedConfig.AuthInfos["another-user"] = *authInfo
|
||||
test := configCommandTest{
|
||||
args: []string{"set-credentials", "another-user", "--" + clientcmd.FlagAuthPath + "=auth-path", "--" + clientcmd.FlagKeyFile + "=client-key", "--" + clientcmd.FlagBearerToken + "=token"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestOverwriteExistingAuth(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
authInfo := clientcmdapi.NewAuthInfo()
|
||||
authInfo.AuthPath = "auth-path"
|
||||
expectedConfig.AuthInfos["red-user"] = *authInfo
|
||||
test := configCommandTest{
|
||||
args: []string{"set-credentials", "red-user", "--" + clientcmd.FlagAuthPath + "=auth-path"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestNewEmptyCluster(t *testing.T) {
|
||||
expectedConfig := *clientcmdapi.NewConfig()
|
||||
expectedConfig.Clusters["new-cluster"] = *clientcmdapi.NewCluster()
|
||||
test := configCommandTest{
|
||||
args: []string{"set-cluster", "new-cluster"},
|
||||
startingConfig: *clientcmdapi.NewConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestAdditionalCluster(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
cluster := *clientcmdapi.NewCluster()
|
||||
cluster.APIVersion = "v1beta1"
|
||||
cluster.CertificateAuthority = "ca-location"
|
||||
cluster.InsecureSkipTLSVerify = true
|
||||
cluster.Server = "serverlocation"
|
||||
expectedConfig.Clusters["different-cluster"] = cluster
|
||||
test := configCommandTest{
|
||||
args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=true", "--" + clientcmd.FlagCAFile + "=ca-location", "--" + clientcmd.FlagAPIVersion + "=v1beta1"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestOverwriteExistingCluster(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
cluster := *clientcmdapi.NewCluster()
|
||||
cluster.Server = "serverlocation"
|
||||
expectedConfig.Clusters["cow-cluster"] = cluster
|
||||
|
||||
test := configCommandTest{
|
||||
args: []string{"set-cluster", "cow-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestNewEmptyContext(t *testing.T) {
|
||||
expectedConfig := *clientcmdapi.NewConfig()
|
||||
expectedConfig.Contexts["new-context"] = *clientcmdapi.NewContext()
|
||||
test := configCommandTest{
|
||||
args: []string{"set-context", "new-context"},
|
||||
startingConfig: *clientcmdapi.NewConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestAdditionalContext(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
context := *clientcmdapi.NewContext()
|
||||
context.Cluster = "some-cluster"
|
||||
context.AuthInfo = "some-user"
|
||||
context.Namespace = "different-namespace"
|
||||
expectedConfig.Contexts["different-context"] = context
|
||||
test := configCommandTest{
|
||||
args: []string{"set-context", "different-context", "--" + clientcmd.FlagClusterName + "=some-cluster", "--" + clientcmd.FlagAuthInfoName + "=some-user", "--" + clientcmd.FlagNamespace + "=different-namespace"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestOverwriteExistingContext(t *testing.T) {
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
context := *clientcmdapi.NewContext()
|
||||
context.Cluster = "clustername"
|
||||
expectedConfig.Contexts["federal-context"] = context
|
||||
|
||||
test := configCommandTest{
|
||||
args: []string{"set-context", "federal-context", "--" + clientcmd.FlagClusterName + "=clustername"},
|
||||
startingConfig: newRedFederalCowHammerConfig(),
|
||||
expectedConfig: expectedConfig,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestToBool(t *testing.T) {
|
||||
type test struct {
|
||||
in string
|
||||
out bool
|
||||
err string
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{"", false, ""},
|
||||
{"true", true, ""},
|
||||
{"on", false, `strconv.ParseBool: parsing "on": invalid syntax`},
|
||||
}
|
||||
|
||||
for _, curr := range tests {
|
||||
b, err := toBool(curr.in)
|
||||
if (len(curr.err) != 0) && err == nil {
|
||||
t.Errorf("Expected error: %v, but got nil", curr.err)
|
||||
}
|
||||
if (len(curr.err) == 0) && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if (err != nil) && (err.Error() != curr.err) {
|
||||
t.Errorf("Expected %v, got %v", curr.err, err)
|
||||
|
||||
}
|
||||
if b != curr.out {
|
||||
t.Errorf("Expected %v, got %v", curr.out, b)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testConfigCommand(args []string, startingConfig clientcmdapi.Config) (string, clientcmdapi.Config) {
|
||||
fakeKubeFile, _ := ioutil.TempFile("", "")
|
||||
defer os.Remove(fakeKubeFile.Name())
|
||||
clientcmd.WriteToFile(startingConfig, fakeKubeFile.Name())
|
||||
|
||||
argsToUse := make([]string, 0, 2+len(args))
|
||||
argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name())
|
||||
argsToUse = append(argsToUse, args...)
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := NewCmdConfig(buf)
|
||||
cmd.SetArgs(argsToUse)
|
||||
cmd.Execute()
|
||||
|
||||
// outBytes, _ := ioutil.ReadFile(fakeKubeFile.Name())
|
||||
config := getConfigFromFileOrDie(fakeKubeFile.Name())
|
||||
|
||||
return buf.String(), *config
|
||||
}
|
||||
|
||||
func (test configCommandTest) run(t *testing.T) {
|
||||
_, actualConfig := testConfigCommand(test.args, test.startingConfig)
|
||||
|
||||
testSetNilMapsToEmpties(reflect.ValueOf(&test.expectedConfig))
|
||||
testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig))
|
||||
|
||||
if !reflect.DeepEqual(test.expectedConfig, actualConfig) {
|
||||
t.Errorf("diff: %v", util.ObjectDiff(test.expectedConfig, actualConfig))
|
||||
t.Errorf("expected: %#v\n actual: %#v", test.expectedConfig, actualConfig)
|
||||
}
|
||||
}
|
||||
func testSetNilMapsToEmpties(curr reflect.Value) {
|
||||
actualCurrValue := curr
|
||||
if curr.Kind() == reflect.Ptr {
|
||||
actualCurrValue = curr.Elem()
|
||||
}
|
||||
|
||||
switch actualCurrValue.Kind() {
|
||||
case reflect.Map:
|
||||
for _, mapKey := range actualCurrValue.MapKeys() {
|
||||
currMapValue := actualCurrValue.MapIndex(mapKey)
|
||||
|
||||
// our maps do not hold pointers to structs, they hold the structs themselves. This means that MapIndex returns the struct itself
|
||||
// That in turn means that they have kinds of type.Struct, which is not a settable type. Because of this, we need to make new struct of that type
|
||||
// copy all the data from the old value into the new value, then take the .addr of the new value to modify it in the next recursion.
|
||||
// clear as mud
|
||||
modifiableMapValue := reflect.New(currMapValue.Type()).Elem()
|
||||
modifiableMapValue.Set(currMapValue)
|
||||
|
||||
if modifiableMapValue.Kind() == reflect.Struct {
|
||||
modifiableMapValue = modifiableMapValue.Addr()
|
||||
}
|
||||
|
||||
testSetNilMapsToEmpties(modifiableMapValue)
|
||||
actualCurrValue.SetMapIndex(mapKey, reflect.Indirect(modifiableMapValue))
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
|
||||
currFieldValue := actualCurrValue.Field(fieldIndex)
|
||||
|
||||
if currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil() {
|
||||
newValue := reflect.MakeMap(currFieldValue.Type())
|
||||
currFieldValue.Set(newValue)
|
||||
} else {
|
||||
testSetNilMapsToEmpties(currFieldValue.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
120
pkg/kubectl/cmd/config/create_authinfo.go
Normal file
120
pkg/kubectl/cmd/config/create_authinfo.go
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
type createAuthInfoOptions struct {
|
||||
pathOptions *pathOptions
|
||||
name string
|
||||
authPath string
|
||||
clientCertificate string
|
||||
clientKey string
|
||||
token string
|
||||
}
|
||||
|
||||
func NewCmdConfigSetAuthInfo(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &createAuthInfoOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-credentials name",
|
||||
Short: "Sets a user entry in .kubeconfig",
|
||||
Long: `Sets a user entry in .kubeconfig
|
||||
|
||||
Specifying a name that already exists overwrites that user entry.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if !options.complete(cmd) {
|
||||
return
|
||||
}
|
||||
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&options.authPath, clientcmd.FlagAuthPath, "", clientcmd.FlagAuthPath+" for the user entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.clientCertificate, clientcmd.FlagCertFile, "", clientcmd.FlagCertFile+" for the user entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.clientKey, clientcmd.FlagKeyFile, "", clientcmd.FlagKeyFile+" for the user entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.token, clientcmd.FlagBearerToken, "", clientcmd.FlagBearerToken+" for the user entry in .kubeconfig")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o createAuthInfoOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, filename, err := o.pathOptions.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authInfo := o.authInfo()
|
||||
config.AuthInfos[o.name] = authInfo
|
||||
|
||||
err = clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// authInfo builds an AuthInfo object from the options
|
||||
func (o *createAuthInfoOptions) authInfo() clientcmdapi.AuthInfo {
|
||||
authInfo := clientcmdapi.AuthInfo{
|
||||
AuthPath: o.authPath,
|
||||
ClientCertificate: o.clientCertificate,
|
||||
ClientKey: o.clientKey,
|
||||
Token: o.token,
|
||||
}
|
||||
|
||||
return authInfo
|
||||
}
|
||||
|
||||
func (o *createAuthInfoOptions) complete(cmd *cobra.Command) bool {
|
||||
args := cmd.Flags().Args()
|
||||
if len(args) != 1 {
|
||||
cmd.Help()
|
||||
return false
|
||||
}
|
||||
|
||||
o.name = args[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (o createAuthInfoOptions) validate() error {
|
||||
if len(o.name) == 0 {
|
||||
return errors.New("You must specify a non-empty user name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
124
pkg/kubectl/cmd/config/create_cluster.go
Normal file
124
pkg/kubectl/cmd/config/create_cluster.go
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
type createClusterOptions struct {
|
||||
pathOptions *pathOptions
|
||||
name string
|
||||
server string
|
||||
apiVersion string
|
||||
insecureSkipTLSVerify bool
|
||||
certificateAuthority string
|
||||
}
|
||||
|
||||
func NewCmdConfigSetCluster(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &createClusterOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-cluster name [server] [insecure-skip-tls-verify] [certificate-authority] [api-version]",
|
||||
Short: "Sets a cluster entry in .kubeconfig",
|
||||
Long: `Sets a cluster entry in .kubeconfig
|
||||
|
||||
Specifying a name that already exists overwrites that cluster entry.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if !options.complete(cmd) {
|
||||
return
|
||||
}
|
||||
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&options.server, clientcmd.FlagAPIServer, "", clientcmd.FlagAPIServer+" for the cluster entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.apiVersion, clientcmd.FlagAPIVersion, "", clientcmd.FlagAPIVersion+" for the cluster entry in .kubeconfig")
|
||||
cmd.Flags().BoolVar(&options.insecureSkipTLSVerify, clientcmd.FlagInsecure, false, clientcmd.FlagInsecure+" for the cluster entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.certificateAuthority, clientcmd.FlagCAFile, "", clientcmd.FlagCAFile+" for the cluster entry in .kubeconfig")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o createClusterOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, filename, err := o.pathOptions.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.Clusters == nil {
|
||||
config.Clusters = make(map[string]clientcmdapi.Cluster)
|
||||
}
|
||||
|
||||
cluster := o.cluster()
|
||||
config.Clusters[o.name] = cluster
|
||||
|
||||
err = clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cluster builds a Cluster object from the options
|
||||
func (o *createClusterOptions) cluster() clientcmdapi.Cluster {
|
||||
cluster := clientcmdapi.Cluster{
|
||||
Server: o.server,
|
||||
APIVersion: o.apiVersion,
|
||||
InsecureSkipTLSVerify: o.insecureSkipTLSVerify,
|
||||
CertificateAuthority: o.certificateAuthority,
|
||||
}
|
||||
|
||||
return cluster
|
||||
}
|
||||
|
||||
func (o *createClusterOptions) complete(cmd *cobra.Command) bool {
|
||||
args := cmd.Flags().Args()
|
||||
if len(args) != 1 {
|
||||
cmd.Help()
|
||||
return false
|
||||
}
|
||||
|
||||
o.name = args[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (o createClusterOptions) validate() error {
|
||||
if len(o.name) == 0 {
|
||||
return errors.New("You must specify a non-empty cluster name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
116
pkg/kubectl/cmd/config/create_context.go
Normal file
116
pkg/kubectl/cmd/config/create_context.go
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
type createContextOptions struct {
|
||||
pathOptions *pathOptions
|
||||
name string
|
||||
cluster string
|
||||
authInfo string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewCmdConfigSetContext(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &createContextOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "set-context name",
|
||||
Short: "Sets a context entry in .kubeconfig",
|
||||
Long: `Sets a context entry in .kubeconfig
|
||||
|
||||
Specifying a name that already exists overwrites that context entry.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if !options.complete(cmd) {
|
||||
return
|
||||
}
|
||||
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&options.cluster, clientcmd.FlagClusterName, "", clientcmd.FlagClusterName+" for the context entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.authInfo, clientcmd.FlagAuthInfoName, "", clientcmd.FlagAuthInfoName+" for the context entry in .kubeconfig")
|
||||
cmd.Flags().StringVar(&options.namespace, clientcmd.FlagNamespace, "", clientcmd.FlagNamespace+" for the context entry in .kubeconfig")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o createContextOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, filename, err := o.pathOptions.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context := o.context()
|
||||
config.Contexts[o.name] = context
|
||||
|
||||
err = clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *createContextOptions) context() clientcmdapi.Context {
|
||||
context := clientcmdapi.Context{
|
||||
Cluster: o.cluster,
|
||||
AuthInfo: o.authInfo,
|
||||
Namespace: o.namespace,
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
func (o *createContextOptions) complete(cmd *cobra.Command) bool {
|
||||
args := cmd.Flags().Args()
|
||||
if len(args) != 1 {
|
||||
cmd.Help()
|
||||
return false
|
||||
}
|
||||
|
||||
o.name = args[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (o createContextOptions) validate() error {
|
||||
if len(o.name) == 0 {
|
||||
return errors.New("You must specify a non-empty context name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
249
pkg/kubectl/cmd/config/set.go
Normal file
249
pkg/kubectl/cmd/config/set.go
Normal file
@ -0,0 +1,249 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
)
|
||||
|
||||
const (
|
||||
cannotHaveStepsAfterError = "Cannot have steps after %v. %v are remaining"
|
||||
additionStepRequiredUnlessUnsettingError = "Must have additional steps after %v unless you are unsetting it"
|
||||
)
|
||||
|
||||
type navigationSteps []string
|
||||
|
||||
type setOptions struct {
|
||||
pathOptions *pathOptions
|
||||
propertyName string
|
||||
propertyValue string
|
||||
}
|
||||
|
||||
func NewCmdConfigSet(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &setOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "set property-name property-value",
|
||||
Short: "Sets an individual value in a .kubeconfig file",
|
||||
Long: `Sets an individual value in a .kubeconfig file
|
||||
|
||||
property-name is a dot delimitted 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.
|
||||
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if !options.complete(cmd) {
|
||||
return
|
||||
}
|
||||
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o setOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, filename, err := o.pathOptions.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(filename) == 0 {
|
||||
return errors.New("cannot set property without using a specific file")
|
||||
}
|
||||
|
||||
parts := strings.Split(o.propertyName, ".")
|
||||
err = modifyConfig(reflect.ValueOf(config), parts, o.propertyValue, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *setOptions) complete(cmd *cobra.Command) bool {
|
||||
endingArgs := cmd.Flags().Args()
|
||||
if len(endingArgs) != 2 {
|
||||
cmd.Help()
|
||||
return false
|
||||
}
|
||||
|
||||
o.propertyValue = endingArgs[1]
|
||||
o.propertyName = endingArgs[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (o setOptions) validate() error {
|
||||
if len(o.propertyValue) == 0 {
|
||||
return errors.New("You cannot use set to unset a property")
|
||||
}
|
||||
|
||||
if len(o.propertyName) == 0 {
|
||||
return errors.New("You must specify a property")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// moreStepsRemaining just makes code read cleaner
|
||||
func moreStepsRemaining(remainder []string) bool {
|
||||
return len(remainder) != 0
|
||||
}
|
||||
|
||||
func (s navigationSteps) nextSteps() navigationSteps {
|
||||
if len(s) < 2 {
|
||||
return make([]string, 0, 0)
|
||||
} else {
|
||||
return s[1:]
|
||||
}
|
||||
}
|
||||
func (s navigationSteps) moreStepsRemaining() bool {
|
||||
return len(s) != 0
|
||||
}
|
||||
func (s navigationSteps) nextStep() string {
|
||||
return s[0]
|
||||
}
|
||||
|
||||
func modifyConfig(curr reflect.Value, steps navigationSteps, propertyValue string, unset bool) error {
|
||||
shouldUnsetNextField := !steps.nextSteps().moreStepsRemaining() && unset
|
||||
shouldSetThisField := !steps.moreStepsRemaining() && !unset
|
||||
|
||||
actualCurrValue := curr
|
||||
if curr.Kind() == reflect.Ptr {
|
||||
actualCurrValue = curr.Elem()
|
||||
}
|
||||
|
||||
switch actualCurrValue.Kind() {
|
||||
case reflect.Map:
|
||||
if shouldSetThisField {
|
||||
return fmt.Errorf("Can't set a map to a value: %v", actualCurrValue)
|
||||
}
|
||||
|
||||
mapKey := reflect.ValueOf(steps.nextStep())
|
||||
mapValueType := curr.Type().Elem().Elem()
|
||||
|
||||
if shouldUnsetNextField {
|
||||
actualCurrValue.SetMapIndex(mapKey, reflect.Value{})
|
||||
return nil
|
||||
}
|
||||
|
||||
currMapValue := actualCurrValue.MapIndex(mapKey)
|
||||
|
||||
needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid
|
||||
if needToSetNewMapValue {
|
||||
currMapValue = reflect.New(mapValueType).Elem()
|
||||
actualCurrValue.SetMapIndex(mapKey, currMapValue)
|
||||
}
|
||||
|
||||
// our maps do not hold pointers to structs, they hold the structs themselves. This means that MapIndex returns the struct itself
|
||||
// That in turn means that they have kinds of type.Struct, which is not a settable type. Because of this, we need to make new struct of that type
|
||||
// copy all the data from the old value into the new value, then take the .addr of the new value to modify it in the next recursion.
|
||||
// clear as mud
|
||||
modifiableMapValue := reflect.New(currMapValue.Type()).Elem()
|
||||
modifiableMapValue.Set(currMapValue)
|
||||
|
||||
if modifiableMapValue.Kind() == reflect.Struct {
|
||||
modifiableMapValue = modifiableMapValue.Addr()
|
||||
}
|
||||
err := modifyConfig(modifiableMapValue, steps.nextSteps(), propertyValue, unset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actualCurrValue.SetMapIndex(mapKey, reflect.Indirect(modifiableMapValue))
|
||||
return nil
|
||||
|
||||
case reflect.String:
|
||||
if steps.moreStepsRemaining() {
|
||||
return fmt.Errorf("Can't have more steps after a string. %v", steps)
|
||||
}
|
||||
actualCurrValue.SetString(propertyValue)
|
||||
return nil
|
||||
|
||||
case reflect.Bool:
|
||||
if steps.moreStepsRemaining() {
|
||||
return fmt.Errorf("Can't have more steps after a bool. %v", steps)
|
||||
}
|
||||
boolValue, err := toBool(propertyValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actualCurrValue.SetBool(boolValue)
|
||||
return nil
|
||||
|
||||
case reflect.Struct:
|
||||
if !steps.moreStepsRemaining() {
|
||||
return fmt.Errorf("Can't set a struct to a value: %v", actualCurrValue)
|
||||
}
|
||||
|
||||
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
|
||||
currFieldValue := actualCurrValue.Field(fieldIndex)
|
||||
currFieldType := actualCurrValue.Type().Field(fieldIndex)
|
||||
currYamlTag := currFieldType.Tag.Get("json")
|
||||
currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
|
||||
|
||||
if currFieldTypeYamlName == steps.nextStep() {
|
||||
thisMapHasNoValue := (currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil())
|
||||
|
||||
if thisMapHasNoValue {
|
||||
newValue := reflect.MakeMap(currFieldValue.Type())
|
||||
currFieldValue.Set(newValue)
|
||||
|
||||
if shouldUnsetNextField {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if shouldUnsetNextField {
|
||||
// if we're supposed to unset the value or if the value is a map that doesn't exist, create a new value and overwrite
|
||||
newValue := reflect.New(currFieldValue.Type()).Elem()
|
||||
currFieldValue.Set(newValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
return modifyConfig(currFieldValue.Addr(), steps.nextSteps(), propertyValue, unset)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to locate path %v under %v", steps, actualCurrValue)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unrecognized type: %v", actualCurrValue)
|
||||
}
|
107
pkg/kubectl/cmd/config/unset.go
Normal file
107
pkg/kubectl/cmd/config/unset.go
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
)
|
||||
|
||||
type unsetOptions struct {
|
||||
pathOptions *pathOptions
|
||||
propertyName string
|
||||
}
|
||||
|
||||
func NewCmdConfigUnset(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &unsetOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "unset property-name",
|
||||
Short: "Unsets an individual value in a .kubeconfig file",
|
||||
Long: `Unsets an individual value in a .kubeconfig file
|
||||
|
||||
property-name is a dot delimitted name where each token represents either a attribute name or a map key. Map keys may not contain dots.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if !options.complete(cmd) {
|
||||
return
|
||||
}
|
||||
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o unsetOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, filename, err := o.pathOptions.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(filename) == 0 {
|
||||
return errors.New("cannot set property without using a specific file")
|
||||
}
|
||||
|
||||
parts := strings.Split(o.propertyName, ".")
|
||||
err = modifyConfig(reflect.ValueOf(config), parts, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *unsetOptions) complete(cmd *cobra.Command) bool {
|
||||
endingArgs := cmd.Flags().Args()
|
||||
if len(endingArgs) != 1 {
|
||||
cmd.Help()
|
||||
return false
|
||||
}
|
||||
|
||||
o.propertyName = endingArgs[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (o unsetOptions) validate() error {
|
||||
if len(o.propertyName) == 0 {
|
||||
return errors.New("You must specify a property")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
98
pkg/kubectl/cmd/config/use_context.go
Normal file
98
pkg/kubectl/cmd/config/use_context.go
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
)
|
||||
|
||||
type useContextOptions struct {
|
||||
pathOptions *pathOptions
|
||||
contextName string
|
||||
}
|
||||
|
||||
func NewCmdConfigUseContext(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &useContextOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "use-context context-name",
|
||||
Short: "Sets the current-context in a .kubeconfig file",
|
||||
Long: `Sets the current-context in a .kubeconfig file`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if !options.complete(cmd) {
|
||||
return
|
||||
}
|
||||
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o useContextOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, filename, err := o.pathOptions.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(filename) == 0 {
|
||||
return errors.New("cannot set current-context without using a specific file")
|
||||
}
|
||||
|
||||
config.CurrentContext = o.contextName
|
||||
|
||||
err = clientcmd.WriteToFile(*config, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *useContextOptions) complete(cmd *cobra.Command) bool {
|
||||
endingArgs := cmd.Flags().Args()
|
||||
if len(endingArgs) != 1 {
|
||||
cmd.Help()
|
||||
return false
|
||||
}
|
||||
|
||||
o.contextName = endingArgs[0]
|
||||
return true
|
||||
}
|
||||
|
||||
func (o useContextOptions) validate() error {
|
||||
if len(o.contextName) == 0 {
|
||||
return errors.New("You must specify a current-context")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
99
pkg/kubectl/cmd/config/view.go
Normal file
99
pkg/kubectl/cmd/config/view.go
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
|
||||
)
|
||||
|
||||
type viewOptions struct {
|
||||
pathOptions *pathOptions
|
||||
merge bool
|
||||
}
|
||||
|
||||
func NewCmdConfigView(out io.Writer, pathOptions *pathOptions) *cobra.Command {
|
||||
options := &viewOptions{pathOptions: pathOptions}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "displays the specified .kubeconfig file or a merged result",
|
||||
Long: `displays the specified .kubeconfig file or a merged result`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := options.run()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&options.merge, "merge", false, "merge together the full hierarchy of .kubeconfig files")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o viewOptions) run() error {
|
||||
err := o.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, _, err := o.getStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%v", string(content))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o viewOptions) validate() error {
|
||||
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, string, error) {
|
||||
switch {
|
||||
case o.merge:
|
||||
loadingRules := clientcmd.NewClientConfigLoadingRules()
|
||||
loadingRules.EnvVarPath = ""
|
||||
loadingRules.CommandLinePath = o.pathOptions.specifiedFile
|
||||
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
|
||||
|
||||
config, err := clientConfig.RawConfig()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Error getting config: %v", err)
|
||||
}
|
||||
return &config, "", nil
|
||||
default:
|
||||
return o.pathOptions.getStartingConfig()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user