{"record":{"id":"6281464cd44e807c","repo":"kubernetes/kops","slug":"error-converting-nodeup-config-to-yaml-w","errorCode":null,"errorMessage":"error converting nodeup config to yaml: %w","messagePattern":"error converting nodeup config to yaml: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/commands/toolbox_enroll.go","lineNumber":974,"sourceCode":"\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"rewriting channels manifest: %w\", err)\n\t\t\t}\n\t\t\tbootstrapData.NodeupScriptAdditionalFiles[nodeupConfig.ChannelsManifest] = rewritten\n\t\t}\n\n\t\tif nodeupConfig.ConfigStore != nil {\n\t\t\tif err := remapTree(&nodeupConfig.ConfigStore.Keypairs, path.Join(targetDir, \"pki/etcd\")); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := remapTree(&nodeupConfig.ConfigStore.Secrets, path.Join(targetDir, \"pki\")); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tnodeupConfigBytes, err := yaml.Marshal(nodeupConfig)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error converting nodeup config to yaml: %w\", err)\n\t\t}\n\t\t// Not much reason to hash this, since we're reading it from the local file system\n\t\t// sum256 := sha256.Sum256(nodeupConfigBytes)\n\t\t// bootConfig.NodeupConfigHash = base64.StdEncoding.EncodeToString(sum256[:])\n\n\t\tp := path.Join(targetDir, \"igconfig\", bootConfig.InstanceGroupRole.ToLowerString(), ig.Name, \"nodeupconfig.yaml\")\n\t\tbootstrapData.NodeupScriptAdditionalFiles[p] = nodeupConfigBytes\n\n\t\t// Copy any static manifests we need on the control plane\n\t\tfor _, staticManifest := range assetBuilder.StaticManifests() {\n\t\t\tif !staticManifest.AppliesToRole(bootConfig.InstanceGroupRole) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tp := path.Join(targetDir, staticManifest.Path)\n\t\t\tbootstrapData.NodeupScriptAdditionalFiles[p] = staticManifest.Contents\n\t\t}\n\t}\n","sourceCodeStart":956,"sourceCodeEnd":992,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/commands/toolbox_enroll.go#L956-L992","documentation":"GetBootstrapData serializes the fully remapped nodeup config (the per-instance-group NodeUpConfig) to YAML so it can be staged on the node's local filesystem. yaml.Marshal of nodeupConfig virtually never fails for well-formed structs; if it does, this wrapper reports the failure and aborts bootstrap data generation.","triggerScenarios":"GetBootstrapData (via getNodeConfig / RunToolboxEnroll / buildBootstrapData) reaching the yaml.Marshal(nodeupConfig) call with a value the yaml encoder cannot represent — e.g. an unsupported type (chan/func/cyclic reference) introduced by custom code or an incompatibility between the vendored k8s.io/apimachinery types and the sigs.k8s.io/yaml encoder after a dependency upgrade.","commonSituations":"Custom kops forks adding fields with non-serializable types to NodeUpConfig; dependency bumps (gopkg.in/yaml.v2/v3 vs sigs.k8s.io/yaml) changing marshal semantics; maps with non-string keys added to the config struct.","solutions":["Read the wrapped inner error from the message — it names the offending Go type or cycle; fix that field on NodeUpConfig (or your fork's added fields) to be YAML-serializable.","If introduced by a dependency upgrade, pin/align k8s.io/apimachinery and sigs.k8s.io/yaml versions (go mod tidy / make gomod) and rebuild.","Ensure any custom fields added to NodeUpConfig have yaml tags and supported types (string keys in maps, no funcs/channels/cycles).","Retry on stock (unforked) kops at the cluster's version to confirm the failure is fork/dependency related."],"exampleFix":"// before: unserializable custom field on NodeUpConfig\ntype NodeUpConfig struct {\n    ExtraHooks map[net.IP]string `json:\"extraHooks,omitempty\"`\n}\n// after: use a string-keyed map\ntype NodeUpConfig struct {\n    ExtraHooks map[string]string `json:\"extraHooks,omitempty\"`\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: ensure the NodeUpConfig can round-trip through YAML before enroll\nif _, err := yaml.Marshal(nodeupConfig); err != nil {\n    return fmt.Errorf(\"nodeup config not serializable: %w\", err)\n}","typeGuard":"func yamSerializable(v interface{}) bool {\n    _, err := yaml.Marshal(v)\n    return err == nil\n}","tryCatchPattern":"bootstrapData, err := GetBootstrapData(ctx, ...)\nif err != nil {\n    if strings.Contains(err.Error(), \"converting nodeup config to yaml\") {\n        // log the wrapped cause naming the offending type, and stop — this is a code/dependency bug, not transient\n        klog.Fatalf(\"non-transient serialization failure: %v\", err)\n    }\n    return err\n}","preventionTips":["Keep NodeUpConfig fields YAML-friendly: string map keys, no funcs/channels/cycles.","Run unit tests that marshal NodeUpConfig whenever adding fields or bumping apimachinery/sigs.k8s.io/yaml.","Avoid ad-hoc dependency upgrades in forks; use `make gomod` and the project's pinned versions."],"tags":["go","yaml","serialization","nodeup-config"],"backgroundTag":"yaml-marshal-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}