internal/api/merge.go (68 lines of code) (raw):

package api import ( "encoding/json" "reflect" "dario.cat/mergo" "k8s.io/apimachinery/pkg/runtime" "github.com/aws/eks-hybrid/internal/util" ) // Merges two NodeConfigs with custom collision handling func (dst *NodeConfig) Merge(src *NodeConfig) error { return mergo.Merge(dst, src, mergo.WithOverride, mergo.WithTransformers(kubeletTransformer{})) } const ( kubeletFlagsName = "Flags" kubeletConfigName = "Config" ) type kubeletTransformer struct{} func (k kubeletTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { if typ == reflect.TypeOf(KubeletOptions{}) { return func(dst, src reflect.Value) error { k.transformFlags( dst.FieldByName(kubeletFlagsName), src.FieldByName(kubeletFlagsName), ) if err := k.transformConfig( dst.FieldByName(kubeletConfigName), src.FieldByName(kubeletConfigName), ); err != nil { return err } return nil } } return nil } func (k kubeletTransformer) transformFlags(dst, src reflect.Value) { if dst.CanSet() { // kubelet flags are parsed using https://github.com/spf13/pflag, where // flag order determines precedence. For single-value flags this is // equivalent to latter values overriding former ones, but for flags // with multiple values like `--node-labels`, the values from every // instance will be merged with precedence based on order. // // Based on this behavior, we choose to explicitly append slices for // this field and no other slices. dst.Set(reflect.AppendSlice(dst, src)) } } func (k kubeletTransformer) transformConfig(dst, src reflect.Value) error { if dst.CanSet() { if dst.Len() <= 0 { // if the destination is empty just use the source data dst.Set(src) } else if src.Len() > 0 { // kubelet config in an inline document here, so we explicitly // perform a merge with dst and src data. mergedMap, err := util.DocumentMerge(dst.Interface(), src.Interface(), mergo.WithOverride) if err != nil { return err } rawMap, err := toInlineDocument(mergedMap) if err != nil { return err } dst.Set(reflect.ValueOf(rawMap)) } } return nil } func toInlineDocument(m map[string]interface{}) (InlineDocument, error) { rawMap := make(InlineDocument) for key, value := range m { rawBytes, err := json.Marshal(value) if err != nil { return nil, err } rawMap[key] = runtime.RawExtension{Raw: rawBytes} } return rawMap, nil }