{"record":{"id":"dff33dbbec881f07","repo":"kubernetes/kops","slug":"readonlyerror","errorCode":"ReadOnlyError","errorMessage":"AssetPath is read-only","messagePattern":"AssetPath is read-only","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/models/vfs.go","lineNumber":31,"sourceCode":"See the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage models\n\nimport (\n\t\"context\"\n\t\"embed\"\n\t\"errors\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path\"\n\n\t\"k8s.io/kops/util/pkg/vfs\"\n)\n\nvar ReadOnlyError = errors.New(\"AssetPath is read-only\")\n\n//go:embed cloudup\nvar content embed.FS\n\ntype AssetPath struct {\n\tlocation string\n}\n\nvar _ vfs.Path = &AssetPath{}\n\nfunc NewAssetPath(location string) *AssetPath {\n\ta := &AssetPath{\n\t\tlocation: location,\n\t}\n\treturn a\n}\n\nfunc (p *AssetPath) Join(relativePath ...string) vfs.Path {","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/models/vfs.go#L13-L49","documentation":"AssetPath is a read-only vfs.Path backed by Go embed.FS content (embedded cloudup templates). Because embedded filesystems cannot be modified at runtime, all write operations (WriteFile, CreateFile, Remove, RemoveAll) unconditionally return the shared package-level ReadOnlyError sentinel.","triggerScenarios":"Calling WriteFile, CreateFile, Remove, or RemoveAll on an AssetPath obtained via vfs.Context, e.g. attempting to overwrite an embedded template or add a new file under the embedded cloudup tree.","commonSituations":"Code that treats all vfs.Path values uniformly and writes templated cluster assets back to the same path it read them from; tests or tooling that try to mutate embedded assets; refactoring code that previously used a real (filesystem/S3) VFS path to use embedded assets.","solutions":["Do not write to AssetPath; read the embedded content and write to a mutable vfs.Path (e.g. vfs.Context.BuildFile or os.WriteFile) instead","Check writability before writing: only call WriteFile on paths that are not AssetPath (e.g. via a type switch on *AssetPath)","If you need to modify the templates, edit the files under upup/models/cloudup in the source tree and rebuild","Use RemoveAll/Remove only on writable backing stores; skip cleanup for embedded assets"],"exampleFix":"// before\nif err := path.WriteFile(ctx, bytes.NewReader(data), vfs.ACLPrivate); err != nil { return err } // AssetPath is read-only\n// after\nswitch p := path.(type) {\ncase *models.AssetPath:\n\t// embedded assets are immutable; write elsewhere\n\treturn os.WriteFile(\"/tmp/cloudup.yaml\", data, 0644)\ndefault:\n\treturn path.WriteFile(ctx, bytes.NewReader(data), vfs.ACLPrivate)\n}","handlingStrategy":"type-guard","validationCode":"func writable(p vfs.Path) bool { _, readOnly := p.(*models.AssetPath); return !readOnly }\nif !writable(path) { /* write to a mutable path instead */ }","typeGuard":"func isAssetPath(p vfs.Path) (*models.AssetPath, bool) {\n\tap, ok := p.(*models.AssetPath)\n\treturn ap, ok\n}","tryCatchPattern":"if err := p.WriteFile(ctx, r, acl); err != nil {\n\tif errors.Is(err, models.ReadOnlyError) {\n\t\t// fall back to writing outside the embedded FS\n\t} else { return err }\n}","preventionTips":["Never assume every vfs.Path is writable; check the concrete type before write ops","Keep embedded assets immutable and copy them to writable storage when mutation is needed","Route all writes through a helper that rejects *AssetPath early"],"tags":["vfs","read-only","embedded-assets","go"],"backgroundTag":"read-only-filesystem","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"}