k3s-io/k3s · warning
invalid GVK format: %s
Error message
invalid GVK format: %s
What it means
getGVK parses strings produced by schema.GroupVersionKind.String(), i.e. exactly 'group/version, Kind=Kind' ('apps/v1, Kind=Deployment'). It splits on the literal ', Kind=' and requires exactly one separator; anything else is rejected. It is used to parse the GVK annotation k3s stores on auto-deployed manifest Addons. Note: both call sites (pkg/deploy/controller.go:256, 303) discard this error via `if gvk, err := getGVK(...); err == nil`, so in shipped k3s the error never propagates - it only fires for direct callers (e.g. tests).
Source
Thrown at pkg/deploy/controller.go:534
subPath := filepath.Join(namePath[0:i]...)
if disables[subPath] {
return true
}
}
if !util.HasSuffixI(fileName, ".yaml", ".yml", ".json") {
return false
}
// Check the basename against the disables map
baseFile := filepath.Base(fileName)
suffix := filepath.Ext(baseFile)
baseName := strings.TrimSuffix(baseFile, suffix)
return disables[baseName]
}
func getGVK(s string) (*schema.GroupVersionKind, error) {
parts := strings.Split(s, ", Kind=")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid GVK format: %s", s)
}
gvk := &schema.GroupVersionKind{}
gvk.Group, gvk.Version = kv.Split(parts[0], "/")
gvk.Kind = parts[1]
return gvk, nil
}
func getGVKString(gvks []schema.GroupVersionKind) string {
strs := make([]string, len(gvks))
for i, gvk := range gvks {
strs[i] = gvk.String()
}
return strings.Join(strs, gvkSep)
}
View on GitHub (pinned to 6ba341e396)
Solutions
- Keep GVK strings in the canonical GroupVersionKind.String() format: 'group/version, Kind=Kind'.
- Let k3s manage the GVK annotation itself - it is written by getGVKString after each successful apply; delete a corrupt annotation and let the controller rewrite it.
- If calling getGVK from code, validate/normalize the input first instead of relying on the error.
Example fix
// before addon.Annotations["deploy.k3s.cattle.io/gvks"] = "apps/v1 Deployment" // after addon.Annotations["deploy.k3s.cattle.io/gvks"] = "apps/v1, Kind=Deployment"
Defensive patterns
Strategy: type-guard
Type guard
// Narrow a string to a valid GroupVersionKind String() before parsing:
func isGVKString(s string) bool {
parts := strings.Split(s, ", Kind=")
if len(parts) != 2 { return false }
seg := strings.Split(parts[0], "/")
if len(seg) < 1 || len(seg) > 2 { return false } // 'v1' or 'group/version'
return parts[0] != "" && parts[1] != ""
}
if isGVKString(s) { gvk, _ := getGVK(s) } Try / catch
// Follow the controller's own pattern - skip invalid entries instead of failing:
if gvk, err := getGVK(gvkString); err == nil {
addonGVKs = append(addonGVKs, *gvk)
} else {
log.Warnf("ignoring malformed GVK %q: %v", gvkString, err)
} Prevention
- Only use schema.GroupVersionKind.String() output as annotation values.
- Treat k3s-managed Addon annotations as controller-owned; change manifests, not annotations.
- In tests, generate GVK strings from the struct rather than literals.
When it happens
Trigger: Invoking getGVK with a string lacking the ', Kind=' separator or containing it more than once, e.g. 'apps/v1', 'Deployment', or 'apps/v1, Kind=Deployment, Kind=Pod' (pkg/deploy/controller.go:531-538). In-cluster, only a hand-edited GVK annotation on an Addon object would carry such a value.
Common situations: Manually editing the auto-deployed Addon annotations (kubectl annotate addons ...); a script writing GVK strings in a different format; unit tests feeding unstructured GVK strings.
Related errors
- invalid endpoint URL %s for %s: %v
- fields for file %s (%d) smaller than required index (key: %d
- invalid ip format '%s'
- unable to parse CIDR for interface %s: %w
- VPN Error. The passed VPN auth info includes an unknown para
AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15).
Data as JSON: /api/errors/157906adc242868a.
Report an issue: GitHub.