@@ -7,14 +7,21 @@ import (
77 "strings"
88 "testing"
99
10- operatorv1 "github.com/openshift/api/operator/v1"
1110 corev1 "k8s.io/api/core/v1"
11+ "k8s.io/apimachinery/pkg/api/equality"
1212 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313 "k8s.io/apimachinery/pkg/labels"
1414 "k8s.io/apimachinery/pkg/runtime"
15+ "k8s.io/apimachinery/pkg/util/diff"
1516 "k8s.io/client-go/kubernetes/fake"
1617 "k8s.io/client-go/kubernetes/scheme"
1718 corev1listers "k8s.io/client-go/listers/core/v1"
19+
20+ "github.com/ghodss/yaml"
21+ kubecontrolplanev1 "github.com/openshift/api/kubecontrolplane/v1"
22+ operatorv1 "github.com/openshift/api/operator/v1"
23+ "github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
24+ "github.com/stretchr/testify/require"
1825)
1926
2027var codec = scheme .Codecs .LegacyCodec (scheme .Scheme .PrioritizedVersionsAllGroups ()... )
@@ -417,6 +424,142 @@ func TestIsRequiredConfigPresentEtcdEndpoints(t *testing.T) {
417424 }
418425}
419426
427+ func TestSpecialMergeRules (t * testing.T ) {
428+ mergeRules := map [string ]resourcemerge.MergeFunc {
429+ ".apiServerArguments.enable-admission-plugins" : mergeStringSlices ,
430+ ".apiServerArguments.disable-admission-plugins" : mergeStringSlices ,
431+ }
432+
433+ configsToMerge := []* kubecontrolplanev1.KubeAPIServerConfig {
434+ {
435+ APIServerArguments : map [string ]kubecontrolplanev1.Arguments {
436+ "audit-log-format" : []string {"json" },
437+ "enable-admission-plugins" : []string {"enabled0" },
438+ "disable-admission-plugins" : []string {"disabled0" },
439+ },
440+ },
441+ {
442+ APIServerArguments : map [string ]kubecontrolplanev1.Arguments {
443+ "audit-log-format" : []string {"yaml" },
444+ "enable-admission-plugins" : []string {"enabled1" },
445+ "disable-admission-plugins" : []string {"disabled1" },
446+ },
447+ },
448+ {
449+ APIServerArguments : map [string ]kubecontrolplanev1.Arguments {
450+ "enable-admission-plugins" : []string {"enabled2" },
451+ "disable-admission-plugins" : []string {"disabled2" },
452+ },
453+ },
454+ }
455+
456+ configs := make ([][]byte , 0 , len (configsToMerge ))
457+ for _ , cfg := range configsToMerge {
458+ cfgBytes , err := yaml .Marshal (cfg )
459+ require .NoError (t , err )
460+ configs = append (configs , cfgBytes )
461+ }
462+
463+ result , _ , err := resourcemerge .MergePrunedConfigMap (
464+ & kubecontrolplanev1.KubeAPIServerConfig {},
465+ & corev1.ConfigMap {Data : map [string ]string {"config.yaml" : "" }},
466+ "config.yaml" ,
467+ mergeRules ,
468+ configs ... ,
469+ )
470+ require .NoError (t , err )
471+
472+ config := & kubecontrolplanev1.KubeAPIServerConfig {}
473+ err = yaml .Unmarshal ([]byte (result .Data ["config.yaml" ]), config )
474+ require .NoError (t , err )
475+
476+ // plugins have special merge rules, therefore slices must be merged
477+ require .ElementsMatch (t , config .APIServerArguments ["enable-admission-plugins" ], []string {"enabled0" , "enabled1" , "enabled2" })
478+ require .ElementsMatch (t , config .APIServerArguments ["disable-admission-plugins" ], []string {"disabled0" , "disabled1" , "disabled2" })
479+
480+ // audit-log-format does not have any special merge rules, therefore value gets replaced
481+ require .ElementsMatch (t , config .APIServerArguments ["audit-log-format" ], []string {"yaml" })
482+ }
483+
484+ func TestMergeStringSlices (t * testing.T ) {
485+ for _ , tt := range []struct {
486+ name string
487+ dst any
488+ src any
489+ expected any
490+ expectError bool
491+ }{
492+ {
493+ name : "dst and src empty" ,
494+ dst : nil ,
495+ src : nil ,
496+ expected : nil ,
497+ expectError : false ,
498+ },
499+ {
500+ name : "src empty" ,
501+ dst : []any {"value" },
502+ src : nil ,
503+ expected : []any {"value" },
504+ expectError : false ,
505+ },
506+ {
507+ name : "dst empty" ,
508+ dst : nil ,
509+ src : []any {"value" },
510+ expected : []any {"value" },
511+ expectError : false ,
512+ },
513+ {
514+ name : "dst not a slice" ,
515+ dst : "not-a-slice" ,
516+ src : []any {"new-item" },
517+ expected : nil ,
518+ expectError : true ,
519+ },
520+ {
521+ name : "src not a slice" ,
522+ dst : []any {"existing-item" },
523+ src : "not-a-slice" ,
524+ expected : nil ,
525+ expectError : true ,
526+ },
527+ {
528+ name : "dst not a string slice" ,
529+ dst : []any {1 , 2 , 3 },
530+ src : []any {"new-item" },
531+ expected : nil ,
532+ expectError : true ,
533+ },
534+ {
535+ name : "src not a string slice" ,
536+ dst : []any {"existing-item" },
537+ src : []any {1 , 2 , 3 },
538+ expected : nil ,
539+ expectError : true ,
540+ },
541+ {
542+ name : "dst and src merged" ,
543+ dst : []any {"existing-item" },
544+ src : []any {"new-item" },
545+ expected : []string {"existing-item" , "new-item" },
546+ expectError : false ,
547+ },
548+ } {
549+ t .Run (tt .name , func (t * testing.T ) {
550+ merged , err := mergeStringSlices (tt .dst , tt .src , "" )
551+ if tt .expectError != (err != nil ) {
552+ t .Errorf ("expected error: %v; got %v" , tt .expectError , err )
553+ }
554+
555+ if ! equality .Semantic .DeepEqual (tt .expected , merged ) {
556+ t .Errorf ("unexpected merged slice: %s" , diff .ObjectReflectDiff (tt .expected , merged ))
557+ }
558+
559+ })
560+ }
561+ }
562+
420563func makeEtcdEndpointsCM (endpoints ... string ) * corev1.ConfigMap {
421564 cm := & corev1.ConfigMap {}
422565 cm .Name = etcdEndpointName
0 commit comments