mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-21 12:48:30 +00:00
Isolate mergo use, add test coverage and error checking
Kubernetes-commit: 3b3886a4509a83aff0afe7ad42da9b7819a17aed
This commit is contained in:
parent
e2b5fa74cd
commit
b3dca7eb8a
@ -29,8 +29,6 @@ import (
|
|||||||
clientauth "k8s.io/client-go/tools/auth"
|
clientauth "k8s.io/client-go/tools/auth"
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -241,10 +239,14 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mergo.Merge(clientConfig, userAuthPartialConfig, mergo.WithOverride)
|
if err := merge(clientConfig, userAuthPartialConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
serverAuthPartialConfig := getServerIdentificationPartialConfig(configClusterInfo)
|
serverAuthPartialConfig := getServerIdentificationPartialConfig(configClusterInfo)
|
||||||
mergo.Merge(clientConfig, serverAuthPartialConfig, mergo.WithOverride)
|
if err := merge(clientConfig, serverAuthPartialConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientConfig, nil
|
return clientConfig, nil
|
||||||
@ -326,8 +328,12 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
|||||||
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
||||||
previouslyMergedConfig := mergedConfig
|
previouslyMergedConfig := mergedConfig
|
||||||
mergedConfig = &restclient.Config{}
|
mergedConfig = &restclient.Config{}
|
||||||
mergo.Merge(mergedConfig, promptedConfig, mergo.WithOverride)
|
if err := merge(mergedConfig, promptedConfig); err != nil {
|
||||||
mergo.Merge(mergedConfig, previouslyMergedConfig, mergo.WithOverride)
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := merge(mergedConfig, previouslyMergedConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
config.promptedCredentials.username = mergedConfig.Username
|
config.promptedCredentials.username = mergedConfig.Username
|
||||||
config.promptedCredentials.password = mergedConfig.Password
|
config.promptedCredentials.password = mergedConfig.Password
|
||||||
}
|
}
|
||||||
@ -335,7 +341,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
|||||||
return mergedConfig, nil
|
return mergedConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
|
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged for only user identification information
|
||||||
func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
|
func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
|
||||||
config := &restclient.Config{}
|
config := &restclient.Config{}
|
||||||
config.Username = info.User
|
config.Username = info.User
|
||||||
@ -495,12 +501,16 @@ func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
|||||||
|
|
||||||
mergedContext := clientcmdapi.NewContext()
|
mergedContext := clientcmdapi.NewContext()
|
||||||
if configContext, exists := contexts[contextName]; exists {
|
if configContext, exists := contexts[contextName]; exists {
|
||||||
mergo.Merge(mergedContext, configContext, mergo.WithOverride)
|
if err := merge(mergedContext, configContext); err != nil {
|
||||||
|
return clientcmdapi.Context{}, err
|
||||||
|
}
|
||||||
} else if required {
|
} else if required {
|
||||||
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
||||||
}
|
}
|
||||||
if config.overrides != nil {
|
if config.overrides != nil {
|
||||||
mergo.Merge(mergedContext, config.overrides.Context, mergo.WithOverride)
|
if err := merge(mergedContext, config.overrides.Context); err != nil {
|
||||||
|
return clientcmdapi.Context{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return *mergedContext, nil
|
return *mergedContext, nil
|
||||||
@ -513,12 +523,16 @@ func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
|||||||
|
|
||||||
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
||||||
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
||||||
mergo.Merge(mergedAuthInfo, configAuthInfo, mergo.WithOverride)
|
if err := merge(mergedAuthInfo, configAuthInfo); err != nil {
|
||||||
|
return clientcmdapi.AuthInfo{}, err
|
||||||
|
}
|
||||||
} else if required {
|
} else if required {
|
||||||
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
||||||
}
|
}
|
||||||
if config.overrides != nil {
|
if config.overrides != nil {
|
||||||
mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo, mergo.WithOverride)
|
if err := merge(mergedAuthInfo, config.overrides.AuthInfo); err != nil {
|
||||||
|
return clientcmdapi.AuthInfo{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return *mergedAuthInfo, nil
|
return *mergedAuthInfo, nil
|
||||||
@ -531,15 +545,21 @@ func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
|||||||
|
|
||||||
mergedClusterInfo := clientcmdapi.NewCluster()
|
mergedClusterInfo := clientcmdapi.NewCluster()
|
||||||
if config.overrides != nil {
|
if config.overrides != nil {
|
||||||
mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults, mergo.WithOverride)
|
if err := merge(mergedClusterInfo, config.overrides.ClusterDefaults); err != nil {
|
||||||
|
return clientcmdapi.Cluster{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
||||||
mergo.Merge(mergedClusterInfo, configClusterInfo, mergo.WithOverride)
|
if err := merge(mergedClusterInfo, configClusterInfo); err != nil {
|
||||||
|
return clientcmdapi.Cluster{}, err
|
||||||
|
}
|
||||||
} else if required {
|
} else if required {
|
||||||
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
||||||
}
|
}
|
||||||
if config.overrides != nil {
|
if config.overrides != nil {
|
||||||
mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo, mergo.WithOverride)
|
if err := merge(mergedClusterInfo, config.overrides.ClusterInfo); err != nil {
|
||||||
|
return clientcmdapi.Cluster{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// * An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
// * An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
||||||
|
@ -22,16 +22,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
utiltesting "k8s.io/client-go/util/testing"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/dump"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
utiltesting "k8s.io/client-go/util/testing"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergoSemantics(t *testing.T) {
|
func TestMergeSemantics(t *testing.T) {
|
||||||
type U struct {
|
type U struct {
|
||||||
A string
|
A string
|
||||||
B int64
|
B int64
|
||||||
@ -41,7 +42,11 @@ func TestMergoSemantics(t *testing.T) {
|
|||||||
X string
|
X string
|
||||||
Y int64
|
Y int64
|
||||||
U U
|
U U
|
||||||
|
|
||||||
|
StringPtr *string
|
||||||
|
StructPtr *U
|
||||||
}
|
}
|
||||||
|
|
||||||
var testDataStruct = []struct {
|
var testDataStruct = []struct {
|
||||||
dst T
|
dst T
|
||||||
src T
|
src T
|
||||||
@ -62,22 +67,31 @@ func TestMergoSemantics(t *testing.T) {
|
|||||||
src: T{S: []string{"test1", "test2", "test3"}},
|
src: T{S: []string{"test1", "test2", "test3"}},
|
||||||
expected: T{S: []string{"test1", "test2", "test3"}},
|
expected: T{S: []string{"test1", "test2", "test3"}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
dst: T{},
|
||||||
|
src: T{StringPtr: ptr.To("a"), StructPtr: ptr.To(U{A: "a", B: 1})},
|
||||||
|
expected: T{StringPtr: ptr.To("a"), StructPtr: ptr.To(U{A: "a", B: 1})},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dst: T{StringPtr: ptr.To("a"), StructPtr: ptr.To(U{A: "a", B: 1})},
|
||||||
|
src: T{},
|
||||||
|
expected: T{StringPtr: ptr.To("a"), StructPtr: ptr.To(U{A: "a", B: 1})},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dst: T{StringPtr: ptr.To("a"), StructPtr: ptr.To(U{A: "a", B: 1})},
|
||||||
|
src: T{StringPtr: ptr.To("b"), StructPtr: ptr.To(U{A: "b", B: 0})}, // zero value B overrides non-zero value B in pointer member
|
||||||
|
expected: T{StringPtr: ptr.To("b"), StructPtr: ptr.To(U{A: "b", B: 0})},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, data := range testDataStruct {
|
for i, data := range testDataStruct {
|
||||||
err := mergo.Merge(&data.dst, &data.src, mergo.WithOverride)
|
t.Logf("case %d: %+v, %+v", i, data.dst, data.src)
|
||||||
|
err := merge(&data.dst, &data.src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error while merging: %s", err)
|
t.Errorf("error while merging: %s", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(data.dst, data.expected) {
|
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.
|
// 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
|
t.Errorf("merge did not provide expected output:\ngot: %s\nwant: %s\ndiff: %s", dump.OneLine(data.dst), dump.OneLine(data.expected), cmp.Diff(data.dst, data.expected))
|
||||||
// unexpectedly.
|
|
||||||
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,22 +107,152 @@ func TestMergoSemantics(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, data := range testDataMap {
|
for _, data := range testDataMap {
|
||||||
err := mergo.Merge(&data.dst, &data.src, mergo.WithOverride)
|
err := merge(&data.dst, &data.src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error while merging: %s", err)
|
t.Errorf("error while merging: %s", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(data.dst, data.expected) {
|
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.
|
// 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
|
t.Errorf("merge did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
||||||
// unexpectedly.
|
|
||||||
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Struct struct {
|
||||||
|
String string
|
||||||
|
StringP *string
|
||||||
|
Int int
|
||||||
|
IntP *int
|
||||||
|
Bool bool
|
||||||
|
BoolP *bool
|
||||||
|
Slice []string
|
||||||
|
SliceP *[]string
|
||||||
|
Map map[string]string
|
||||||
|
MapP *map[string]string
|
||||||
|
}
|
||||||
|
type T2 struct {
|
||||||
|
String string
|
||||||
|
StringP *string
|
||||||
|
Int int
|
||||||
|
IntP *int
|
||||||
|
Bool bool
|
||||||
|
BoolP *bool
|
||||||
|
Slice []string
|
||||||
|
SliceP *[]string
|
||||||
|
Map map[string]string
|
||||||
|
MapP *map[string]string
|
||||||
|
Struct Struct
|
||||||
|
StructP *Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
var testcases = []struct {
|
||||||
|
name string
|
||||||
|
dst T2
|
||||||
|
src T2
|
||||||
|
expected T2
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "zero noop",
|
||||||
|
dst: T2{},
|
||||||
|
src: T2{},
|
||||||
|
expected: T2{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "override zero dst",
|
||||||
|
dst: T2{},
|
||||||
|
src: T2{
|
||||||
|
String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"}),
|
||||||
|
Struct: Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})},
|
||||||
|
StructP: ptr.To(Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})}),
|
||||||
|
},
|
||||||
|
expected: T2{
|
||||||
|
String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"}),
|
||||||
|
Struct: Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})},
|
||||||
|
StructP: ptr.To(Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noop zero src",
|
||||||
|
dst: T2{
|
||||||
|
String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"}),
|
||||||
|
Struct: Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})},
|
||||||
|
StructP: ptr.To(Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})}),
|
||||||
|
},
|
||||||
|
src: T2{},
|
||||||
|
expected: T2{
|
||||||
|
String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"}),
|
||||||
|
Struct: Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})},
|
||||||
|
StructP: ptr.To(Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overwrite non-zero values, merge maps",
|
||||||
|
dst: T2{
|
||||||
|
String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}),
|
||||||
|
Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"}),
|
||||||
|
Struct: Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})},
|
||||||
|
StructP: ptr.To(Struct{String: "a", StringP: ptr.To("a"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"a"}, SliceP: ptr.To([]string{"a"}), Map: map[string]string{"a": "1"}, MapP: ptr.To(map[string]string{"a": "1"})}),
|
||||||
|
},
|
||||||
|
src: T2{
|
||||||
|
String: "b", StringP: ptr.To("b"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"b"}, SliceP: ptr.To([]string{"b"}),
|
||||||
|
Map: map[string]string{"b": "1"}, MapP: ptr.To(map[string]string{"b": "1"}),
|
||||||
|
Struct: Struct{String: "b", StringP: ptr.To("b"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"b"}, SliceP: ptr.To([]string{"b"}), Map: map[string]string{"b": "1"}, MapP: ptr.To(map[string]string{"b": "1"})},
|
||||||
|
StructP: ptr.To(Struct{String: "b", StringP: ptr.To("b"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"b"}, SliceP: ptr.To([]string{"b"}), Map: map[string]string{"b": "1"}, MapP: ptr.To(map[string]string{"b": "1"})}),
|
||||||
|
},
|
||||||
|
expected: T2{
|
||||||
|
// overwritten
|
||||||
|
String: "b", StringP: ptr.To("b"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"b"}, SliceP: ptr.To([]string{"b"}), MapP: ptr.To(map[string]string{"b": "1"}),
|
||||||
|
// merged
|
||||||
|
Map: map[string]string{"a": "1", "b": "1"},
|
||||||
|
Struct: Struct{
|
||||||
|
// overwritten
|
||||||
|
String: "b", StringP: ptr.To("b"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"b"}, SliceP: ptr.To([]string{"b"}), MapP: ptr.To(map[string]string{"b": "1"}),
|
||||||
|
// merged
|
||||||
|
Map: map[string]string{"a": "1", "b": "1"},
|
||||||
|
},
|
||||||
|
// overwritten
|
||||||
|
StructP: ptr.To(Struct{String: "b", StringP: ptr.To("b"), Int: 1, IntP: ptr.To(1), Bool: true, BoolP: ptr.To(true), Slice: []string{"b"}, SliceP: ptr.To([]string{"b"}), Map: map[string]string{"b": "1"}, MapP: ptr.To(map[string]string{"b": "1"})}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overwrite non-zero values, merge maps",
|
||||||
|
dst: T2{
|
||||||
|
Struct: Struct{
|
||||||
|
String: "a", Int: 1, Map: map[string]string{"a": "1", "shared": "1"}, MapP: ptr.To(map[string]string{"a": "1", "shared": "1"}),
|
||||||
|
},
|
||||||
|
StructP: ptr.To(Struct{
|
||||||
|
String: "a", Int: 1, Map: map[string]string{"a": "1", "shared": "1"}, MapP: ptr.To(map[string]string{"a": "1", "shared": "1"}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
src: T2{
|
||||||
|
Struct: Struct{
|
||||||
|
String: "b", Int: 0, Map: map[string]string{"b": "1"},
|
||||||
|
},
|
||||||
|
StructP: ptr.To(Struct{
|
||||||
|
String: "b", Int: 0, Map: map[string]string{"b": "1"},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expected: T2{
|
||||||
|
Struct: Struct{
|
||||||
|
String: "b", Int: 1 /* preserved */, Map: map[string]string{"a": "1", "b": "1", "shared": "1"} /* merged */, MapP: ptr.To(map[string]string{"a": "1", "shared": "1"}), /* preserved */
|
||||||
|
},
|
||||||
|
StructP: ptr.To(Struct{
|
||||||
|
String: "b", Int: 0 /* overwritten */, Map: map[string]string{"b": "1"} /* unmerged */, MapP: nil, /* overwritten*/
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := merge(&tc.dst, &tc.src)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error while merging: %s", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tc.dst, tc.expected) {
|
||||||
|
// This test verifies that the semantics of the merge are what we expect.
|
||||||
|
t.Errorf("merge did not provide expected output:\ngot: %s\nwant: %s\ndiff: %s", dump.OneLine(tc.dst), dump.OneLine(tc.expected), cmp.Diff(tc.dst, tc.expected))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createValidTestConfig() *clientcmdapi.Config {
|
func createValidTestConfig() *clientcmdapi.Config {
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -248,7 +247,9 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
|||||||
mapConfig := clientcmdapi.NewConfig()
|
mapConfig := clientcmdapi.NewConfig()
|
||||||
|
|
||||||
for _, kubeconfig := range kubeconfigs {
|
for _, kubeconfig := range kubeconfigs {
|
||||||
mergo.Merge(mapConfig, kubeconfig, mergo.WithOverride)
|
if err := merge(mapConfig, kubeconfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge all of the struct values in the reverse order so that priority is given correctly
|
// merge all of the struct values in the reverse order so that priority is given correctly
|
||||||
@ -256,14 +257,20 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
|||||||
nonMapConfig := clientcmdapi.NewConfig()
|
nonMapConfig := clientcmdapi.NewConfig()
|
||||||
for i := len(kubeconfigs) - 1; i >= 0; i-- {
|
for i := len(kubeconfigs) - 1; i >= 0; i-- {
|
||||||
kubeconfig := kubeconfigs[i]
|
kubeconfig := kubeconfigs[i]
|
||||||
mergo.Merge(nonMapConfig, kubeconfig, mergo.WithOverride)
|
if err := merge(nonMapConfig, kubeconfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
|
// 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.
|
// get the values we expect.
|
||||||
config := clientcmdapi.NewConfig()
|
config := clientcmdapi.NewConfig()
|
||||||
mergo.Merge(config, mapConfig, mergo.WithOverride)
|
if err := merge(config, mapConfig); err != nil {
|
||||||
mergo.Merge(config, nonMapConfig, mergo.WithOverride)
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := merge(config, nonMapConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if rules.ResolvePaths() {
|
if rules.ResolvePaths() {
|
||||||
if err := ResolveLocalPaths(config); err != nil {
|
if err := ResolveLocalPaths(config); err != nil {
|
||||||
|
29
tools/clientcmd/merge.go
Normal file
29
tools/clientcmd/merge.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 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 clientcmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// recursively merges src into dst:
|
||||||
|
// - non-pointer struct fields are recursively merged
|
||||||
|
// - maps are shallow merged with src keys taking priority over dst
|
||||||
|
// - non-zero src fields encountered during recursion that are not maps or structs overwrite and recursion stops
|
||||||
|
func merge(dst, src any) error {
|
||||||
|
return mergo.Merge(dst, src, mergo.WithOverride)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user