reconcile merge

Signed-off-by: Huamin Chen <hchen@redhat.com>
This commit is contained in:
Huamin Chen
2019-01-15 16:20:41 +00:00
parent 85b8415024
commit e46099a504
2425 changed files with 271763 additions and 40453 deletions

View File

@ -1,78 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"client_config_test.go",
"loader_test.go",
"merged_client_builder_test.go",
"overrides_test.go",
"validation_test.go",
],
embed = [":go_default_library"],
deps = [
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/imdario/mergo:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api/latest:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"auth_loaders.go",
"client_config.go",
"config.go",
"doc.go",
"flag.go",
"helpers.go",
"loader.go",
"merged_client_builder.go",
"overrides.go",
"validation.go",
],
importpath = "k8s.io/client-go/tools/clientcmd",
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/howeyc/gopass:go_default_library",
"//vendor/github.com/imdario/mergo:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/auth:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api/latest:go_default_library",
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,50 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"helpers_test.go",
"types_test.go",
],
embed = [":go_default_library"],
deps = ["//vendor/github.com/ghodss/yaml:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"helpers.go",
"register.go",
"types.go",
"zz_generated.deepcopy.go",
],
importpath = "k8s.io/client-go/tools/clientcmd/api",
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/client-go/tools/clientcmd/api/latest:all-srcs",
"//staging/src/k8s.io/client-go/tools/clientcmd/api/v1:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -15,4 +15,5 @@ limitations under the License.
*/
// +k8s:deepcopy-gen=package
package api

View File

@ -29,6 +29,8 @@ import (
func init() {
sDec, _ := base64.StdEncoding.DecodeString("REDACTED+")
redactedBytes = []byte(string(sDec))
sDec, _ = base64.StdEncoding.DecodeString("DATA+OMITTED")
dataOmittedBytes = []byte(string(sDec))
}
// IsConfigEmpty returns true if the config is empty.
@ -79,7 +81,10 @@ func MinifyConfig(config *Config) error {
return nil
}
var redactedBytes []byte
var (
redactedBytes []byte
dataOmittedBytes []byte
)
// Flatten redacts raw data entries from the config object for a human-readable view.
func ShortenConfig(config *Config) {
@ -97,7 +102,7 @@ func ShortenConfig(config *Config) {
}
for key, cluster := range config.Clusters {
if len(cluster.CertificateAuthorityData) > 0 {
cluster.CertificateAuthorityData = redactedBytes
cluster.CertificateAuthorityData = dataOmittedBytes
}
config.Clusters[key] = cluster
}

View File

@ -23,7 +23,7 @@ import (
"reflect"
"testing"
"github.com/ghodss/yaml"
"sigs.k8s.io/yaml"
)
func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caContent string, t *testing.T) Config {
@ -229,7 +229,7 @@ func Example_minifyAndShorten() {
// clusters:
// cow-cluster:
// LocationOfOrigin: ""
// certificate-authority-data: REDACTED
// certificate-authority-data: DATA+OMITTED
// server: http://cow.org:8080
// contexts:
// federal-context:
@ -276,14 +276,15 @@ func TestShortenSuccess(t *testing.T) {
}
redacted := string(redactedBytes)
dataOmitted := string(dataOmittedBytes)
if len(mutatingConfig.Clusters) != 2 {
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
}
if !reflect.DeepEqual(startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster]) {
t.Errorf("expected %v, got %v", startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster])
}
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != redacted {
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != dataOmitted {
t.Errorf("expected %v, got %v", dataOmitted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
}
if len(mutatingConfig.AuthInfos) != 2 {

View File

@ -1,33 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["latest.go"],
importpath = "k8s.io/client-go/tools/clientcmd/api/latest",
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -21,6 +21,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/tools/clientcmd/api/v1"
)
@ -47,14 +48,8 @@ var (
func init() {
Scheme = runtime.NewScheme()
if err := api.AddToScheme(Scheme); err != nil {
// Programmer error, detect immediately
panic(err)
}
if err := v1.AddToScheme(Scheme); err != nil {
// Programmer error, detect immediately
panic(err)
}
utilruntime.Must(api.AddToScheme(Scheme))
utilruntime.Must(v1.AddToScheme(Scheme))
yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, Scheme, Scheme)
Codec = versioning.NewDefaultingCodecForScheme(
Scheme,

View File

@ -119,6 +119,9 @@ type AuthInfo struct {
// 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 map[string]runtime.Object `json:"extensions,omitempty"`
@ -147,6 +150,35 @@ type AuthProviderConfig struct {
Config map[string]string `json:"config,omitempty"`
}
// 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"`
}
// NewConfig is a convenience function that returns a new Config object with non-nil maps
func NewConfig() *Config {
return &Config{

View File

@ -19,7 +19,7 @@ package api
import (
"fmt"
"github.com/ghodss/yaml"
"sigs.k8s.io/yaml"
)
func Example_emptyConfig() {

View File

@ -1,37 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"conversion.go",
"doc.go",
"register.go",
"types.go",
"zz_generated.deepcopy.go",
],
importpath = "k8s.io/client-go/tools/clientcmd/api/v1",
deps = [
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -17,6 +17,7 @@ limitations under the License.
package v1
import (
"fmt"
"sort"
"k8s.io/apimachinery/pkg/conversion"
@ -105,7 +106,11 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
if err := s.Convert(&curr.Cluster, newCluster, 0); err != nil {
return err
}
(*out)[curr.Name] = newCluster
if (*out)[curr.Name] == nil {
(*out)[curr.Name] = newCluster
} else {
return fmt.Errorf("error converting *[]NamedCluster into *map[string]*api.Cluster: duplicate name \"%v\" in list: %v", curr.Name, *in)
}
}
return nil
@ -136,7 +141,11 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
if err := s.Convert(&curr.AuthInfo, newAuthInfo, 0); err != nil {
return err
}
(*out)[curr.Name] = newAuthInfo
if (*out)[curr.Name] == nil {
(*out)[curr.Name] = newAuthInfo
} else {
return fmt.Errorf("error converting *[]NamedAuthInfo into *map[string]*api.AuthInfo: duplicate name \"%v\" in list: %v", curr.Name, *in)
}
}
return nil
@ -167,7 +176,11 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
if err := s.Convert(&curr.Context, newContext, 0); err != nil {
return err
}
(*out)[curr.Name] = newContext
if (*out)[curr.Name] == nil {
(*out)[curr.Name] = newContext
} else {
return fmt.Errorf("error converting *[]NamedContext into *map[string]*api.Context: duplicate name \"%v\" in list: %v", curr.Name, *in)
}
}
return nil
@ -198,7 +211,11 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
if err := s.Convert(&curr.Extension, &newExtension, 0); err != nil {
return err
}
(*out)[curr.Name] = newExtension
if (*out)[curr.Name] == nil {
(*out)[curr.Name] = newExtension
} else {
return fmt.Errorf("error converting *[]NamedExtension into *map[string]runtime.Object: duplicate name \"%v\" in list: %v", curr.Name, *in)
}
}
return nil

View File

@ -15,4 +15,5 @@ limitations under the License.
*/
// +k8s:deepcopy-gen=package
package v1

View File

@ -113,6 +113,9 @@ type AuthInfo struct {
// 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"`
@ -169,3 +172,32 @@ 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

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Copyright 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.
@ -46,22 +46,26 @@ func (in *AuthInfo) DeepCopyInto(out *AuthInfo) {
in, out := &in.ImpersonateUserExtra, &out.ImpersonateUserExtra
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
(*out)[key] = make([]string, len(val))
copy((*out)[key], val)
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
if in.AuthProvider != nil {
in, out := &in.AuthProvider, &out.AuthProvider
if *in == nil {
*out = nil
} else {
*out = new(AuthProviderConfig)
(*in).DeepCopyInto(*out)
}
*out = new(AuthProviderConfig)
(*in).DeepCopyInto(*out)
}
if in.Exec != nil {
in, out := &in.Exec, &out.Exec
*out = new(ExecConfig)
(*in).DeepCopyInto(*out)
}
if in.Extensions != nil {
in, out := &in.Extensions, &out.Extensions
@ -210,6 +214,48 @@ func (in *Context) DeepCopy() *Context {
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

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Copyright 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.
@ -46,22 +46,26 @@ func (in *AuthInfo) DeepCopyInto(out *AuthInfo) {
in, out := &in.ImpersonateUserExtra, &out.ImpersonateUserExtra
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
(*out)[key] = make([]string, len(val))
copy((*out)[key], val)
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
if in.AuthProvider != nil {
in, out := &in.AuthProvider, &out.AuthProvider
if *in == nil {
*out = nil
} else {
*out = new(AuthProviderConfig)
(*in).DeepCopyInto(*out)
}
*out = new(AuthProviderConfig)
(*in).DeepCopyInto(*out)
}
if in.Exec != nil {
in, out := &in.Exec, &out.Exec
*out = new(ExecConfig)
(*in).DeepCopyInto(*out)
}
if in.Extensions != nil {
in, out := &in.Extensions, &out.Extensions
@ -150,36 +154,45 @@ func (in *Config) DeepCopyInto(out *Config) {
in, out := &in.Clusters, &out.Clusters
*out = make(map[string]*Cluster, len(*in))
for key, val := range *in {
var outVal *Cluster
if val == nil {
(*out)[key] = nil
} else {
(*out)[key] = new(Cluster)
val.DeepCopyInto((*out)[key])
in, out := &val, &outVal
*out = new(Cluster)
(*in).DeepCopyInto(*out)
}
(*out)[key] = outVal
}
}
if in.AuthInfos != nil {
in, out := &in.AuthInfos, &out.AuthInfos
*out = make(map[string]*AuthInfo, len(*in))
for key, val := range *in {
var outVal *AuthInfo
if val == nil {
(*out)[key] = nil
} else {
(*out)[key] = new(AuthInfo)
val.DeepCopyInto((*out)[key])
in, out := &val, &outVal
*out = new(AuthInfo)
(*in).DeepCopyInto(*out)
}
(*out)[key] = outVal
}
}
if in.Contexts != nil {
in, out := &in.Contexts, &out.Contexts
*out = make(map[string]*Context, len(*in))
for key, val := range *in {
var outVal *Context
if val == nil {
(*out)[key] = nil
} else {
(*out)[key] = new(Context)
val.DeepCopyInto((*out)[key])
in, out := &val, &outVal
*out = new(Context)
(*in).DeepCopyInto(*out)
}
(*out)[key] = outVal
}
}
if in.Extensions != nil {
@ -241,6 +254,48 @@ func (in *Context) DeepCopy() *Context {
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 *Preferences) DeepCopyInto(out *Preferences) {
*out = *in

View File

@ -23,7 +23,8 @@ import (
"io/ioutil"
"os"
"github.com/howeyc/gopass"
"golang.org/x/crypto/ssh/terminal"
clientauth "k8s.io/client-go/tools/auth"
)
@ -89,8 +90,12 @@ func promptForString(field string, r io.Reader, show bool) (result string, err e
_, err = fmt.Fscan(r, &result)
} else {
var data []byte
data, err = gopass.GetPasswdMasked()
result = string(data)
if terminal.IsTerminal(int(os.Stdin.Fd())) {
data, err = terminal.ReadPassword(int(os.Stdin.Fd()))
result = string(data)
} else {
return "", fmt.Errorf("error reading input for %s", field)
}
}
return result, err
}

View File

@ -24,10 +24,9 @@ import (
"os"
"strings"
"github.com/golang/glog"
"github.com/imdario/mergo"
"k8s.io/klog"
"k8s.io/api/core/v1"
restclient "k8s.io/client-go/rest"
clientauth "k8s.io/client-go/tools/auth"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -100,6 +99,26 @@ func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string,
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
}
// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
config, err := Load(configBytes)
if err != nil {
return nil, err
}
return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
}
// RESTConfigFromKubeConfig is a convenience method to give back a restconfig from your kubeconfig bytes.
// For programmatic access, this is what you want 80% of the time
func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
clientConfig, err := NewClientConfigFromBytes(configBytes)
if err != nil {
return nil, err
}
return clientConfig.ClientConfig()
}
func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
return config.config, nil
}
@ -156,10 +175,6 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
// only try to read the auth information if we are secure
if restclient.IsConfigTransportTLS(*clientConfig) {
var err error
// mergo is a first write wins for map value and a last writing wins for interface values
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
// Our mergo.Merge version is older than this change.
var persister restclient.AuthProviderConfigPersister
if config.configAccess != nil {
authInfoName, _ := config.getAuthInfoName()
@ -169,13 +184,13 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
if err != nil {
return nil, err
}
mergo.Merge(clientConfig, userAuthPartialConfig)
mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
if err != nil {
return nil, err
}
mergo.Merge(clientConfig, serverAuthPartialConfig)
mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
}
return clientConfig, nil
@ -195,7 +210,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
mergo.Merge(mergedConfig, configClientConfig)
mergo.MergeWithOverwrite(mergedConfig, configClientConfig)
return mergedConfig, nil
}
@ -214,11 +229,11 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
if len(configAuthInfo.Token) > 0 {
mergedConfig.BearerToken = configAuthInfo.Token
} else if len(configAuthInfo.TokenFile) > 0 {
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
if err != nil {
ts := restclient.NewCachedFileTokenSource(configAuthInfo.TokenFile)
if _, err := ts.Token(); err != nil {
return nil, err
}
mergedConfig.BearerToken = string(tokenBytes)
mergedConfig.WrapTransport = restclient.TokenSourceWrapTransport(ts)
}
if len(configAuthInfo.Impersonate) > 0 {
mergedConfig.Impersonate = restclient.ImpersonationConfig{
@ -241,6 +256,9 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
mergedConfig.AuthProvider = configAuthInfo.AuthProvider
mergedConfig.AuthConfigPersister = persistAuthConfig
}
if configAuthInfo.Exec != nil {
mergedConfig.ExecProvider = configAuthInfo.Exec
}
// if there still isn't enough information to authenticate the user, try prompting
if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
@ -257,8 +275,8 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
previouslyMergedConfig := mergedConfig
mergedConfig = &restclient.Config{}
mergo.Merge(mergedConfig, promptedConfig)
mergo.Merge(mergedConfig, previouslyMergedConfig)
mergo.MergeWithOverwrite(mergedConfig, promptedConfig)
mergo.MergeWithOverwrite(mergedConfig, previouslyMergedConfig)
config.promptedCredentials.username = mergedConfig.Username
config.promptedCredentials.password = mergedConfig.Password
}
@ -291,7 +309,8 @@ func canIdentifyUser(config restclient.Config) bool {
return len(config.Username) > 0 ||
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
len(config.BearerToken) > 0 ||
config.AuthProvider != nil
config.AuthProvider != nil ||
config.ExecProvider != nil
}
// Namespace implements ClientConfig
@ -314,7 +333,7 @@ func (config *DirectClientConfig) Namespace() (string, bool, error) {
}
if len(configContext.Namespace) == 0 {
return v1.NamespaceDefault, false, nil
return "default", false, nil
}
return configContext.Namespace, false, nil
@ -400,11 +419,11 @@ func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
mergedContext := clientcmdapi.NewContext()
if configContext, exists := contexts[contextName]; exists {
mergo.Merge(mergedContext, configContext)
mergo.MergeWithOverwrite(mergedContext, configContext)
} else if required {
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
}
mergo.Merge(mergedContext, config.overrides.Context)
mergo.MergeWithOverwrite(mergedContext, config.overrides.Context)
return *mergedContext, nil
}
@ -416,11 +435,11 @@ func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
mergedAuthInfo := clientcmdapi.NewAuthInfo()
if configAuthInfo, exists := authInfos[authInfoName]; exists {
mergo.Merge(mergedAuthInfo, configAuthInfo)
mergo.MergeWithOverwrite(mergedAuthInfo, configAuthInfo)
} else if required {
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
}
mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo)
mergo.MergeWithOverwrite(mergedAuthInfo, config.overrides.AuthInfo)
return *mergedAuthInfo, nil
}
@ -431,13 +450,13 @@ func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
clusterInfoName, required := config.getClusterName()
mergedClusterInfo := clientcmdapi.NewCluster()
mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults)
mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterDefaults)
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
mergo.Merge(mergedClusterInfo, configClusterInfo)
mergo.MergeWithOverwrite(mergedClusterInfo, configClusterInfo)
} else if required {
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
}
mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo)
mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterInfo)
// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
@ -526,12 +545,12 @@ func (config *inClusterClientConfig) Possible() bool {
// to the default config.
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
if kubeconfigPath == "" && masterUrl == "" {
glog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
klog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
kubeconfig, err := restclient.InClusterConfig()
if err == nil {
return kubeconfig, nil
}
glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
}
return NewNonInteractiveDeferredLoadingClientConfig(
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},

View File

@ -18,31 +18,95 @@ package clientcmd
import (
"io/ioutil"
"net/http"
"os"
"reflect"
"strings"
"testing"
"github.com/imdario/mergo"
restclient "k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
func TestOldMergoLib(t *testing.T) {
type T struct {
X string
func TestMergoSemantics(t *testing.T) {
type U struct {
A string
B int64
}
dst := T{X: "one"}
src := T{X: "two"}
mergo.Merge(&dst, &src)
if dst.X != "two" {
// mergo.Merge changed in an incompatible way with
//
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
//
// We have to stay with the old version which still does eager
// copying from src to dst in structs.
t.Errorf("mergo.Merge library found with incompatible, new behavior")
type T struct {
S []string
X string
Y int64
U U
}
var testDataStruct = []struct {
dst T
src T
expected T
}{
{
dst: T{X: "one"},
src: T{X: "two"},
expected: T{X: "two"},
},
{
dst: T{X: "one", Y: 5, U: U{A: "four", B: 6}},
src: T{X: "two", U: U{A: "three", B: 4}},
expected: T{X: "two", Y: 5, U: U{A: "three", B: 4}},
},
{
dst: T{S: []string{"test3", "test4", "test5"}},
src: T{S: []string{"test1", "test2", "test3"}},
expected: T{S: []string{"test1", "test2", "test3"}},
},
}
for _, data := range testDataStruct {
err := mergo.MergeWithOverwrite(&data.dst, &data.src)
if err != nil {
t.Errorf("error while merging: %s", err)
}
if !reflect.DeepEqual(data.dst, data.expected) {
// The mergo library has previously changed in a an incompatible way.
// example:
//
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
//
// This test verifies that the semantics of the merge are what we expect.
// If they are not, the mergo library may have been updated and broken
// unexpectedly.
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
}
}
var testDataMap = []struct {
dst map[string]int
src map[string]int
expected map[string]int
}{
{
dst: map[string]int{"rsc": 6543, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
src: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912},
expected: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
},
}
for _, data := range testDataMap {
err := mergo.MergeWithOverwrite(&data.dst, &data.src)
if err != nil {
t.Errorf("error while merging: %s", err)
}
if !reflect.DeepEqual(data.dst, data.expected) {
// The mergo library has previously changed in a an incompatible way.
// example:
//
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
//
// This test verifies that the semantics of the merge are what we expect.
// If they are not, the mergo library may have been updated and broken
// unexpectedly.
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
}
}
}
@ -126,6 +190,54 @@ func TestMergeContext(t *testing.T) {
matchStringArg(namespace, actual, t)
}
func TestModifyContext(t *testing.T) {
expectedCtx := map[string]bool{
"updated": true,
"clean": true,
}
tempPath, err := ioutil.TempFile("", "testclientcmd-")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.Remove(tempPath.Name())
pathOptions := NewDefaultPathOptions()
config := createValidTestConfig()
pathOptions.GlobalFile = tempPath.Name()
// define new context and assign it - our path options config
config.Contexts["updated"] = &clientcmdapi.Context{
Cluster: "updated",
AuthInfo: "updated",
}
config.CurrentContext = "updated"
if err := ModifyConfig(pathOptions, *config, true); err != nil {
t.Errorf("Unexpected error: %v", err)
}
startingConfig, err := pathOptions.GetStartingConfig()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// make sure the current context was updated
matchStringArg("updated", startingConfig.CurrentContext, t)
// there should now be two contexts
if len(startingConfig.Contexts) != len(expectedCtx) {
t.Fatalf("unexpected nuber of contexts, expecting %v, but found %v", len(expectedCtx), len(startingConfig.Contexts))
}
for key := range startingConfig.Contexts {
if !expectedCtx[key] {
t.Fatalf("expected context %q to exist", key)
}
}
}
func TestCertificateData(t *testing.T) {
caData := []byte("ca-data")
certData := []byte("cert-data")
@ -133,7 +245,7 @@ func TestCertificateData(t *testing.T) {
config := clientcmdapi.NewConfig()
config.Clusters["clean"] = &clientcmdapi.Cluster{
Server: "https://localhost:8443",
Server: "https://localhost:8443",
CertificateAuthorityData: caData,
}
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
@ -222,7 +334,19 @@ func TestBasicTokenFile(t *testing.T) {
t.Fatalf("Unexpected error: %v", err)
}
matchStringArg(token, clientConfig.BearerToken, t)
var out *http.Request
clientConfig.WrapTransport(fakeTransport(func(req *http.Request) (*http.Response, error) {
out = req
return &http.Response{}, nil
})).RoundTrip(&http.Request{})
matchStringArg(token, strings.TrimPrefix(out.Header.Get("Authorization"), "Bearer "), t)
}
type fakeTransport func(*http.Request) (*http.Response, error)
func (ft fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return ft(req)
}
func TestPrecedenceTokenFile(t *testing.T) {
@ -526,3 +650,46 @@ func TestNamespaceOverride(t *testing.T) {
matchStringArg("foo", ns, t)
}
func TestAuthConfigMerge(t *testing.T) {
content := `
apiVersion: v1
clusters:
- cluster:
server: https://localhost:8080
name: foo-cluster
contexts:
- context:
cluster: foo-cluster
user: foo-user
namespace: bar
name: foo-context
current-context: foo-context
kind: Config
users:
- name: foo-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1alpha1
args:
- arg-1
- arg-2
command: foo-command
`
tmpfile, err := ioutil.TempFile("", "kubeconfig")
if err != nil {
t.Error(err)
}
defer os.Remove(tmpfile.Name())
if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
t.Error(err)
}
config, err := BuildConfigFromFlags("", tmpfile.Name())
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
}
}

View File

@ -24,7 +24,7 @@ import (
"reflect"
"sort"
"github.com/golang/glog"
"k8s.io/klog"
restclient "k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -68,7 +68,9 @@ func (o *PathOptions) GetEnvVarFiles() []string {
return []string{}
}
return filepath.SplitList(envVarValue)
fileList := filepath.SplitList(envVarValue)
// prevent the same path load multiple times
return deduplicate(fileList)
}
func (o *PathOptions) GetLoadingPrecedence() []string {
@ -218,6 +220,9 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
}
}
// seenConfigs stores a map of config source filenames to computed config objects
seenConfigs := map[string]*clientcmdapi.Config{}
for key, context := range newConfig.Contexts {
startingContext, exists := startingConfig.Contexts[key]
if !reflect.DeepEqual(context, startingContext) || !exists {
@ -226,15 +231,28 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
destinationFile = configAccess.GetDefaultFilename()
}
configToWrite, err := getConfigFromFile(destinationFile)
if err != nil {
return err
// we only obtain a fresh config object from its source file
// if we have not seen it already - this prevents us from
// reading and writing to the same number of files repeatedly
// when multiple / all contexts share the same destination file.
configToWrite, seen := seenConfigs[destinationFile]
if !seen {
var err error
configToWrite, err = getConfigFromFile(destinationFile)
if err != nil {
return err
}
seenConfigs[destinationFile] = configToWrite
}
configToWrite.Contexts[key] = context
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
configToWrite.Contexts[key] = context
}
}
// actually persist config object changes
for destinationFile, configToWrite := range seenConfigs {
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
return err
}
}
@ -465,7 +483,7 @@ func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
config, err := getConfigFromFile(filename)
if err != nil {
glog.FatalDepth(1, err)
klog.FatalDepth(1, err)
}
return config

View File

@ -27,8 +27,8 @@ import (
goruntime "runtime"
"strings"
"github.com/golang/glog"
"github.com/imdario/mergo"
"k8s.io/klog"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -139,7 +139,9 @@ func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
envVarFiles := os.Getenv(RecommendedConfigPathEnvVar)
if len(envVarFiles) != 0 {
chain = append(chain, filepath.SplitList(envVarFiles)...)
fileList := filepath.SplitList(envVarFiles)
// prevent the same path load multiple times
chain = append(chain, deduplicate(fileList)...)
} else {
chain = append(chain, RecommendedHomeFile)
@ -209,7 +211,7 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
mapConfig := clientcmdapi.NewConfig()
for _, kubeconfig := range kubeconfigs {
mergo.Merge(mapConfig, kubeconfig)
mergo.MergeWithOverwrite(mapConfig, kubeconfig)
}
// merge all of the struct values in the reverse order so that priority is given correctly
@ -217,14 +219,14 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
nonMapConfig := clientcmdapi.NewConfig()
for i := len(kubeconfigs) - 1; i >= 0; i-- {
kubeconfig := kubeconfigs[i]
mergo.Merge(nonMapConfig, kubeconfig)
mergo.MergeWithOverwrite(nonMapConfig, kubeconfig)
}
// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
// get the values we expect.
config := clientcmdapi.NewConfig()
mergo.Merge(config, mapConfig)
mergo.Merge(config, nonMapConfig)
mergo.MergeWithOverwrite(config, mapConfig)
mergo.MergeWithOverwrite(config, nonMapConfig)
if rules.ResolvePaths() {
if err := ResolveLocalPaths(config); err != nil {
@ -354,7 +356,7 @@ func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
if err != nil {
return nil, err
}
glog.V(6).Infoln("Config loaded from file", filename)
klog.V(6).Infoln("Config loaded from file", filename)
// set LocationOfOrigin on every Cluster, User, and Context
for key, obj := range config.AuthInfos {
@ -557,7 +559,12 @@ func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string {
}
func GetAuthInfoFileReferences(authInfo *clientcmdapi.AuthInfo) []*string {
return []*string{&authInfo.ClientCertificate, &authInfo.ClientKey, &authInfo.TokenFile}
s := []*string{&authInfo.ClientCertificate, &authInfo.ClientKey, &authInfo.TokenFile}
// Only resolve exec command if it isn't PATH based.
if authInfo.Exec != nil && strings.ContainsRune(authInfo.Exec.Command, filepath.Separator) {
s = append(s, &authInfo.Exec.Command)
}
return s
}
// ResolvePaths updates the given refs to be absolute paths, relative to the given base directory
@ -610,3 +617,17 @@ func MakeRelative(path, base string) (string, error) {
}
return path, nil
}
// deduplicate removes any duplicated values and returns a new slice, keeping the order unchanged
func deduplicate(s []string) []string {
encountered := map[string]bool{}
ret := make([]string, 0)
for i := range s {
if encountered[s[i]] {
continue
}
encountered[s[i]] = true
ret = append(ret, s[i])
}
return ret
}

View File

@ -26,7 +26,7 @@ import (
"strings"
"testing"
"github.com/ghodss/yaml"
"sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/runtime"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -201,11 +201,182 @@ func TestLoadingEmptyMaps(t *testing.T) {
}
}
func TestDuplicateClusterName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
- cluster:
api-version: v1
server: https://kubernetes.default.svc:443
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
name: kubeconfig-cluster
- cluster:
api-version: v2
server: https://test.example.server:443
certificate-authority: /var/run/secrets/test.example.io/serviceaccount/ca.crt
name: kubeconfig-cluster
contexts:
- context:
cluster: kubeconfig-cluster
namespace: default
user: kubeconfig-user
name: kubeconfig-context
current-context: kubeconfig-context
users:
- name: kubeconfig-user
user:
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
`), os.FileMode(0755))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
_, err = LoadFromFile(configFile.Name())
if err == nil || !strings.Contains(err.Error(),
"error converting *[]NamedCluster into *map[string]*api.Cluster: duplicate name \"kubeconfig-cluster\" in list") {
t.Error("Expected error in loading duplicate cluster name, got none")
}
}
func TestDuplicateContextName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
- cluster:
api-version: v1
server: https://kubernetes.default.svc:443
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
name: kubeconfig-cluster
contexts:
- context:
cluster: kubeconfig-cluster
namespace: default
user: kubeconfig-user
name: kubeconfig-context
- context:
cluster: test-example-cluster
namespace: test-example
user: test-example-user
name: kubeconfig-context
current-context: kubeconfig-context
users:
- name: kubeconfig-user
user:
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
`), os.FileMode(0755))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
_, err = LoadFromFile(configFile.Name())
if err == nil || !strings.Contains(err.Error(),
"error converting *[]NamedContext into *map[string]*api.Context: duplicate name \"kubeconfig-context\" in list") {
t.Error("Expected error in loading duplicate context name, got none")
}
}
func TestDuplicateUserName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
- cluster:
api-version: v1
server: https://kubernetes.default.svc:443
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
name: kubeconfig-cluster
contexts:
- context:
cluster: kubeconfig-cluster
namespace: default
user: kubeconfig-user
name: kubeconfig-context
current-context: kubeconfig-context
users:
- name: kubeconfig-user
user:
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
- name: kubeconfig-user
user:
tokenFile: /var/run/secrets/test.example.com/serviceaccount/token
`), os.FileMode(0755))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
_, err = LoadFromFile(configFile.Name())
if err == nil || !strings.Contains(err.Error(),
"error converting *[]NamedAuthInfo into *map[string]*api.AuthInfo: duplicate name \"kubeconfig-user\" in list") {
t.Error("Expected error in loading duplicate user name, got none")
}
}
func TestDuplicateExtensionName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
- cluster:
api-version: v1
server: https://kubernetes.default.svc:443
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
name: kubeconfig-cluster
contexts:
- context:
cluster: kubeconfig-cluster
namespace: default
user: kubeconfig-user
name: kubeconfig-context
current-context: kubeconfig-context
users:
- name: kubeconfig-user
user:
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
extensions:
- extension:
bytes: test
name: test-extension
- extension:
bytes: some-example
name: test-extension
`), os.FileMode(0755))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
_, err = LoadFromFile(configFile.Name())
if err == nil || !strings.Contains(err.Error(),
"error converting *[]NamedExtension into *map[string]runtime.Object: duplicate name \"test-extension\" in list") {
t.Error("Expected error in loading duplicate extension name, got none")
}
}
func TestResolveRelativePaths(t *testing.T) {
pathResolutionConfig1 := clientcmdapi.Config{
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"},
"absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"},
"relative-cmd-1": {Exec: &clientcmdapi.ExecConfig{Command: "../relative/client/cmd"}},
"absolute-cmd-1": {Exec: &clientcmdapi.ExecConfig{Command: "/absolute/client/cmd"}},
"PATH-cmd-1": {Exec: &clientcmdapi.ExecConfig{Command: "cmd"}},
},
Clusters: map[string]*clientcmdapi.Cluster{
"relative-server-1": {CertificateAuthority: "../relative/ca"},
@ -291,9 +462,21 @@ func TestResolveRelativePaths(t *testing.T) {
matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientCertificate, authInfo.ClientCertificate, t)
matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientKey, authInfo.ClientKey, t)
}
if key == "relative-cmd-1" {
foundAuthInfoCount++
matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos[key].Exec.Command), authInfo.Exec.Command, t)
}
if key == "absolute-cmd-1" {
foundAuthInfoCount++
matchStringArg(pathResolutionConfig1.AuthInfos[key].Exec.Command, authInfo.Exec.Command, t)
}
if key == "PATH-cmd-1" {
foundAuthInfoCount++
matchStringArg(pathResolutionConfig1.AuthInfos[key].Exec.Command, authInfo.Exec.Command, t)
}
}
if foundAuthInfoCount != 4 {
t.Errorf("Expected 4 users, found %v: %v", foundAuthInfoCount, mergedConfig.AuthInfos)
if foundAuthInfoCount != 7 {
t.Errorf("Expected 7 users, found %v: %v", foundAuthInfoCount, mergedConfig.AuthInfos)
}
}
@ -577,3 +760,30 @@ func Example_mergingEverythingNoConflicts() {
// user:
// token: red-token
}
func TestDeduplicate(t *testing.T) {
testCases := []struct {
src []string
expect []string
}{
{
src: []string{"a", "b", "c", "d", "e", "f"},
expect: []string{"a", "b", "c", "d", "e", "f"},
},
{
src: []string{"a", "b", "c", "b", "e", "f"},
expect: []string{"a", "b", "c", "e", "f"},
},
{
src: []string{"a", "a", "b", "b", "c", "b"},
expect: []string{"a", "b", "c"},
},
}
for _, testCase := range testCases {
get := deduplicate(testCase.src)
if !reflect.DeepEqual(get, testCase.expect) {
t.Errorf("expect: %v, get: %v", testCase.expect, get)
}
}
}

View File

@ -20,9 +20,8 @@ import (
"io"
"sync"
"github.com/golang/glog"
"k8s.io/klog"
"k8s.io/api/core/v1"
restclient "k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
@ -120,7 +119,7 @@ func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, e
// check for in-cluster configuration and use it
if config.icc.Possible() {
glog.V(4).Infof("Using in-cluster configuration")
klog.V(4).Infof("Using in-cluster configuration")
return config.icc.ClientConfig()
}
@ -145,7 +144,7 @@ func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
if len(ns) > 0 {
// if we got a non-default namespace from the kubeconfig, use it
if ns != v1.NamespaceDefault {
if ns != "default" {
return ns, false, nil
}
@ -157,7 +156,7 @@ func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
}
}
glog.V(4).Infof("Using in-cluster namespace")
klog.V(4).Infof("Using in-cluster namespace")
// allow the namespace from the service account token directory to be used.
return config.icc.Namespace()

View File

@ -237,6 +237,25 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err
}
}
if authInfo.Exec != nil {
if authInfo.AuthProvider != nil {
validationErrors = append(validationErrors, fmt.Errorf("authProvider cannot be provided in combination with an exec plugin for %s", authInfoName))
}
if len(authInfo.Exec.Command) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("command must be specified for %v to use exec authentication plugin", authInfoName))
}
if len(authInfo.Exec.APIVersion) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("apiVersion must be specified for %v to use exec authentication plugin", authInfoName))
}
for _, v := range authInfo.Exec.Env {
if len(v.Name) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("env variable name must be specified for %v to use exec authentication plugin", authInfoName))
} else if len(v.Value) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("env variable %s value must be specified for %v to use exec authentication plugin", v.Name, authInfoName))
}
}
}
// authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
if (len(methods) > 1) && (!usingAuthPath) {
validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))

View File

@ -365,6 +365,106 @@ func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
test.testConfig(t)
}
func TestValidateAuthInfoExec(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "/bin/example",
APIVersion: "clientauthentication.k8s.io/v1alpha1",
Args: []string{"hello", "world"},
Env: []clientcmdapi.ExecEnvVar{
{Name: "foo", Value: "bar"},
},
},
}
test := configValidationTest{
config: config,
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoExecNoVersion(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "/bin/example",
},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
"apiVersion must be specified for user to use exec authentication plugin",
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoExecNoCommand(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
APIVersion: "clientauthentication.k8s.io/v1alpha1",
},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
"command must be specified for user to use exec authentication plugin",
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoExecWithAuthProvider(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
AuthProvider: &clientcmdapi.AuthProviderConfig{
Name: "oidc",
},
Exec: &clientcmdapi.ExecConfig{
Command: "/bin/example",
APIVersion: "clientauthentication.k8s.io/v1alpha1",
},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
"authProvider cannot be provided in combination with an exec plugin for user",
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
func TestValidateAuthInfoExecInvalidEnv(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "/bin/example",
APIVersion: "clientauthentication.k8s.io/v1alpha1",
Env: []clientcmdapi.ExecEnvVar{
{Name: "foo"}, // No value
},
},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
"env variable foo value must be specified for user to use exec authentication plugin",
},
}
test.testAuthInfo("user", t)
test.testConfig(t)
}
type configValidationTest struct {
config *clientcmdapi.Config
expectedErrorSubstring []string