Merge pull request #2 from roycaihw/move-config

Move config loader to go-base
This commit is contained in:
Yu Liao
2018-05-17 10:36:35 -07:00
committed by GitHub
11 changed files with 1939 additions and 1 deletions

View File

@@ -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
View 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
View 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"`
}

View 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
View 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
View 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()
}

View 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
View 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
View 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
View 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
View 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)
}
}
}