kubernetes/kops · error

ReadOnlyError

ReadOnlyError

Error message

AssetPath is read-only

What it means

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.

Source

Thrown at upup/models/vfs.go:31

See the License for the specific language governing permissions and
limitations under the License.
*/

package models

import (
	"context"
	"embed"
	"errors"
	"io"
	"io/fs"
	"os"
	"path"

	"k8s.io/kops/util/pkg/vfs"
)

var ReadOnlyError = errors.New("AssetPath is read-only")

//go:embed cloudup
var content embed.FS

type AssetPath struct {
	location string
}

var _ vfs.Path = &AssetPath{}

func NewAssetPath(location string) *AssetPath {
	a := &AssetPath{
		location: location,
	}
	return a
}

func (p *AssetPath) Join(relativePath ...string) vfs.Path {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. 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
  2. Check writability before writing: only call WriteFile on paths that are not AssetPath (e.g. via a type switch on *AssetPath)
  3. If you need to modify the templates, edit the files under upup/models/cloudup in the source tree and rebuild
  4. Use RemoveAll/Remove only on writable backing stores; skip cleanup for embedded assets

Example fix

// before
if err := path.WriteFile(ctx, bytes.NewReader(data), vfs.ACLPrivate); err != nil { return err } // AssetPath is read-only
// after
switch p := path.(type) {
case *models.AssetPath:
	// embedded assets are immutable; write elsewhere
	return os.WriteFile("/tmp/cloudup.yaml", data, 0644)
default:
	return path.WriteFile(ctx, bytes.NewReader(data), vfs.ACLPrivate)
}
Defensive patterns

Strategy: type-guard

Validate before calling

func writable(p vfs.Path) bool { _, readOnly := p.(*models.AssetPath); return !readOnly }
if !writable(path) { /* write to a mutable path instead */ }

Type guard

func isAssetPath(p vfs.Path) (*models.AssetPath, bool) {
	ap, ok := p.(*models.AssetPath)
	return ap, ok
}

Try / catch

if err := p.WriteFile(ctx, r, acl); err != nil {
	if errors.Is(err, models.ReadOnlyError) {
		// fall back to writing outside the embedded FS
	} else { return err }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/dff33dbbec881f07. Report an issue: GitHub.