minikube/pkg/minikube/assets/vm_assets.go

431 lines
9.8 KiB
Go

/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package assets
import (
"bytes"
"embed"
"fmt"
"html/template"
"io"
"os"
"path"
"path/filepath"
"strconv"
"time"
"github.com/pkg/errors"
"k8s.io/klog/v2"
)
// MemorySource is the source name used for in-memory copies
const MemorySource = "memory"
// ReadableFile is something that can be read
type ReadableFile interface {
io.Reader
GetLength() int
GetSourcePath() string
GetPermissions() string
GetModTime() (time.Time, error)
Seek(int64, int) (int64, error)
Close() error
}
// CopyableFile is something that can be copied
type CopyableFile interface {
ReadableFile
io.Writer
SetLength(int)
GetTargetPath() string
GetTargetDir() string
GetTargetName() string
}
type writeFn func(d []byte) (n int, err error)
// BaseCopyableFile is something that can be copied and written
type BaseCopyableFile struct {
ReadableFile
writer writeFn
length int
targetDir string
targetName string
}
// Write is for write something into the file
func (r *BaseCopyableFile) Write(d []byte) (n int, err error) {
return r.writer(d)
}
// SetLength is for setting the length
func (r *BaseCopyableFile) SetLength(length int) {
r.length = length
}
// GetTargetPath returns target path
func (r *BaseCopyableFile) GetTargetPath() string {
return filepath.Join(r.GetTargetDir(), r.GetTargetName())
}
// GetTargetDir returns target dir
func (r *BaseCopyableFile) GetTargetDir() string {
return r.targetDir
}
// GetTargetName returns target name
func (r *BaseCopyableFile) GetTargetName() string {
return r.targetName
}
// NewBaseCopyableFile creates a new instance of BaseCopyableFile
func NewBaseCopyableFile(source ReadableFile, writer writeFn, targetDir, targetName string) *BaseCopyableFile {
return &BaseCopyableFile{
ReadableFile: source,
writer: writer,
targetDir: targetDir,
targetName: targetName,
}
}
// BaseAsset is the base asset class
type BaseAsset struct {
SourcePath string
TargetDir string
TargetName string
Permissions string
Source string
}
// GetSourcePath returns asset name
func (b *BaseAsset) GetSourcePath() string {
return b.SourcePath
}
// GetTargetPath returns target path
func (b *BaseAsset) GetTargetPath() string {
return path.Join(b.GetTargetDir(), b.GetTargetName())
}
// GetTargetDir returns target dir
func (b *BaseAsset) GetTargetDir() string {
return b.TargetDir
}
// GetTargetName returns target name
func (b *BaseAsset) GetTargetName() string {
return b.TargetName
}
// GetPermissions returns permissions
func (b *BaseAsset) GetPermissions() string {
return b.Permissions
}
// GetModTime returns mod time
func (b *BaseAsset) GetModTime() (time.Time, error) {
return time.Time{}, nil
}
// FileAsset is an asset using a file
type FileAsset struct {
BaseAsset
reader io.ReadSeeker
writer io.Writer
file *os.File // Optional pointer to close file through FileAsset.Close()
}
// NewMemoryAssetTarget creates a new MemoryAsset, with target
func NewMemoryAssetTarget(d []byte, targetPath, permissions string) *MemoryAsset {
return NewMemoryAsset(d, path.Dir(targetPath), path.Base(targetPath), permissions)
}
// NewFileAsset creates a new FileAsset
func NewFileAsset(src, targetDir, targetName, permissions string) (*FileAsset, error) {
klog.V(4).Infof("NewFileAsset: %s -> %s", src, path.Join(targetDir, targetName))
info, err := os.Stat(src)
if err != nil {
return nil, errors.Wrapf(err, "stat")
}
if info.Size() == 0 {
klog.Warningf("NewFileAsset: %s is an empty file!", src)
}
f, err := os.Open(src)
if err != nil {
return nil, errors.Wrap(err, "open")
}
return &FileAsset{
BaseAsset: BaseAsset{
SourcePath: src,
TargetDir: targetDir,
TargetName: targetName,
Permissions: permissions,
},
reader: io.NewSectionReader(f, 0, info.Size()),
file: f,
}, nil
}
// GetLength returns the file length, or 0 (on error)
func (f *FileAsset) GetLength() (flen int) {
fi, err := os.Stat(f.SourcePath)
if err != nil {
klog.Errorf("stat(%q) failed: %v", f.SourcePath, err)
return 0
}
return int(fi.Size())
}
// SetLength sets the file length
func (f *FileAsset) SetLength(flen int) {
err := os.Truncate(f.SourcePath, int64(flen))
if err != nil {
klog.Errorf("truncate(%q) failed: %v", f.SourcePath, err)
}
}
// GetModTime returns modification time of the file
func (f *FileAsset) GetModTime() (time.Time, error) {
fi, err := os.Stat(f.SourcePath)
if err != nil {
klog.Errorf("stat(%q) failed: %v", f.SourcePath, err)
return time.Time{}, err
}
return fi.ModTime(), nil
}
// Read reads the asset
func (f *FileAsset) Read(p []byte) (int, error) {
if f.reader == nil {
return 0, errors.New("Error attempting FileAsset.Read, FileAsset.reader uninitialized")
}
return f.reader.Read(p)
}
// Write writes the asset
func (f *FileAsset) Write(p []byte) (int, error) {
if f.writer == nil {
f.file.Close()
perms, err := strconv.ParseUint(f.Permissions, 8, 32)
if err != nil || perms > 07777 {
return 0, err
}
f.file, err = os.OpenFile(f.SourcePath, os.O_RDWR|os.O_CREATE, os.FileMode(perms))
if err != nil {
return 0, err
}
f.writer = io.Writer(f.file)
}
return f.writer.Write(p)
}
// Seek resets the reader to offset
func (f *FileAsset) Seek(offset int64, whence int) (int64, error) {
return f.reader.Seek(offset, whence)
}
// Close closes the opend file.
func (f *FileAsset) Close() error {
if f.file == nil {
return nil
}
return f.file.Close()
}
// MemoryAsset is a memory-based asset
type MemoryAsset struct {
BaseAsset
reader io.ReadSeeker
length int
}
// GetLength returns length
func (m *MemoryAsset) GetLength() int {
return m.length
}
// SetLength returns length
func (m *MemoryAsset) SetLength(len int) {
m.length = len
}
// Read reads the asset
func (m *MemoryAsset) Read(p []byte) (int, error) {
return m.reader.Read(p)
}
// Writer writes the asset
func (m *MemoryAsset) Write(p []byte) (int, error) {
m.length = len(p)
m.reader = bytes.NewReader(p)
return len(p), nil
}
// Seek resets the reader to offset
func (m *MemoryAsset) Seek(offset int64, whence int) (int64, error) {
return m.reader.Seek(offset, whence)
}
// Close implemented for CopyableFile interface. Always return nil.
func (m *MemoryAsset) Close() error {
return nil
}
// NewMemoryAsset creates a new MemoryAsset
func NewMemoryAsset(d []byte, targetDir, targetName, permissions string) *MemoryAsset {
return &MemoryAsset{
BaseAsset: BaseAsset{
TargetDir: targetDir,
TargetName: targetName,
Permissions: permissions,
SourcePath: MemorySource,
},
reader: bytes.NewReader(d),
length: len(d),
}
}
// BinAsset is a bindata (binary data) asset
type BinAsset struct {
embed.FS
BaseAsset
reader io.ReadSeeker
template *template.Template
length int
}
// MustBinAsset creates a new BinAsset, or panics if invalid
func MustBinAsset(fs embed.FS, name, targetDir, targetName, permissions string) *BinAsset {
asset, err := NewBinAsset(fs, name, targetDir, targetName, permissions)
if err != nil {
panic(fmt.Sprintf("Failed to define asset %s: %v", name, err))
}
return asset
}
// NewBinAsset creates a new BinAsset
func NewBinAsset(fs embed.FS, name, targetDir, targetName, permissions string) (*BinAsset, error) {
m := &BinAsset{
FS: fs,
BaseAsset: BaseAsset{
SourcePath: name,
TargetDir: targetDir,
TargetName: targetName,
Permissions: permissions,
},
template: nil,
}
err := m.loadData()
return m, err
}
func defaultValue(defValue string, val interface{}) string {
if val == nil {
return defValue
}
strVal, ok := val.(string)
if !ok || strVal == "" {
return defValue
}
return strVal
}
func (m *BinAsset) loadData() error {
contents, err := m.FS.ReadFile(m.SourcePath)
if err != nil {
return err
}
tpl, err := template.New(m.SourcePath).Funcs(template.FuncMap{"default": defaultValue}).Parse(string(contents))
if err != nil {
return err
}
m.template = tpl
m.length = len(contents)
m.reader = bytes.NewReader(contents)
klog.V(1).Infof("Created asset %s with %d bytes", m.SourcePath, m.length)
if m.length == 0 {
return fmt.Errorf("%s is an empty asset", m.SourcePath)
}
return nil
}
// IsTemplate returns if the asset is a template
func (m *BinAsset) IsTemplate() bool {
return m.template != nil
}
// Evaluate evaluates the template to a new asset
func (m *BinAsset) Evaluate(data interface{}) (*MemoryAsset, error) {
if !m.IsTemplate() {
return nil, errors.Errorf("the asset %s is not a template", m.SourcePath)
}
var buf bytes.Buffer
if err := m.template.Execute(&buf, data); err != nil {
return nil, err
}
return NewMemoryAsset(buf.Bytes(), m.GetTargetDir(), m.GetTargetName(), m.GetPermissions()), nil
}
// GetLength returns length
func (m *BinAsset) GetLength() int {
return m.length
}
// SetLength sets length
func (m *BinAsset) SetLength(len int) {
m.length = len
}
// Read reads the asset
func (m *BinAsset) Read(p []byte) (int, error) {
if m.GetLength() == 0 {
return 0, fmt.Errorf("attempted read from a 0 length asset")
}
return m.reader.Read(p)
}
// Write writes the asset
func (m *BinAsset) Write(p []byte) (int, error) {
m.length = len(p)
m.reader = bytes.NewReader(p)
return len(p), nil
}
// Seek resets the reader to offset
func (m *BinAsset) Seek(offset int64, whence int) (int64, error) {
return m.reader.Seek(offset, whence)
}
// Close implemented for CopyableFile interface. Always return nil.
func (m *BinAsset) Close() error {
return nil
}