kubernetes/kops · error

only accepts structs; got %T

Error message

only accepts structs; got %T

What it means

getValueFromStruct walks a dotted key path through a struct using reflection, dereferencing pointers along the way. If, after dereferencing, the current value is not a struct (and cannot be indexed further by field name), it returns this error. It exists because FieldByName only works on struct kinds.

Solutions

  1. Verify each segment of the dotted tag path resolves to a struct until the final segment
  2. Update the configfile tag to match the current (possibly changed) shape of the target struct
  3. Ensure the `target` argument passed to BuildConfigYaml is a pointer to a struct, not a map or slice
  4. If the source field legitimately became a non-struct, remove or rework the tag since dotted traversal only supports nested structs

Example fix

// before
FeatureGates map[string]string `json:"featureGates,omitempty" configfile:"feature-gates.enabled"`
// after (maps are not traversable; address the struct directly or drop the tag)
FeatureGates map[string]string `json:"featureGates,omitempty" configfile:"-"`
Defensive patterns

Strategy: type-guard

Validate before calling

func isStructLike(x interface{}) bool {
	v := reflect.ValueOf(x)
	for v.Kind() == reflect.Ptr { v = v.Elem() }
	return v.Kind() == reflect.Struct
}

Type guard

func ensureStruct(v reflect.Value) (reflect.Value, error) {
	for v.Kind() == reflect.Ptr {
		if v.IsNil() { return v, fmt.Errorf("nil pointer in path") }
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		return v, fmt.Errorf("only accepts structs; got %s", v.Kind())
	}
	return v, nil
}

Try / catch

v, err := getValueFromStruct(key, obj)
if err != nil {
	// "only accepts structs; got %T" — check the segment of the dotted path that went off-struct
	return fmt.Errorf("field path %q invalid for target: %w", key, err)
}

Prevention

When it happens

Trigger: The dotted path in a configfile tag traverses into a map, slice, interface, or basic type before a remaining key is looked up (e.g. tag "foo.bar" where foo resolves to a map or a string); or the target passed to getValueFromStruct is not a struct/pointer-to-struct at all.

Common situations: A configfile tag path written for a nested struct now points at a field that upstream changed to a map or scalar; test cases (TestWrongStructField) exercising wrong field paths; passing a non-struct target struct into the config builder.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/351b143437ddef8a. Report an issue: GitHub.

Appendix: source

Thrown at pkg/configbuilder/buildconfigfile.go:103

	if err != nil {
		return nil, err
	}

	return configFile, nil
}

func getValueFromStruct(keyWithDots string, object interface{}) (*reflect.Value, error) {
	keySlice := strings.Split(keyWithDots, ".")
	v := reflect.ValueOf(object)
	// iterate through field names, ignoring the first name as it might be the current instance name
	// you can make it recursive also if want to support types like slice,map etc along with struct
	for _, key := range keySlice {
		for v.Kind() == reflect.Ptr {
			v = v.Elem()
		}
		// we only accept structs
		if v.Kind() != reflect.Struct {
			return nil, fmt.Errorf("only accepts structs; got %T", v)
		}
		v = v.FieldByName(key)
	}

	return &v, nil
}

View on GitHub (pinned to 4c8573c808)