{"record":{"id":"e743869bae3a03ff","repo":"derailed/k9s","slug":"expecting-serviceaccount-resource","errorCode":null,"errorMessage":"expecting ServiceAccount resource","messagePattern":"expecting ServiceAccount resource","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/dao/dp.go","lineNumber":304,"sourceCode":"\t\t}\n\t}\n\n\tfor _, s := range spec.ImagePullSecrets {\n\t\tif s.Name == name {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\tif saName := spec.ServiceAccountName; saName != \"\" {\n\t\to, err := f.Get(client.SaGVR, client.FQN(ns, saName), wait, labels.Everything())\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tvar sa v1.ServiceAccount\n\t\terr = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sa)\n\t\tif err != nil {\n\t\t\treturn false, errors.New(\"expecting ServiceAccount resource\")\n\t\t}\n\n\t\tfor _, ref := range sa.Secrets {\n\t\t\tif ref.Namespace == ns && ref.Name == name {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i := range spec.Volumes {\n\t\tif sec := spec.Volumes[i].Secret; sec != nil {\n\t\t\tif sec.SecretName == name {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false, nil","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/dao/dp.go#L286-L322","documentation":"While resolving a workload's secret references, hasSecret follows spec.template.spec.serviceAccountName, fetches that ServiceAccount from the factory, and converts it into v1.ServiceAccount (internal/dao/dp.go:304). If the conversion fails, the sentinel 'expecting ServiceAccount resource' is returned with the real error dropped. Callers inside Deployment/DaemonSet/StatefulSet Scan only log this as a warning and skip the workload, so the scan survives, but direct callers receive the error.","triggerScenarios":"A secret reference scan (SecGVR branch of Scan) reaching a workload whose serviceAccountName resolves to a ServiceAccount object that does not conform to the compiled v1.ServiceAccount schema — mistyped metadata/secrets fields, an aggregated source shadowing serviceaccounts.core, or k9s/cluster version skew. Also triggered by embedding code calling hasSecret directly.","commonSituations":"Clusters with custom apiservers or webhooks that mutate ServiceAccounts; version skew between k9s and the apiserver; scans that warn 'Unable to locate secret' in k9s logs while silently skipping workloads whose SA fails conversion.","solutions":["Inspect the referenced ServiceAccount: kubectl get serviceaccount <name> -n <ns> -o yaml and fix unexpected or mistyped fields.","Upgrade k9s to a build matching the cluster's Kubernetes minor version.","Confirm serviceaccounts.core is served by the core apiserver and not shadowed.","When patching, wrap the converter error with %w so the offending field is reported.","Optionally skip the workload with a warning instead of propagating the error out of hasSecret."],"exampleFix":"// before\nvar sa v1.ServiceAccount\nerr = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sa)\nif err != nil {\n    return false, errors.New(\"expecting ServiceAccount resource\")\n}\n// after\nu, ok := o.(*unstructured.Unstructured)\nif !ok {\n    return false, fmt.Errorf(\"expected unstructured serviceaccount, got %T\", o)\n}\nvar sa v1.ServiceAccount\nif err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &sa); err != nil {\n    return false, fmt.Errorf(\"serviceaccount %q does not match v1 schema: %w\", client.FQN(ns, saName), err)\n}","handlingStrategy":"try-catch","validationCode":"o, err := factory.Get(client.SaGVR, client.FQN(ns, saName), wait, labels.Everything())\nif err != nil { return false, err }\nu, ok := o.(*unstructured.Unstructured)\nif !ok || u.GroupVersionKind().Kind != \"ServiceAccount\" {\n    return false, fmt.Errorf(\"not a serviceaccount: %s\", u.GroupVersionKind())\n}","typeGuard":"func isServiceAccount(o runtime.Object) bool {\n    u, ok := o.(*unstructured.Unstructured)\n    return ok && u.GroupVersionKind().Kind == \"ServiceAccount\" && u.GroupVersionKind().Group == \"\"\n}","tryCatchPattern":"var sa v1.ServiceAccount\nif err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &sa); err != nil {\n    return false, fmt.Errorf(\"serviceaccount %q conversion: %w\", saName, err)\n}","preventionTips":["Keep ServiceAccounts untouched by custom webhooks, or validate their output against the v1 schema.","Wrap conversion errors with %w so skipped workloads are traceable in k9s warnings.","Check k9s logs for 'Unable to locate secret' warnings — they often hide this failure.","Align k9s and cluster versions."],"tags":["kubernetes","go","type-conversion","serviceaccount","references"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}