// Copyright 2026 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 // Package embeddedasset provides helpers that preserve the legacy generated // asset API exposed by the OpenAPI packages. package embeddedasset import ( "bytes" "compress/gzip" "fmt" "io" "os" "path" "path/filepath" "strings" "time" ) // File describes one embedded asset. type File struct { // Path is the full slash-separated virtual path used to look up the asset. Path string Data []byte Compressed bool Size int64 Mode os.FileMode ModTime time.Time } // Asset returns a fresh copy of the uncompressed asset data. func Asset(file File, name string) ([]byte, error) { if canonical(name) != file.Path { return nil, fmt.Errorf("Asset %s not found", name) } if !file.Compressed { return bytes.Clone(file.Data), nil } reader, err := gzip.NewReader(bytes.NewReader(file.Data)) if err != nil { return nil, fmt.Errorf("read %q: %w", name, err) } b, readErr := io.ReadAll(io.LimitReader(reader, file.Size+1)) closeErr := reader.Close() if readErr != nil { return nil, fmt.Errorf("read %q: %w", name, readErr) } if closeErr != nil { return nil, fmt.Errorf("read %q: %w", name, closeErr) } if int64(len(b)) != file.Size { return nil, fmt.Errorf("read %q: expected %d bytes, got %d", name, file.Size, len(b)) } return b, nil } // MustAsset is like Asset but panics on error. func MustAsset(file File, name string) []byte { b, err := Asset(file, name) if err != nil { panic("asset: Asset(" + name + "): " + err.Error()) } return b } // AssetInfo returns metadata for an embedded asset. func AssetInfo(file File, name string) (os.FileInfo, error) { if canonical(name) != file.Path { return nil, fmt.Errorf("AssetInfo %s not found", name) } return fileInfo{file: file}, nil } // AssetDir returns the immediate children of a directory in the asset path. func AssetDir(file File, name string) ([]string, error) { name = canonical(name) parts := strings.Split(file.Path, "/") if name == "" { return []string{parts[0]}, nil } dirParts := strings.Split(name, "/") if len(dirParts) >= len(parts) { return nil, fmt.Errorf("Asset %s not found", name) } for i := range dirParts { if dirParts[i] != parts[i] { return nil, fmt.Errorf("Asset %s not found", name) } } return []string{parts[len(dirParts)]}, nil } // RestoreAsset restores an asset under dir. func RestoreAsset(file File, dir, name string) error { b, err := Asset(file, name) if err != nil { return err } info, err := AssetInfo(file, name) if err != nil { return err } target := filePath(dir, name) if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { return fmt.Errorf("create asset directory: %w", err) } if err := os.WriteFile(target, b, info.Mode()); err != nil { return fmt.Errorf("write asset %q: %w", name, err) } if err := os.Chtimes(target, info.ModTime(), info.ModTime()); err != nil { return fmt.Errorf("restore timestamps for asset %q: %w", name, err) } return nil } // RestoreAssets restores an asset or directory recursively under dir. func RestoreAssets(file File, dir, name string) error { children, err := AssetDir(file, name) if err != nil { return RestoreAsset(file, dir, name) } for _, child := range children { if err := RestoreAssets(file, dir, filepath.Join(name, child)); err != nil { return err } } return nil } type fileInfo struct { file File } func (info fileInfo) Name() string { return path.Base(info.file.Path) } func (info fileInfo) Size() int64 { return info.file.Size } func (info fileInfo) Mode() os.FileMode { return info.file.Mode } func (info fileInfo) ModTime() time.Time { return info.file.ModTime } func (info fileInfo) IsDir() bool { return false } func (info fileInfo) Sys() interface{} { return nil } func canonical(name string) string { return strings.ReplaceAll(name, "\\", "/") } func filePath(dir, name string) string { return filepath.Join(append([]string{dir}, strings.Split(canonical(name), "/")...)...) }