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