Merge pull request #2 from roycaihw/move-config
Move config loader to go-base
This commit is contained in:
11
README.md
11
README.md
@@ -1 +1,10 @@
|
||||
# This repo is for testing new client library structure
|
||||
# go-base
|
||||
|
||||
This is the utility part of the [go client](https://github.com/kubernetes-client/go). It has been added to the main
|
||||
repo using git submodules.
|
||||
For more information refer to [clients-library-structure](https://github.com/kubernetes-client/community/blob/master/design-docs/clients-library-structure.md).
|
||||
|
||||
# Development
|
||||
Any changes to utilites in this repo should be send as a PR to this repo. After
|
||||
the PR is merged, developers should create another PR in the main repo to update
|
||||
the submodule.
|
||||
|
||||
27
config/api/README.md
Normal file
27
config/api/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Kubernetes Config type Definition and Deepcopy Utility
|
||||
|
||||
This directory contains type definition and deepcopy utility copied from
|
||||
k8s.io/client-go that are used for parsing and persist kube config yaml
|
||||
file.
|
||||
|
||||
### types.go
|
||||
|
||||
The Config type definition is copied from k8s.io/client-go/tools/clientcmd/api/v1/types.go
|
||||
for parsing the kube config yaml. The "k8s.io/apimachinery/pkg/runtime" dependency has
|
||||
been removed. An example of using this type definition to parse a kube config
|
||||
yaml is:
|
||||
|
||||
```go
|
||||
// Init an empty api.Config as unmarshal layout template
|
||||
c := api.Config{}
|
||||
err = yaml.Unmarshal(kubeConfig, &c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
```
|
||||
|
||||
### zz\_generated.deepcopy.go
|
||||
The Config type deepcopy util file is copied from
|
||||
k8s.io/client-go/tools/clientcmd/api/v1/zz\_generated.deepcopy.go
|
||||
for deepcopy the kube config. The "k8s.io/apimachinery/pkg/runtime" dependency has
|
||||
been removed.
|
||||
204
config/api/types.go
Normal file
204
config/api/types.go
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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
|
||||
|
||||
// NOTE: This Config type definition is copied from k8s.io/client-go/tools/clientcmd/api/v1/types.go
|
||||
// for parsing the kube config yaml. The "k8s.io/apimachinery/pkg/runtime" dependency has
|
||||
// been removed.
|
||||
|
||||
// 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
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type Config struct {
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// 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
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
type Preferences struct {
|
||||
// +optional
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
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"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
// +optional
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
// +optional
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
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 {
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||
// +optional
|
||||
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
// +optional
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||
// +optional
|
||||
ClientKeyData []byte `json:"client-key-data,omitempty"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Token string `json:"token,omitempty"`
|
||||
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
|
||||
// +optional
|
||||
TokenFile string `json:"tokenFile,omitempty"`
|
||||
// Impersonate is the username to imperonate. The name matches the flag.
|
||||
// +optional
|
||||
Impersonate string `json:"as,omitempty"`
|
||||
// ImpersonateGroups is the groups to imperonate.
|
||||
// +optional
|
||||
ImpersonateGroups []string `json:"as-groups,omitempty"`
|
||||
// ImpersonateUserExtra contains additional information for impersonated user.
|
||||
// +optional
|
||||
ImpersonateUserExtra map[string][]string `json:"as-user-extra,omitempty"`
|
||||
// Username is the username for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password is the password for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Password string `json:"password,omitempty"`
|
||||
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
|
||||
// Exec specifies a custom exec-based authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
Exec *ExecConfig `json:"exec,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
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
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
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 interface{} `json:"extension"`
|
||||
}
|
||||
|
||||
// AuthProviderConfig holds the configuration for a specified auth provider.
|
||||
type AuthProviderConfig struct {
|
||||
Name string `json:"name"`
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
// ExecConfig specifies a command to provide client credentials. The command is exec'd
|
||||
// and outputs structured stdout holding credentials.
|
||||
//
|
||||
// See the client.authentiction.k8s.io API group for specifications of the exact input
|
||||
// and output format
|
||||
type ExecConfig struct {
|
||||
// Command to execute.
|
||||
Command string `json:"command"`
|
||||
// Arguments to pass to the command when executing it.
|
||||
// +optional
|
||||
Args []string `json:"args"`
|
||||
// Env defines additional environment variables to expose to the process. These
|
||||
// are unioned with the host's environment, as well as variables client-go uses
|
||||
// to pass argument to the plugin.
|
||||
// +optional
|
||||
Env []ExecEnvVar `json:"env"`
|
||||
|
||||
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
|
||||
// the same encoding version as the input.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
}
|
||||
|
||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
||||
// credential plugin.
|
||||
type ExecEnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
344
config/api/zz_generated.deepcopy.go
Normal file
344
config/api/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,344 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
// NOTE: This Config type deepcopy util file is copied from k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.deepcopy.go
|
||||
// for deepcopy the kube config. The "k8s.io/apimachinery/pkg/runtime" dependency has
|
||||
// been removed.
|
||||
|
||||
package api
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthInfo) DeepCopyInto(out *AuthInfo) {
|
||||
*out = *in
|
||||
if in.ClientCertificateData != nil {
|
||||
in, out := &in.ClientCertificateData, &out.ClientCertificateData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClientKeyData != nil {
|
||||
in, out := &in.ClientKeyData, &out.ClientKeyData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImpersonateGroups != nil {
|
||||
in, out := &in.ImpersonateGroups, &out.ImpersonateGroups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImpersonateUserExtra != nil {
|
||||
in, out := &in.ImpersonateUserExtra, &out.ImpersonateUserExtra
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = make([]string, len(val))
|
||||
copy((*out)[key], val)
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.AuthProvider != nil {
|
||||
in, out := &in.AuthProvider, &out.AuthProvider
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(AuthProviderConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
if in.Exec != nil {
|
||||
in, out := &in.Exec, &out.Exec
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(ExecConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthInfo.
|
||||
func (in *AuthInfo) DeepCopy() *AuthInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AuthInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthProviderConfig) DeepCopyInto(out *AuthProviderConfig) {
|
||||
*out = *in
|
||||
if in.Config != nil {
|
||||
in, out := &in.Config, &out.Config
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthProviderConfig.
|
||||
func (in *AuthProviderConfig) DeepCopy() *AuthProviderConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AuthProviderConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Cluster) DeepCopyInto(out *Cluster) {
|
||||
*out = *in
|
||||
if in.CertificateAuthorityData != nil {
|
||||
in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
|
||||
func (in *Cluster) DeepCopy() *Cluster {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Cluster)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Config) DeepCopyInto(out *Config) {
|
||||
*out = *in
|
||||
in.Preferences.DeepCopyInto(&out.Preferences)
|
||||
if in.Clusters != nil {
|
||||
in, out := &in.Clusters, &out.Clusters
|
||||
*out = make([]NamedCluster, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AuthInfos != nil {
|
||||
in, out := &in.AuthInfos, &out.AuthInfos
|
||||
*out = make([]NamedAuthInfo, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Contexts != nil {
|
||||
in, out := &in.Contexts, &out.Contexts
|
||||
*out = make([]NamedContext, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||
func (in *Config) DeepCopy() *Config {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Config)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Context) DeepCopyInto(out *Context) {
|
||||
*out = *in
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Context.
|
||||
func (in *Context) DeepCopy() *Context {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Context)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecConfig) DeepCopyInto(out *ExecConfig) {
|
||||
*out = *in
|
||||
if in.Args != nil {
|
||||
in, out := &in.Args, &out.Args
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Env != nil {
|
||||
in, out := &in.Env, &out.Env
|
||||
*out = make([]ExecEnvVar, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecConfig.
|
||||
func (in *ExecConfig) DeepCopy() *ExecConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExecConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecEnvVar) DeepCopyInto(out *ExecEnvVar) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecEnvVar.
|
||||
func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExecEnvVar)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedAuthInfo) DeepCopyInto(out *NamedAuthInfo) {
|
||||
*out = *in
|
||||
in.AuthInfo.DeepCopyInto(&out.AuthInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedAuthInfo.
|
||||
func (in *NamedAuthInfo) DeepCopy() *NamedAuthInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedAuthInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedCluster) DeepCopyInto(out *NamedCluster) {
|
||||
*out = *in
|
||||
in.Cluster.DeepCopyInto(&out.Cluster)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedCluster.
|
||||
func (in *NamedCluster) DeepCopy() *NamedCluster {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedCluster)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedContext) DeepCopyInto(out *NamedContext) {
|
||||
*out = *in
|
||||
in.Context.DeepCopyInto(&out.Context)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedContext.
|
||||
func (in *NamedContext) DeepCopy() *NamedContext {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedContext)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedExtension) DeepCopyInto(out *NamedExtension) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedExtension.
|
||||
func (in *NamedExtension) DeepCopy() *NamedExtension {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedExtension)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Preferences) DeepCopyInto(out *Preferences) {
|
||||
*out = *in
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preferences.
|
||||
func (in *Preferences) DeepCopy() *Preferences {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Preferences)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
77
config/authentication.go
Normal file
77
config/authentication.go
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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/ioutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// time.Duration that prevents the client dropping valid credential
|
||||
// due to time skew
|
||||
expirySkewPreventionDelay = 5 * time.Minute
|
||||
)
|
||||
|
||||
// Read authentication from kube-config user section if exists.
|
||||
//
|
||||
// This function goes through various authentication methods in user
|
||||
// section of kube-config and stops if it finds a valid authentication
|
||||
// method. The order of authentication methods is:
|
||||
//
|
||||
// 1. GCP auth-provider
|
||||
// 2. token_data
|
||||
// 3. token field (point to a token file)
|
||||
// 4. username/password
|
||||
func (l *KubeConfigLoader) loadAuthentication() {
|
||||
// The function walks though authentication methods. It doesn't fail on
|
||||
// single method loading failure. It is each loading function's responsiblity
|
||||
// to log meaningful failure message. Kubeconfig is allowed to have no user
|
||||
// in current context, therefore it is allowed that no authentication is loaded.
|
||||
|
||||
if l.loadGCPToken() || l.loadUserToken() || l.loadUserPassToken() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (l *KubeConfigLoader) loadUserToken() bool {
|
||||
if l.user.Token == "" && l.user.TokenFile == "" {
|
||||
return false
|
||||
}
|
||||
// Token takes precedence than TokenFile
|
||||
if l.user.Token != "" {
|
||||
l.restConfig.token = "Bearer " + l.user.Token
|
||||
return true
|
||||
}
|
||||
|
||||
// Read TokenFile
|
||||
token, err := ioutil.ReadFile(l.user.TokenFile)
|
||||
if err != nil {
|
||||
// A user may not provide any TokenFile, so we don't log error here
|
||||
return false
|
||||
}
|
||||
l.restConfig.token = "Bearer " + string(token)
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *KubeConfigLoader) loadUserPassToken() bool {
|
||||
if l.user.Username != "" && l.user.Password != "" {
|
||||
l.restConfig.token = basicAuthToken(l.user.Username, l.user.Password)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
111
config/gcp.go
Normal file
111
config/gcp.go
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
const (
|
||||
gcpRFC3339Format = "2006-01-02 15:04:05"
|
||||
)
|
||||
|
||||
// GoogleCredentialLoader defines the interface for getting GCP token
|
||||
type GoogleCredentialLoader interface {
|
||||
GetGoogleCredentials() (*oauth2.Token, error)
|
||||
}
|
||||
|
||||
func (l *KubeConfigLoader) loadGCPToken() bool {
|
||||
if l.user.AuthProvider == nil || l.user.AuthProvider.Name != "gcp" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Refresh GCP token if necessary
|
||||
if l.user.AuthProvider.Config == nil {
|
||||
if err := l.refreshGCPToken(); err != nil {
|
||||
glog.Errorf("failed to refresh GCP token: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if _, ok := l.user.AuthProvider.Config["expiry"]; !ok {
|
||||
if err := l.refreshGCPToken(); err != nil {
|
||||
glog.Errorf("failed to refresh GCP token: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
expired, err := isExpired(l.user.AuthProvider.Config["expiry"])
|
||||
if err != nil {
|
||||
glog.Errorf("failed to determine if GCP token is expired: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if expired {
|
||||
if err := l.refreshGCPToken(); err != nil {
|
||||
glog.Errorf("failed to refresh GCP token: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Use GCP access token
|
||||
l.restConfig.token = "Bearer " + l.user.AuthProvider.Config["access-token"]
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *KubeConfigLoader) refreshGCPToken() error {
|
||||
if l.user.AuthProvider.Config == nil {
|
||||
l.user.AuthProvider.Config = map[string]string{}
|
||||
}
|
||||
|
||||
// Get *oauth2.Token through Google APIs
|
||||
if l.gcLoader == nil {
|
||||
l.gcLoader = DefaultGoogleCredentialLoader{}
|
||||
}
|
||||
credentials, err := l.gcLoader.GetGoogleCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store credentials to Config
|
||||
l.user.AuthProvider.Config["access-token"] = credentials.AccessToken
|
||||
l.user.AuthProvider.Config["expiry"] = credentials.Expiry.Format(gcpRFC3339Format)
|
||||
|
||||
setUserWithName(l.rawConfig.AuthInfos, l.currentContext.AuthInfo, &l.user)
|
||||
// Persist kube config file
|
||||
if !l.skipConfigPersist {
|
||||
if err := l.persistConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultGoogleCredentialLoader provides the default method for getting GCP token
|
||||
type DefaultGoogleCredentialLoader struct{}
|
||||
|
||||
// GetGoogleCredentials fetches GCP using default locations
|
||||
func (l DefaultGoogleCredentialLoader) GetGoogleCredentials() (*oauth2.Token, error) {
|
||||
credentials, err := google.FindDefaultCredentials(context.Background(), "https://www.googleapis.com/auth/cloud-platform")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Google credentials: %v", err)
|
||||
}
|
||||
return credentials.TokenSource.Token()
|
||||
}
|
||||
74
config/incluster_config.go
Normal file
74
config/incluster_config.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"k8s.io/client/kubernetes/client"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceHostEnvName = "KUBERNETES_SERVICE_HOST"
|
||||
servicePortEnvName = "KUBERNETES_SERVICE_PORT"
|
||||
serviceTokenFilename = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
serviceCertFilename = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
)
|
||||
|
||||
// InClusterConfig returns a config object which uses the service account
|
||||
// kubernetes gives to pods. It's intended for clients that expect to be
|
||||
// running inside a pod running on kubernetes. It will return an error if
|
||||
// called from a process not running in a kubernetes environment.
|
||||
func InClusterConfig() (*client.Configuration, error) {
|
||||
host, port := os.Getenv(serviceHostEnvName), os.Getenv(servicePortEnvName)
|
||||
if len(host) == 0 || len(port) == 0 {
|
||||
return nil, fmt.Errorf("unable to load in-cluster configuration, %v and %v must be defined", serviceHostEnvName, servicePortEnvName)
|
||||
}
|
||||
|
||||
token, err := ioutil.ReadFile(serviceTokenFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCert, err := ioutil.ReadFile(serviceCertFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
c := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &client.Configuration{
|
||||
BasePath: "https://" + net.JoinHostPort(host, port),
|
||||
Host: net.JoinHostPort(host, port),
|
||||
Scheme: "https",
|
||||
DefaultHeader: map[string]string{"Authentication": "Bearer " + string(token)},
|
||||
UserAgent: defaultUserAgent,
|
||||
HTTPClient: c,
|
||||
}, nil
|
||||
}
|
||||
277
config/kube_config.go
Normal file
277
config/kube_config.go
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/client/kubernetes/client"
|
||||
"k8s.io/client/kubernetes/config/api"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "Swagger-Codegen/0.1.0a1/go"
|
||||
kubeConfigEnvName = "KUBECONFIG"
|
||||
kubeConfigDefaultFilename = "~/.kube/config"
|
||||
)
|
||||
|
||||
// KubeConfigLoader implements the util functions to load authentication and cluster
|
||||
// info and hosts intermediate info values.
|
||||
type KubeConfigLoader struct {
|
||||
rawConfig api.Config
|
||||
restConfig RestConfig
|
||||
|
||||
// Skip config persistence, default to false
|
||||
skipConfigPersist bool
|
||||
configFilename string
|
||||
|
||||
// Current cluster, user and context
|
||||
cluster api.Cluster
|
||||
user api.AuthInfo
|
||||
currentContext api.Context
|
||||
|
||||
// Set this interface to pass in custom Google credential loader instead of
|
||||
// using the default loader
|
||||
gcLoader GoogleCredentialLoader
|
||||
}
|
||||
|
||||
// RestConfig contains the information that a rest client needs to talk with a server
|
||||
type RestConfig struct {
|
||||
basePath string
|
||||
host string
|
||||
scheme string
|
||||
|
||||
// authentication token
|
||||
token string
|
||||
|
||||
// TLS info
|
||||
caCert []byte
|
||||
clientCert []byte
|
||||
clientKey []byte
|
||||
|
||||
// skip TLS verification, default to false
|
||||
skipTLSVerify bool
|
||||
}
|
||||
|
||||
// LoadKubeConfig loads authentication and cluster information from kube-config file
|
||||
// and stores them in returned client.Configuration.
|
||||
func LoadKubeConfig() (*client.Configuration, error) {
|
||||
kubeConfigFilename := os.Getenv(kubeConfigEnvName)
|
||||
// Fallback to default kubeconfig file location if no env variable set
|
||||
if kubeConfigFilename == "" {
|
||||
kubeConfigFilename = kubeConfigDefaultFilename
|
||||
}
|
||||
|
||||
loader, err := NewKubeConfigLoaderFromYAMLFile(kubeConfigFilename, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loader.LoadAndSet()
|
||||
}
|
||||
|
||||
// NewKubeConfigLoaderFromYAMLFile creates a new KubeConfigLoader with a parsed
|
||||
// config yaml file.
|
||||
func NewKubeConfigLoaderFromYAMLFile(filename string, skipConfigPersist bool) (*KubeConfigLoader, error) {
|
||||
kubeConfig, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Init an empty api.Config as unmarshal layout template
|
||||
c := api.Config{}
|
||||
if err := yaml.Unmarshal(kubeConfig, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := KubeConfigLoader{
|
||||
rawConfig: c,
|
||||
skipConfigPersist: skipConfigPersist,
|
||||
configFilename: filename,
|
||||
}
|
||||
|
||||
// Init loader with current cluster, user and context
|
||||
if err := l.LoadActiveContext(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// LoadAndSet loads authentication and cluster information from kube-config file and
|
||||
// stores them in returned client.Configuration.
|
||||
func (l *KubeConfigLoader) LoadAndSet() (*client.Configuration, error) {
|
||||
l.loadAuthentication()
|
||||
|
||||
if err := l.loadClusterInfo(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.setConfig()
|
||||
}
|
||||
|
||||
// loadClusterInfo uses the current cluster, user and context info stored in loader and
|
||||
// gets necessary TLS information
|
||||
func (l *KubeConfigLoader) loadClusterInfo() error {
|
||||
// The swagger-codegen go client doesn't work well with base path having trailing slash.
|
||||
// This is a short term fix.
|
||||
l.restConfig.basePath = strings.TrimRight(l.cluster.Server, "/")
|
||||
|
||||
u, err := url.Parse(l.cluster.Server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.restConfig.host = u.Host
|
||||
l.restConfig.scheme = u.Scheme
|
||||
|
||||
if l.cluster.InsecureSkipTLSVerify {
|
||||
l.restConfig.skipTLSVerify = true
|
||||
}
|
||||
|
||||
if l.restConfig.scheme == "https" {
|
||||
if !l.restConfig.skipTLSVerify {
|
||||
l.restConfig.caCert, err = DataOrFile(l.cluster.CertificateAuthorityData, l.cluster.CertificateAuthority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.restConfig.clientCert, err = DataOrFile(l.user.ClientCertificateData, l.user.ClientCertificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.restConfig.clientKey, err = DataOrFile(l.user.ClientKeyData, l.user.ClientKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setConfig converts authentication and TLS info into client Configuration
|
||||
func (l *KubeConfigLoader) setConfig() (*client.Configuration, error) {
|
||||
// Set TLS info
|
||||
transport := http.Transport{}
|
||||
if l.restConfig.scheme == "https" && !l.restConfig.skipTLSVerify {
|
||||
cert, err := tls.X509KeyPair(l.restConfig.clientCert, l.restConfig.clientKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(l.restConfig.caCert)
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
c := &http.Client{
|
||||
Transport: &transport,
|
||||
}
|
||||
|
||||
header := make(map[string]string)
|
||||
// Add authentication info to default header
|
||||
if l.restConfig.token != "" {
|
||||
header["Authorization"] = l.restConfig.token
|
||||
// Handle Golang dropping headers on redirect
|
||||
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
req.Header.Add("Authorization", l.restConfig.token)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &client.Configuration{
|
||||
BasePath: l.restConfig.basePath,
|
||||
Host: l.restConfig.host,
|
||||
Scheme: l.restConfig.scheme,
|
||||
DefaultHeader: header,
|
||||
UserAgent: defaultUserAgent,
|
||||
HTTPClient: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RestConfig returns the value of RestConfig in a KubeConfigLoader
|
||||
func (l *KubeConfigLoader) RestConfig() RestConfig {
|
||||
return l.restConfig
|
||||
}
|
||||
|
||||
// SetActiveContext sets the active context in rawConfig, performs necessary persistence,
|
||||
// and reload active context. This function enables context switch
|
||||
func (l *KubeConfigLoader) SetActiveContext(ctx string) error {
|
||||
currentContext, err := getContextWithName(l.rawConfig.Contexts, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentContext.DeepCopyInto(&l.currentContext)
|
||||
l.rawConfig.CurrentContext = ctx
|
||||
|
||||
// Persist kube config file
|
||||
if !l.skipConfigPersist {
|
||||
if err := l.persistConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return l.LoadActiveContext()
|
||||
}
|
||||
|
||||
// LoadActiveContext parses the loader's rawConfig using current context and set loader's
|
||||
// current cluster and user.
|
||||
func (l *KubeConfigLoader) LoadActiveContext() error {
|
||||
currentContext, err := getContextWithName(l.rawConfig.Contexts, l.rawConfig.CurrentContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentContext.DeepCopyInto(&l.currentContext)
|
||||
|
||||
cluster, err := getClusterWithName(l.rawConfig.Clusters, l.currentContext.Cluster)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cluster.DeepCopyInto(&l.cluster)
|
||||
|
||||
user, err := getUserWithName(l.rawConfig.AuthInfos, l.currentContext.AuthInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kube config may have no (current) user
|
||||
if user != nil {
|
||||
user.DeepCopyInto(&l.user)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// persisConfig saves the stored rawConfig to the config file. This function is not exposed and
|
||||
// should be called only when skipConfigPersist is false.
|
||||
// TODO(roycaihw): enable custom persistConfig function
|
||||
func (l *KubeConfigLoader) persistConfig() error {
|
||||
if l.skipConfigPersist {
|
||||
return nil
|
||||
}
|
||||
data, err := yaml.Marshal(l.rawConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(l.configFilename, data, 0644)
|
||||
}
|
||||
484
config/kube_config_test.go
Normal file
484
config/kube_config_test.go
Normal file
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"k8s.io/client/kubernetes/config/api"
|
||||
)
|
||||
|
||||
const (
|
||||
testData = "test-data"
|
||||
testAnotherData = "test-another-data"
|
||||
|
||||
testServer = "http://test-server"
|
||||
testUsername = "me"
|
||||
testPassword = "pass"
|
||||
|
||||
// token for me:pass
|
||||
testBasicToken = "Basic bWU6cGFzcw=="
|
||||
|
||||
testSSLServer = "https://test-server"
|
||||
testCertAuth = "cert-auth"
|
||||
testClientKey = "client-key"
|
||||
testClientCert = "client-cert"
|
||||
|
||||
bearerTokenFormat = "Bearer %s"
|
||||
testTokenExpiry = "2000-01-01 12:00:00" // always in past
|
||||
)
|
||||
|
||||
var (
|
||||
// base64 encoded string, used as a test token
|
||||
testDataBase64 = b64.StdEncoding.EncodeToString([]byte(testData))
|
||||
|
||||
// base64 encoded string, used as another test token
|
||||
testAnotherDataBase64 = b64.StdEncoding.EncodeToString([]byte(testAnotherData))
|
||||
|
||||
testCertAuthBase64 = stringToBase64(testCertAuth)
|
||||
|
||||
testClientKeyBase64 = stringToBase64(testClientKey)
|
||||
|
||||
testClientCertBase64 = stringToBase64(testClientCert)
|
||||
|
||||
// test time set to time.Now() + 2 * expirySkewPreventionDelay, which doesn't expire
|
||||
testTokenNoExpiry = time.Now().Add(2 * expirySkewPreventionDelay).UTC().Format(gcpRFC3339Format)
|
||||
)
|
||||
|
||||
var testKubeConfig = api.Config{
|
||||
CurrentContext: "no_user",
|
||||
Contexts: []api.NamedContext{
|
||||
{
|
||||
Name: "no_user",
|
||||
Context: api.Context{
|
||||
Cluster: "default",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "non_existing_user",
|
||||
Context: api.Context{
|
||||
Cluster: "default",
|
||||
AuthInfo: "non_existing_user",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "simple_token",
|
||||
Context: api.Context{
|
||||
Cluster: "default",
|
||||
AuthInfo: "simple_token",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gcp",
|
||||
Context: api.Context{
|
||||
Cluster: "default",
|
||||
AuthInfo: "gcp",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "expired_gcp",
|
||||
Context: api.Context{
|
||||
Cluster: "default",
|
||||
AuthInfo: "expired_gcp",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user_pass",
|
||||
Context: api.Context{
|
||||
Cluster: "default",
|
||||
AuthInfo: "user_pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl",
|
||||
Context: api.Context{
|
||||
Cluster: "ssl",
|
||||
AuthInfo: "ssl",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl_no_verification",
|
||||
Context: api.Context{
|
||||
Cluster: "ssl_no_verification",
|
||||
AuthInfo: "ssl",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl_no_file",
|
||||
Context: api.Context{
|
||||
Cluster: "ssl_no_file",
|
||||
AuthInfo: "ssl_no_file",
|
||||
},
|
||||
},
|
||||
},
|
||||
Clusters: []api.NamedCluster{
|
||||
{
|
||||
Name: "default",
|
||||
Cluster: api.Cluster{
|
||||
Server: testServer,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl",
|
||||
Cluster: api.Cluster{
|
||||
Server: testSSLServer,
|
||||
CertificateAuthorityData: testCertAuthBase64,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl_no_verification",
|
||||
Cluster: api.Cluster{
|
||||
Server: testSSLServer,
|
||||
InsecureSkipTLSVerify: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl_no_file",
|
||||
Cluster: api.Cluster{
|
||||
Server: testSSLServer,
|
||||
CertificateAuthority: "test-cert-no-file",
|
||||
},
|
||||
},
|
||||
},
|
||||
AuthInfos: []api.NamedAuthInfo{
|
||||
{
|
||||
Name: "simple_token",
|
||||
AuthInfo: api.AuthInfo{
|
||||
Token: testDataBase64,
|
||||
Username: testUsername,
|
||||
Password: testPassword,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gcp",
|
||||
AuthInfo: api.AuthInfo{
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "gcp",
|
||||
Config: map[string]string{
|
||||
"access-token": testDataBase64,
|
||||
"expiry": testTokenNoExpiry,
|
||||
},
|
||||
},
|
||||
Token: testDataBase64,
|
||||
Username: testUsername,
|
||||
Password: testPassword,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "expired_gcp",
|
||||
AuthInfo: api.AuthInfo{
|
||||
AuthProvider: &api.AuthProviderConfig{
|
||||
Name: "gcp",
|
||||
Config: map[string]string{
|
||||
"access-token": testDataBase64,
|
||||
"expiry": testTokenExpiry,
|
||||
},
|
||||
},
|
||||
Token: testDataBase64,
|
||||
Username: testUsername,
|
||||
Password: testPassword,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "user_pass",
|
||||
AuthInfo: api.AuthInfo{
|
||||
Username: testUsername,
|
||||
Password: testPassword,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl",
|
||||
AuthInfo: api.AuthInfo{
|
||||
Token: testDataBase64,
|
||||
ClientCertificateData: testClientCertBase64,
|
||||
ClientKeyData: testClientKeyBase64,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssl_no_file",
|
||||
AuthInfo: api.AuthInfo{
|
||||
Token: testDataBase64,
|
||||
ClientCertificate: "test-client-cert-no-file",
|
||||
ClientKey: "test-client-key-no-file",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLoadKubeConfig(t *testing.T) {
|
||||
tcs := []struct {
|
||||
ActiveContext string
|
||||
|
||||
Server string
|
||||
Token string
|
||||
CACert []byte
|
||||
Cert []byte
|
||||
Key []byte
|
||||
SkipTLSVerify bool
|
||||
GCLoader GoogleCredentialLoader
|
||||
}{
|
||||
{
|
||||
ActiveContext: "no_user",
|
||||
Server: testServer,
|
||||
},
|
||||
{
|
||||
ActiveContext: "non_existing_user",
|
||||
Server: testServer,
|
||||
},
|
||||
{
|
||||
ActiveContext: "simple_token",
|
||||
Server: testServer,
|
||||
Token: fmt.Sprintf(bearerTokenFormat, testDataBase64),
|
||||
},
|
||||
{
|
||||
ActiveContext: "user_pass",
|
||||
Server: testServer,
|
||||
Token: testBasicToken,
|
||||
},
|
||||
{
|
||||
ActiveContext: "gcp",
|
||||
Server: testServer,
|
||||
Token: fmt.Sprintf(bearerTokenFormat, testDataBase64),
|
||||
GCLoader: FakeGoogleCredentialLoaderNoRefresh{},
|
||||
},
|
||||
{
|
||||
ActiveContext: "expired_gcp",
|
||||
Server: testServer,
|
||||
Token: fmt.Sprintf(bearerTokenFormat, testAnotherDataBase64),
|
||||
GCLoader: FakeGoogleCredentialLoader{},
|
||||
},
|
||||
{
|
||||
ActiveContext: "ssl",
|
||||
Server: testSSLServer,
|
||||
Token: fmt.Sprintf(bearerTokenFormat, testDataBase64),
|
||||
CACert: testCertAuthBase64,
|
||||
Cert: testClientCertBase64,
|
||||
Key: testClientKeyBase64,
|
||||
},
|
||||
{
|
||||
ActiveContext: "ssl_no_verification",
|
||||
Server: testSSLServer,
|
||||
Token: fmt.Sprintf(bearerTokenFormat, testDataBase64),
|
||||
Cert: testClientCertBase64,
|
||||
Key: testClientKeyBase64,
|
||||
SkipTLSVerify: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
expected, err := FakeConfig(tc.Server, tc.Token, tc.CACert, tc.Cert, tc.Key, tc.SkipTLSVerify)
|
||||
if err != nil {
|
||||
t.Errorf("context %v, unexpected error setting up fake config: %v", tc.ActiveContext, err)
|
||||
}
|
||||
|
||||
actual := KubeConfigLoader{
|
||||
rawConfig: testKubeConfig,
|
||||
skipConfigPersist: true,
|
||||
gcLoader: tc.GCLoader,
|
||||
}
|
||||
if err := actual.SetActiveContext(tc.ActiveContext); err != nil {
|
||||
t.Errorf("context %v, unexpected error setting config active context: %v", tc.ActiveContext, err)
|
||||
}
|
||||
|
||||
// We are only testing loading auth and TLS info in LoadAndSet; we are not testing setting
|
||||
// the generate client's Configuration based on the restConfig, because we are using fake
|
||||
// data as TLS cert, which would fail PEM validation
|
||||
actual.loadAuthentication()
|
||||
if err := actual.loadClusterInfo(); err != nil {
|
||||
t.Errorf("context %v, unexpected error loading kube config: %v", tc.ActiveContext, err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, actual.RestConfig()) {
|
||||
t.Errorf("context %v, config loaded mismatch: want %v, got %v", tc.ActiveContext, expected, actual.RestConfig())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadKubeConfigSSLNoFile(t *testing.T) {
|
||||
actual := KubeConfigLoader{
|
||||
rawConfig: testKubeConfig,
|
||||
skipConfigPersist: true,
|
||||
}
|
||||
if err := actual.SetActiveContext("ssl_no_file"); err != nil {
|
||||
t.Errorf("context %v, unexpected error setting config active context: %v", "ssl_no_file", err)
|
||||
}
|
||||
|
||||
// We are only testing loading auth and TLS info in LoadAndSet; we are not testing setting
|
||||
// the generate client's Configuration based on the restConfig, because we are using fake
|
||||
// data as TLS cert, which would fail PEM validation
|
||||
actual.loadAuthentication()
|
||||
if err := actual.loadClusterInfo(); err == nil || !strings.Contains(err.Error(), "failed to get data or file") {
|
||||
t.Errorf("context %v, expecting failure to get file, got: %v", "ssl_no_file", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadKubeConfigSSLLocalFile(t *testing.T) {
|
||||
tc := struct {
|
||||
ActiveContext string
|
||||
|
||||
Server string
|
||||
Token string
|
||||
CACert []byte
|
||||
Cert []byte
|
||||
Key []byte
|
||||
SkipTLSVerify bool
|
||||
}{
|
||||
|
||||
ActiveContext: "ssl_local_file",
|
||||
Server: testSSLServer,
|
||||
Token: fmt.Sprintf(bearerTokenFormat, testDataBase64),
|
||||
CACert: testCertAuthBase64,
|
||||
Cert: testClientCertBase64,
|
||||
Key: testClientKeyBase64,
|
||||
}
|
||||
|
||||
expected, err := FakeConfig(tc.Server, tc.Token, tc.CACert, tc.Cert, tc.Key, tc.SkipTLSVerify)
|
||||
if err != nil {
|
||||
t.Errorf("context %v, unexpected error setting up fake config: %v", tc.ActiveContext, err)
|
||||
}
|
||||
|
||||
// Set up CA cert file
|
||||
testCACertFile, err := ioutil.TempFile(os.TempDir(), "ca-cert")
|
||||
if err != nil {
|
||||
t.Errorf("error: failed to create temp ca-cert file")
|
||||
}
|
||||
defer os.Remove(testCACertFile.Name())
|
||||
if err := ioutil.WriteFile(testCACertFile.Name(), testCertAuthBase64, 0644); err != nil {
|
||||
t.Errorf("context %v, unexpected error writing temp file %v: %v", tc.ActiveContext, testCACertFile.Name(), err)
|
||||
}
|
||||
|
||||
// Set up token file
|
||||
testTokenFile, err := ioutil.TempFile(os.TempDir(), "token")
|
||||
if err != nil {
|
||||
t.Errorf("error: failed to create temp token file")
|
||||
}
|
||||
defer os.Remove(testTokenFile.Name())
|
||||
if err := ioutil.WriteFile(testTokenFile.Name(), []byte(testDataBase64), 0644); err != nil {
|
||||
t.Errorf("context %v, unexpected error writing temp file %v: %v", tc.ActiveContext, testTokenFile.Name(), err)
|
||||
}
|
||||
|
||||
// Set up client cert file
|
||||
testClientCertFile, err := ioutil.TempFile(os.TempDir(), "client-cert")
|
||||
if err != nil {
|
||||
t.Errorf("error: failed to create temp client-cert file")
|
||||
}
|
||||
defer os.Remove(testClientCertFile.Name())
|
||||
if err := ioutil.WriteFile(testClientCertFile.Name(), testClientCertBase64, 0644); err != nil {
|
||||
t.Errorf("context %v, unexpected error writing temp file %v: %v", tc.ActiveContext, testClientCertFile.Name(), err)
|
||||
}
|
||||
|
||||
// Set up client key file
|
||||
testClientKeyFile, err := ioutil.TempFile(os.TempDir(), "client-key")
|
||||
if err != nil {
|
||||
t.Errorf("error: failed to create temp client-key file")
|
||||
}
|
||||
defer os.Remove(testClientKeyFile.Name())
|
||||
if err := ioutil.WriteFile(testClientKeyFile.Name(), testClientKeyBase64, 0644); err != nil {
|
||||
t.Errorf("context %v, unexpected error writing temp file %v: %v", tc.ActiveContext, testClientKeyFile.Name(), err)
|
||||
}
|
||||
|
||||
actual := KubeConfigLoader{
|
||||
rawConfig: api.Config{
|
||||
CurrentContext: "ssl_local_file",
|
||||
Contexts: []api.NamedContext{
|
||||
{
|
||||
Name: "ssl_local_file",
|
||||
Context: api.Context{
|
||||
Cluster: "ssl_local_file",
|
||||
AuthInfo: "ssl_local_file",
|
||||
},
|
||||
},
|
||||
},
|
||||
Clusters: []api.NamedCluster{
|
||||
{
|
||||
Name: "ssl_local_file",
|
||||
Cluster: api.Cluster{
|
||||
Server: testSSLServer,
|
||||
CertificateAuthority: testCACertFile.Name(),
|
||||
},
|
||||
},
|
||||
},
|
||||
AuthInfos: []api.NamedAuthInfo{
|
||||
{
|
||||
Name: "ssl_local_file",
|
||||
AuthInfo: api.AuthInfo{
|
||||
TokenFile: testTokenFile.Name(),
|
||||
ClientCertificate: testClientCertFile.Name(),
|
||||
ClientKey: testClientKeyFile.Name(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skipConfigPersist: true,
|
||||
}
|
||||
if err := actual.SetActiveContext(tc.ActiveContext); err != nil {
|
||||
t.Errorf("context %v, unexpected error setting config active context: %v", tc.ActiveContext, err)
|
||||
}
|
||||
|
||||
// We are only testing loading auth and TLS info in LoadAndSet; we are not testing setting
|
||||
// the generate client's Configuration based on the restConfig, because we are using fake
|
||||
// data as TLS cert, which would fail PEM validation
|
||||
actual.loadAuthentication()
|
||||
if err := actual.loadClusterInfo(); err != nil {
|
||||
t.Errorf("context %v, unexpected error loading kube config: %v", tc.ActiveContext, err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, actual.RestConfig()) {
|
||||
t.Errorf("context %v, config loaded mismatch: want %v, got %v", tc.ActiveContext, expected, actual.RestConfig())
|
||||
}
|
||||
}
|
||||
|
||||
func FakeConfig(server, token string, caCert, clientCert, clientKey []byte, skipTLSVerify bool) (RestConfig, error) {
|
||||
u, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return RestConfig{}, err
|
||||
}
|
||||
|
||||
return RestConfig{
|
||||
basePath: strings.TrimRight(server, "/"),
|
||||
host: u.Host,
|
||||
scheme: u.Scheme,
|
||||
token: token,
|
||||
caCert: caCert,
|
||||
clientCert: clientCert,
|
||||
clientKey: clientKey,
|
||||
skipTLSVerify: skipTLSVerify,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func stringToBase64(str string) []byte {
|
||||
return []byte(b64.StdEncoding.EncodeToString([]byte(str)))
|
||||
}
|
||||
|
||||
type FakeGoogleCredentialLoader struct{}
|
||||
|
||||
func (l FakeGoogleCredentialLoader) GetGoogleCredentials() (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: testAnotherDataBase64, Expiry: time.Now().UTC()}, nil
|
||||
}
|
||||
|
||||
type FakeGoogleCredentialLoaderNoRefresh struct{}
|
||||
|
||||
func (l FakeGoogleCredentialLoaderNoRefresh) GetGoogleCredentials() (*oauth2.Token, error) {
|
||||
return nil, fmt.Errorf("should not be called")
|
||||
}
|
||||
118
config/util.go
Normal file
118
config/util.go
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"k8s.io/client/kubernetes/config/api"
|
||||
)
|
||||
|
||||
// DataOrFile reads content of data, or file's content if data doesn't exist
|
||||
// and represent it as []byte data.
|
||||
func DataOrFile(data []byte, file string) ([]byte, error) {
|
||||
if data != nil {
|
||||
return data, nil
|
||||
}
|
||||
result, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get data or file: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// isExpired returns true if the token expired in expirySkewPreventionDelay time (default is 5 minutes)
|
||||
func isExpired(timestamp string) (bool, error) {
|
||||
ts, err := time.Parse(gcpRFC3339Format, timestamp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return ts.Before(time.Now().UTC().Add(expirySkewPreventionDelay)), nil
|
||||
}
|
||||
|
||||
func basicAuthToken(username, password string) string {
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
|
||||
}
|
||||
|
||||
func getContextWithName(contexts []api.NamedContext, name string) (*api.Context, error) {
|
||||
var context *api.Context
|
||||
for _, c := range contexts {
|
||||
if c.Name == name {
|
||||
if context != nil {
|
||||
return nil, fmt.Errorf("error parsing kube config: duplicate contexts with name %v", name)
|
||||
}
|
||||
context = c.Context.DeepCopy()
|
||||
}
|
||||
}
|
||||
if context == nil {
|
||||
return nil, fmt.Errorf("error parsing kube config: couldn't find context with name %v", name)
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func getClusterWithName(clusters []api.NamedCluster, name string) (*api.Cluster, error) {
|
||||
var cluster *api.Cluster
|
||||
for _, c := range clusters {
|
||||
if c.Name == name {
|
||||
if cluster != nil {
|
||||
return nil, fmt.Errorf("error parsing kube config: duplicate clusters with name %v", name)
|
||||
}
|
||||
cluster = c.Cluster.DeepCopy()
|
||||
}
|
||||
}
|
||||
if cluster == nil {
|
||||
return nil, fmt.Errorf("error parsing kube config: couldn't find cluster with name %v", name)
|
||||
}
|
||||
return cluster, nil
|
||||
}
|
||||
|
||||
func getUserWithName(users []api.NamedAuthInfo, name string) (*api.AuthInfo, error) {
|
||||
var user *api.AuthInfo
|
||||
for _, u := range users {
|
||||
if u.Name == name {
|
||||
if user != nil {
|
||||
return nil, fmt.Errorf("error parsing kube config: duplicate users with name %v", name)
|
||||
}
|
||||
user = u.AuthInfo.DeepCopy()
|
||||
}
|
||||
}
|
||||
// A context may have no user, or using non-existing user name. We simply return nil *api.AuthInfo in this case.
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func setUserWithName(users []api.NamedAuthInfo, name string, user *api.AuthInfo) error {
|
||||
var userFound bool
|
||||
var userTarget *api.AuthInfo
|
||||
|
||||
for i, u := range users {
|
||||
if u.Name == name {
|
||||
if userFound {
|
||||
return fmt.Errorf("error setting kube config: duplicate users with name %v", name)
|
||||
}
|
||||
userTarget = &users[i].AuthInfo
|
||||
userFound = true
|
||||
}
|
||||
}
|
||||
if !userFound {
|
||||
return fmt.Errorf("error setting kube config: cannot find user with name: %v", name)
|
||||
}
|
||||
user.DeepCopyInto(userTarget)
|
||||
return nil
|
||||
}
|
||||
213
config/util_test.go
Normal file
213
config/util_test.go
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client/kubernetes/config/api"
|
||||
)
|
||||
|
||||
func TestSetUserWithName(t *testing.T) {
|
||||
tcs := []struct {
|
||||
Origin []api.NamedAuthInfo
|
||||
Name string
|
||||
User *api.AuthInfo
|
||||
Expected []api.NamedAuthInfo
|
||||
}{
|
||||
{
|
||||
Origin: []api.NamedAuthInfo{
|
||||
{"A", api.AuthInfo{}},
|
||||
{"B", api.AuthInfo{}},
|
||||
{"C", api.AuthInfo{}},
|
||||
},
|
||||
Name: "B",
|
||||
User: &api.AuthInfo{Token: "test-token"},
|
||||
Expected: []api.NamedAuthInfo{
|
||||
{"A", api.AuthInfo{}},
|
||||
{"B", api.AuthInfo{Token: "test-token"}},
|
||||
{"C", api.AuthInfo{}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
if err := setUserWithName(tc.Origin, tc.Name, tc.User); err != nil {
|
||||
t.Errorf("unexpected error setting user with name %v: %v", tc.Name, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.Origin, tc.Expected) {
|
||||
t.Errorf("setUserWithName mismatch: want %v, got %v", tc.Expected, tc.Origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserWithName(t *testing.T) {
|
||||
users := []api.NamedAuthInfo{
|
||||
{"A", api.AuthInfo{}},
|
||||
{"B", api.AuthInfo{Token: "test-token"}},
|
||||
{"D", api.AuthInfo{}},
|
||||
{"D", api.AuthInfo{}},
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
Name string
|
||||
ExpectedUser *api.AuthInfo
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "A",
|
||||
ExpectedUser: &api.AuthInfo{},
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
ExpectedUser: &api.AuthInfo{Token: "test-token"},
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
ExpectedUser: nil,
|
||||
// A context may have no user, or using non-existing user name.
|
||||
// We simply return nil *api.AuthInfo in this case.
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "D",
|
||||
ExpectedUser: nil,
|
||||
ExpectedError: fmt.Errorf("error parsing kube config: duplicate users with name D"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
user, err := getUserWithName(users, tc.Name)
|
||||
|
||||
if !reflect.DeepEqual(tc.ExpectedUser, user) {
|
||||
t.Errorf("getUserWithName mismatch: want %v, got %v", tc.ExpectedUser, user)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.ExpectedError, err) {
|
||||
t.Errorf("getUserWithName error mismatch: want %v, got %v", tc.ExpectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContextWithName(t *testing.T) {
|
||||
contexts := []api.NamedContext{
|
||||
{"A", api.Context{}},
|
||||
{"B", api.Context{
|
||||
Cluster: "test-cluster",
|
||||
AuthInfo: "test-user",
|
||||
Namespace: "test-namespace",
|
||||
}},
|
||||
{"D", api.Context{}},
|
||||
{"D", api.Context{}},
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
Name string
|
||||
ExpectedContext *api.Context
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "A",
|
||||
ExpectedContext: &api.Context{},
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
ExpectedContext: &api.Context{
|
||||
Cluster: "test-cluster",
|
||||
AuthInfo: "test-user",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
ExpectedContext: nil,
|
||||
ExpectedError: fmt.Errorf("error parsing kube config: couldn't find context with name C"),
|
||||
},
|
||||
{
|
||||
Name: "D",
|
||||
ExpectedContext: nil,
|
||||
ExpectedError: fmt.Errorf("error parsing kube config: duplicate contexts with name D"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
context, err := getContextWithName(contexts, tc.Name)
|
||||
|
||||
if !reflect.DeepEqual(tc.ExpectedContext, context) {
|
||||
t.Errorf("getContextWithName mismatch: want %v, got %v", tc.ExpectedContext, context)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.ExpectedError, err) {
|
||||
t.Errorf("getContextWithName error mismatch: want %v, got %v", tc.ExpectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClusterWithName(t *testing.T) {
|
||||
clusters := []api.NamedCluster{
|
||||
{"A", api.Cluster{}},
|
||||
{"B", api.Cluster{Server: "test-server"}},
|
||||
{"D", api.Cluster{}},
|
||||
{"D", api.Cluster{}},
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
Name string
|
||||
ExpectedCluster *api.Cluster
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "A",
|
||||
ExpectedCluster: &api.Cluster{},
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
ExpectedCluster: &api.Cluster{Server: "test-server"},
|
||||
ExpectedError: nil,
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
ExpectedCluster: nil,
|
||||
ExpectedError: fmt.Errorf("error parsing kube config: couldn't find cluster with name C"),
|
||||
},
|
||||
{
|
||||
Name: "D",
|
||||
ExpectedCluster: nil,
|
||||
ExpectedError: fmt.Errorf("error parsing kube config: duplicate clusters with name D"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
cluster, err := getClusterWithName(clusters, tc.Name)
|
||||
|
||||
if !reflect.DeepEqual(tc.ExpectedCluster, cluster) {
|
||||
t.Errorf("getClusterWithName mismatch: want %v, got %v", tc.ExpectedCluster, cluster)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.ExpectedError, err) {
|
||||
t.Errorf("getClusterWithName error mismatch: want %v, got %v", tc.ExpectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user