431 lines
9.8 KiB
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
|
|
}
|