func (c *configUpdateProcessor) createV1Key(node, name string) model.Key {
	if node == "" {
		return c.globalConfigKeyFn(name)
	} else {
		return c.nodeConfigKeyFn(node, name)
	}
}
Create the appropriate v1 config key depending on whether this global or node specific
configuration.
package updateprocessors
import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
	log "github.com/sirupsen/logrus"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"github.com/projectcalico/libcalico-go/lib/backend/model"
	"github.com/projectcalico/libcalico-go/lib/backend/watchersyncer"
	cerrors "github.com/projectcalico/libcalico-go/lib/errors"
)
const (
	AllowAnnotations    = true
	DisallowAnnotations = false
)
func NewConfigUpdateProcessor(
	specType reflect.Type,
	allowAnnotations bool,
	nodeConfigKeyFn NodeConfigKeyFn,
	globalConfigKeyFn GlobalConfigKeyFn,
	valueConverters map[string]ConfigFieldValueToV1ModelValue,
) watchersyncer.SyncerUpdateProcessor {
	names := make(map[string]struct{}, specType.NumField())
	for i := 0; i < specType.NumField(); i++ {
		names[getConfigName(specType.Field(i))] = struct{}{}
	}
	return &configUpdateProcessor{
		specType:          specType,
		allowAnnotations:  allowAnnotations,
		nodeConfigKeyFn:   nodeConfigKeyFn,
		globalConfigKeyFn: globalConfigKeyFn,
		names:             names,
		additionalNames:   map[string]struct{}{},
		valueConverters:   valueConverters,
	}
}
type NodeConfigKeyFn func(node, name string) model.Key
type GlobalConfigKeyFn func(name string) model.Key
type ConfigFieldValueToV1ModelValue func(value interface{}) interface{}
var (
	globalConfigName        = "default"
	perNodeConfigNamePrefix = "node."
	annotationConfigPrefix  = "config.projectcalico.org/"
)
type configUpdateProcessor struct {
	specType          reflect.Type
	allowAnnotations  bool
	nodeConfigKeyFn   NodeConfigKeyFn
	globalConfigKeyFn GlobalConfigKeyFn
	names             map[string]struct{}
	additionalNames   map[string]struct{}
	valueConverters   map[string]ConfigFieldValueToV1ModelValue
}
func (c *configUpdateProcessor) Process(kvp *model.KVPair) ([]*model.KVPair, error) {
	if kvp.Value == nil {
		return c.processDeleted(kvp)
	} else {
		return c.processAddOrModified(kvp)
	}
}
func (c *configUpdateProcessor) processDeleted(kvp *model.KVPair) ([]*model.KVPair, error) {
	node, err := c.extractNode(kvp.Key)
	if err != nil {
		return nil, err
	}
	kvps := make([]*model.KVPair, len(c.names)+len(c.additionalNames))
	i := 0
	for name := range c.names {
		kvps[i] = &model.KVPair{
			Key: c.createV1Key(node, name),
		}
		i++
	}
	for name := range c.additionalNames {
		kvps[i] = &model.KVPair{
			Key: c.createV1Key(node, name),
		}
		i++
	}
	return kvps, nil
}
func (c *configUpdateProcessor) processAddOrModified(kvp *model.KVPair) ([]*model.KVPair, error) {
	node, err := c.extractNode(kvp.Key)
	if err != nil {
		log.WithField("Key", kvp.Key).Warning("Failed to extract node/global name from key")
		return nil, err
	}
	overrides, err := c.extractAnnotations(kvp)
	if err != nil {
		return nil, err
	}
	specValue := reflect.ValueOf(kvp.Value).Elem().FieldByName("Spec")
	if !specValue.IsValid() || specValue.Type() != c.specType {
		log.WithFields(log.Fields{
			"SpecValue":    specValue,
			"ExpectedType": c.specType,
		}).Info("Spec is missing or incorrect type")
		return nil, errors.New("Spec is missing or incorrect type")
	}
	var kvps []*model.KVPair
	numFields := len(c.names)
	for i := 0; i < numFields; i++ {
		fieldInfo := c.specType.Field(i)
		name := getConfigName(fieldInfo)
		key := c.createV1Key(node, name)
		if key == nil {
			log.WithField("name", name).Debug("No associated v1 key, skipping field")
			continue
		}
		if v, ok := overrides[name]; ok {
			kvps = append(kvps, &model.KVPair{
				Key:      key,
				Value:    v,
				Revision: kvp.Revision,
			})
			delete(overrides, name)
			continue
		}
		var value interface{}
		field := specValue.Field(i)
		if field.Kind() == reflect.Ptr {
			if !field.IsNil() {
				value = field.Elem().Interface()
			}
		} else {
			value = field.Interface()
			if s, ok := value.(string); ok && len(s) == 0 {
				value = nil
			}
		}
		if value != nil {
			if s, ok := c.valueConverters[name]; ok {
				value = s(value)
			} else {
				switch vt := value.(type) {
				case string:
					value = vt
				case v1.Duration:
					switch fieldInfo.Tag.Get("configv1timescale") {
					case "milliseconds":
						ms := vt.Duration / time.Millisecond
						nMs := vt.Duration % time.Millisecond
						value = fmt.Sprintf("%v", float64(ms)+float64(nMs)/1e6)
					default:
						value = fmt.Sprintf("%v", vt.Seconds())
					}
				case []string:
					value = strings.Join(vt, ",")
				default:
					value = fmt.Sprintf("%v", vt)
				}
			}
		}
		kvps = append(kvps, &model.KVPair{
			Key:      key,
			Value:    value,
			Revision: kvp.Revision,
		})
	}
	var value interface{}
	for name := range c.additionalNames {
		if v, ok := overrides[name]; ok {
			value = v
			delete(overrides, name)
		} else {
			value = nil
		}
		kvps = append(kvps, &model.KVPair{
			Key:      c.createV1Key(node, name),
			Value:    value,
			Revision: kvp.Revision,
		})
	}
	for name, value := range overrides {
		kvps = append(kvps, &model.KVPair{
			Key:      c.createV1Key(node, name),
			Value:    value,
			Revision: kvp.Revision,
		})
		c.additionalNames[name] = struct{}{}
	}
	return kvps, nil
}
func (c *configUpdateProcessor) OnSyncerStarting() {
	c.additionalNames = map[string]struct{}{}
}
func (c *configUpdateProcessor) extractAnnotations(kvp *model.KVPair) (map[string]string, error) {
	ma, ok := kvp.Value.(v1.ObjectMetaAccessor)
	if !ok {
		return nil, errors.New("Unexpected value type in conversion")
	}
	overrides := map[string]string{}
	if !c.allowAnnotations {
		log.Debug("Annotations are disallowed - skipping extraction")
		return overrides, nil
	}
	annotations := ma.GetObjectMeta().GetAnnotations()
	for k, v := range annotations {
		if strings.HasPrefix(k, annotationConfigPrefix) {
			overrides[k[len(annotationConfigPrefix):]] = v
		}
	}
	return overrides, nil
}
func (c *configUpdateProcessor) extractNode(key model.Key) (string, error) {
	k, ok := key.(model.ResourceKey)
	if !ok {
		return "", errors.New("Unexpected key type in conversion")
	}
	switch {
	case k.Name == globalConfigName:
		return "", nil
	case strings.HasPrefix(k.Name, perNodeConfigNamePrefix):
		return k.Name[len(perNodeConfigNamePrefix):], nil
	default:
		return "", cerrors.ErrorParsingDatastoreEntry{RawKey: k.Name}
	}
}