func (m *migrationHelper) getConfigName(field reflect.StructField) string {
	name := field.Tag.Get("confignamev1")
	if name == "" {
		name = field.Name
	}
	return name
}
Return the config name from the field. The field name is either specified in the
configname tag, otherwise it just uses the struct field name.
package migrator
import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
	log "github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
	"github.com/projectcalico/libcalico-go/lib/backend/model"
	"github.com/projectcalico/libcalico-go/lib/net"
	"github.com/projectcalico/libcalico-go/lib/upgrade/converters"
)
func (m *migrationHelper) queryAndConvertFelixConfigV1ToV3(
	data *MigrationData,
) error {
	m.statusBullet("handling FelixConfiguration (global) resource")
	kvps, err := m.clientv1.List(model.GlobalConfigListOptions{})
	if err != nil {
		return fmt.Errorf("error querying FelixConfiguration: %v", err)
	}
	globalConfig := apiv3.NewFelixConfiguration()
	globalConfig.Name = "default"
	if err := m.parseFelixConfigV1IntoResourceV3(kvps, globalConfig, data); err != nil {
		return fmt.Errorf("error converting FelixConfiguration: %v", err)
	}
	m.statusBullet("handling ClusterInformation (global) resource")
	clusterInfo := apiv3.NewClusterInformation()
	clusterInfo.Name = "default"
	if err = m.parseFelixConfigV1IntoResourceV3(kvps, clusterInfo, data); err != nil {
		return fmt.Errorf("error converting ClusterInformation: %v", err)
	}
	ready := m.clientv1.IsKDD()
	clusterInfo.Spec.DatastoreReady = &ready
	if m.clientv1.IsKDD() {
		m.statusBullet("skipping FelixConfiguration (per-node) resources - not supported")
	} else {
		m.statusBullet("handling FelixConfiguration (per-node) resources")
		kvps, err = m.clientv1.List(model.HostConfigListOptions{})
		if err != nil {
			return fmt.Errorf("error querying FelixConfiguration: %v", err)
		}
		nodeKvps := make(map[string][]*model.KVPair, 0)
		for _, kvp := range kvps {
			hk := kvp.Key.(model.HostConfigKey)
			hk.Hostname = converters.ConvertNodeName(hk.Hostname)
			kvp.Key = hk
			nodeKvps[hk.Hostname] = append(nodeKvps[hk.Hostname], kvp)
		}
		for node, kvps := range nodeKvps {
			nodeConfig := apiv3.NewFelixConfiguration()
			nodeConfig.Name = fmt.Sprintf("node.%s", node)
			if err := m.parseFelixConfigV1IntoResourceV3(kvps, nodeConfig, data); err != nil {
				return fmt.Errorf("error converting FelixConfiguration: %v", err)
			}
		}
	}
	return nil
}
func (m *migrationHelper) parseFelixConfigV1IntoResourceV3(
	kvps []*model.KVPair,
	res converters.Resource,
	data *MigrationData,
) error {
	logCxtRes := log.WithFields(log.Fields{
		"kind": res.GetObjectKind().GroupVersionKind().Kind,
		"name": res.GetObjectMeta().GetName(),
	})
	keysv1 := map[string]model.Key{}
	configv1 := map[string]string{}
	for _, kvp := range kvps {
		if kvp.Value == nil {
			continue
		}
		switch key := kvp.Key.(type) {
		case model.GlobalConfigKey:
			configv1[key.Name] = kvp.Value.(string)
			keysv1[key.Name] = key
		case model.HostConfigKey:
			configv1[key.Name] = kvp.Value.(string)
			keysv1[key.Name] = key
		}
	}
	specValue := reflect.ValueOf(res).Elem().FieldByName("Spec")
	if !specValue.IsValid() {
		return fmt.Errorf("unable to process config resource type: %v", res)
	}
	setField := false
	specType := specValue.Type()
	for i := 0; i < specType.NumField(); i++ {
		field := specType.Field(i)
		fieldValue := specValue.Field(i)
		configName := m.getConfigName(field)
		logCxt := logCxtRes.WithFields(log.Fields{
			"field":      field.Name,
			"configName": configName,
		})
		configStrValue, ok := configv1[configName]
		if !ok {
			logCxt.Debug("config value is not configured in v1")
			continue
		}
		isPtr := field.Type.Kind() == reflect.Ptr
		fieldName := field.Name
		switch {
		case strings.HasPrefix(fieldName, "Failsafe"):
			if configStrValue == "none" {
				vProtoPort := &[]apiv3.ProtoPort{}
				fieldValue.Set(reflect.ValueOf(vProtoPort))
				setField = true
				continue
			}
			vProtoPort, err := m.parseProtoPort(configStrValue)
			if err != nil {
				logCxt.WithError(err).Info("Failed to parse field")
				data.ConversionErrors = append(data.ConversionErrors, ConversionError{
					Cause:   err,
					KeyV1:   keysv1[configName],
					ValueV1: configStrValue,
					KeyV3:   resourceToKey(res),
				})
				continue
			}
			fieldValue.Set(reflect.ValueOf(vProtoPort))  
			setField = true
			continue
		case strings.HasPrefix(fieldName, "LogSeverity"):
			configStrValue = convertLogLevel(configStrValue)
		}
		_, ok = fieldValue.Interface().(*metav1.Duration)
		if ok {
			if duration, err := strconv.ParseFloat(configStrValue, 64); err != nil {
				logCxt.WithError(err).Info("Failed to parse float for Duration field")
				data.ConversionErrors = append(data.ConversionErrors, ConversionError{
					Cause:   fmt.Errorf("failed to parse float for Duration field: %v", err),
					KeyV1:   keysv1[configName],
					ValueV1: configStrValue,
					KeyV3:   resourceToKey(res),
				})
			} else {
				switch field.Tag.Get("configv1timescale") {
				case "milliseconds":
					fieldValue.Set(reflect.ValueOf(&metav1.Duration{Duration: time.Duration(duration * float64(time.Millisecond))}))
				default:
					fieldValue.Set(reflect.ValueOf(&metav1.Duration{Duration: time.Duration(duration * float64(time.Second))}))
				}
				setField = true
				continue
			}
		}
		var kind reflect.Kind
		if isPtr {
			kind = field.Type.Elem().Kind()
		} else {
			kind = fieldValue.Kind()
		}
		switch kind {
		case reflect.Uint32:
			if value, err := strconv.ParseUint(configStrValue, 10, 32); err != nil {
				logCxt.WithError(err).Info("Failed to parse uint32 field")
				data.ConversionErrors = append(data.ConversionErrors, ConversionError{
					Cause:   fmt.Errorf("failed to parse uint32 field: %v", err),
					KeyV1:   keysv1[configName],
					ValueV1: configStrValue,
					KeyV3:   resourceToKey(res),
				})
				continue
			} else if isPtr {
				vu := uint32(value)
				fieldValue.Set(reflect.ValueOf(&vu))
			} else {
				fieldValue.SetUint(value)
			}
		case reflect.Int:
			if value, err := strconv.ParseInt(configStrValue, 10, 64); err != nil {
				logCxt.WithError(err).Info("Failed to parse int field")
				data.ConversionErrors = append(data.ConversionErrors, ConversionError{
					Cause:   fmt.Errorf("failed to parse int field: %v", err),
					KeyV1:   keysv1[configName],
					ValueV1: configStrValue,
					KeyV3:   resourceToKey(res),
				})
				continue
			} else if isPtr {
				vi := int(value)
				fieldValue.Set(reflect.ValueOf(&vi))
			} else {
				fieldValue.SetInt(value)
			}
		case reflect.Bool:
			if value, err := strconv.ParseBool(configStrValue); err != nil {
				logCxt.WithError(err).Info("Failed to parse bool field")
				data.ConversionErrors = append(data.ConversionErrors, ConversionError{
					Cause:   fmt.Errorf("failed to parse bool field: %v", err),
					KeyV1:   keysv1[configName],
					ValueV1: configStrValue,
					KeyV3:   resourceToKey(res),
				})
				continue
			} else if isPtr {
				fieldValue.Set(reflect.ValueOf(&value))
			} else {
				fieldValue.SetBool(value)
			}
		case reflect.String:
			if isPtr {
				fieldValue.Set(reflect.ValueOf(&configStrValue))
			} else {
				fieldValue.SetString(configStrValue)
			}
		default:
			logCxt.Info("Unhandle field type")
			data.ConversionErrors = append(data.ConversionErrors, ConversionError{
				Cause: fmt.Errorf("unhandled field type, please raise an issue on GitHub " +
					"(https://github.com/projectcalico/calico) that includes this error message"),
				KeyV1:   keysv1[configName],
				ValueV1: configStrValue,
				KeyV3:   resourceToKey(res),
			})
			continue
		}
		setField = true
	}
	if setField {
		data.Resources = append(data.Resources, res)
	}
	return nil
}
func (m *migrationHelper) parseProtoPortFailed(msg string) error {
	return fmt.Errorf("failed to parse ProtoPort-%s", msg)
}
func (m *migrationHelper) parseProtoPort(raw string) (*[]apiv3.ProtoPort, error) {
	var result []apiv3.ProtoPort
	for _, portStr := range strings.Split(raw, ",") {
		portStr = strings.Trim(portStr, " ")
		if portStr == "" {
			continue
		}
		protocolStr := "tcp"
		netStr := ""
		if strings.Contains(portStr, "[") && strings.Contains(portStr, "]") {
			startIndex := strings.Index(portStr, "[")
			endIndex := strings.Index(portStr, "]:")
			netStr = portStr[startIndex+1 : endIndex]
			var withoutIPv6 strings.Builder
			withoutIPv6.WriteString(portStr[:startIndex])
			withoutIPv6.WriteString(portStr[endIndex+2:])
			portStr = withoutIPv6.String()
		}
		parts := strings.Split(portStr, ":")
		if len(parts) > 3 {
			return nil, m.parseProtoPortFailed("ports should be <protocol>:<net>:<number> or <protocol>:<number> or <number>")
		}
		if len(parts) > 2 {
			netStr = parts[1]
			protocolStr = strings.ToUpper(parts[0])
			portStr = parts[2]
		}
		if len(parts) == 2 {
			protocolStr = strings.ToUpper(parts[0])
			portStr = parts[1]
		}
		if protocolStr != "TCP" && protocolStr != "UDP" {
			return nil, m.parseProtoPortFailed("unknown protocol: " + protocolStr)
		}
		port, err := strconv.Atoi(portStr)
		if err != nil {
			return nil, m.parseProtoPortFailed("ports should be integers")
		}
		if port < 0 || port > 65535 {
			err = m.parseProtoPortFailed("ports must be in range 0-65535")
			return nil, err
		}
		protoPort := apiv3.ProtoPort{
			Protocol: protocolStr,
			Port:     uint16(port),
		}
		if netStr != "" {
			_, netParsed, err := net.ParseCIDROrIP(netStr)
			if err != nil {
				err = m.parseProtoPortFailed("invalid CIDR or IP " + netStr)
				return nil, err
			}
			protoPort.Net = netParsed.String()
		}
		result = append(result, protoPort)
	}
	return &result, nil
}