{"record":{"id":"67c45c60e4fd9e5b","repo":"kubernetes/kops","slug":"error-reading-addon-q-v","errorCode":null,"errorMessage":"error reading addon %q: %v","messagePattern":"error reading addon %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/clusteraddons/load.go","lineNumber":46,"sourceCode":"type ClusterAddon struct {\n\tRaw     string\n\tObjects kubemanifest.ObjectList\n}\n\n// LoadClusterAddon loads a set of objects from the specified VFS location\nfunc LoadClusterAddon(vfsContext *vfs.VFSContext, location string) (*ClusterAddon, error) {\n\tu, err := url.Parse(location)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid addon location: %q\", location)\n\t}\n\n\t// TODO: Should we support relative paths for \"standard\" addons?  See equivalent code in LoadChannel\n\n\tresolved := u.String()\n\tklog.V(2).Infof(\"Loading addon from %q\", resolved)\n\taddonBytes, err := vfsContext.ReadFile(resolved)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading addon %q: %v\", resolved, err)\n\t}\n\taddon, err := ParseClusterAddon(addonBytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing addon %q: %v\", resolved, err)\n\t}\n\tklog.V(4).Infof(\"Addon contents: %s\", string(addonBytes))\n\n\treturn addon, nil\n}\n\n// ParseClusterAddon parses a ClusterAddon object\nfunc ParseClusterAddon(raw []byte) (*ClusterAddon, error) {\n\tobjects, err := kubemanifest.LoadObjectsFrom(raw)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing addon %v\", err)\n\t}\n\n\treturn &ClusterAddon{Raw: string(raw), Objects: objects}, nil","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/clusteraddons/load.go#L28-L64","documentation":"After resolving the addon location to a URL, LoadClusterAddon reads the addon manifest bytes through the VFS context. Any ReadFile failure (not-found, permission denied, TLS/network errors, bad credentials) is wrapped as this error with the resolved URL and underlying cause.","triggerScenarios":"LoadClusterAddon called (from RunCreateCluster) with a location that resolves to a nonexistent file, a private bucket without credentials, a DNS/network failure, or an HTTP 404 on the addon URL.","commonSituations":"Kubernetes version-mismatched addon URLs that no longer exist upstream; offline/restricted networks during cluster creation; misconfigured S3/VFS credentials; typos in addon file names.","solutions":["Verify the addon URL/exists: open the resolved location in a browser or curl it; fix the path if 404.","Check VFS credentials and network access for the backend hosting the addon.","Pin the addon location to a version-matched URL for your kops/Kubernetes version."],"exampleFix":"// before\naddon, err := LoadClusterAddon(vfs.Context, \"https://addons.k8s.io/v1.28/networking.yaml\") // 404\n// after\naddon, err := LoadClusterAddon(vfs.Context, \"https://addons.k8s.io/stable/networking.yaml\")","handlingStrategy":"try-catch","validationCode":"// pre-flight reachability check\nif err := probeURL(resolvedLocation); err != nil {\n  return fmt.Errorf(\"addon location unreachable before create: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"addon, err := LoadClusterAddon(vfs.Context, loc)\nif err != nil {\n  if strings.Contains(err.Error(), \"error reading addon\") {\n    return retryWithBackoff(func() (*ClusterAddon, error) { return LoadClusterAddon(vfs.Context, loc) })\n  }\n  return err\n}","preventionTips":["Curl/GET the addon URL before cluster creation.","Verify VFS/cloud credentials for private addon stores.","Pin addon URLs to version-matched stable locations."],"tags":["clusteraddons","vfs","network","read"],"backgroundTag":"resource-fetch-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}