169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
package genericresource
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
|
"k8s.io/cli-runtime/pkg/resource"
|
|
)
|
|
|
|
const defaultHTTPGetAttempts int = 3
|
|
|
|
var defaultNewFunc = func() interface{} {
|
|
return map[string]interface{}{}
|
|
}
|
|
|
|
var errMissingResource = fmt.Errorf(`you must provide one or more resources`)
|
|
|
|
// Builder provides convenience functions for taking arguments and parameters
|
|
// from the command line and converting them to a list of resources to iterate
|
|
// over using the Visitor interface.
|
|
type Builder struct {
|
|
errs []error
|
|
paths []Visitor
|
|
stdinInUse bool
|
|
mapper *mapper
|
|
schema resource.ContentValidator
|
|
}
|
|
|
|
// NewBuilder returns a Builder.
|
|
func NewBuilder() *Builder {
|
|
return &Builder{
|
|
mapper: &mapper{
|
|
newFunc: defaultNewFunc,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Schema set the schema to validate data in files.
|
|
func (b *Builder) Schema(schema resource.ContentValidator) *Builder {
|
|
b.schema = schema
|
|
return b
|
|
}
|
|
|
|
// Constructor tells wanted type of object.
|
|
func (b *Builder) Constructor(newFunc func() interface{}) *Builder {
|
|
b.mapper.newFunc = newFunc
|
|
return b
|
|
}
|
|
|
|
// Filename groups input in two categories: URLs and files (files, directories, STDIN)
|
|
func (b *Builder) Filename(recursive bool, filenames ...string) *Builder {
|
|
for _, s := range filenames {
|
|
switch {
|
|
case s == "-":
|
|
b.Stdin()
|
|
case strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://"):
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
b.errs = append(b.errs, fmt.Errorf("the URL passed to filename %q is not valid: %v", s, err))
|
|
continue
|
|
}
|
|
b.URL(defaultHTTPGetAttempts, u)
|
|
default:
|
|
matches, err := expandIfFilePattern(s)
|
|
if err != nil {
|
|
b.errs = append(b.errs, err)
|
|
continue
|
|
}
|
|
b.Path(recursive, matches...)
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Stdin will read objects from the standard input.
|
|
func (b *Builder) Stdin() *Builder {
|
|
if b.stdinInUse {
|
|
b.errs = append(b.errs, resource.StdinMultiUseError)
|
|
}
|
|
b.stdinInUse = true
|
|
b.paths = append(b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
|
|
return b
|
|
}
|
|
|
|
// URL accepts a number of URLs directly.
|
|
func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
|
|
for _, u := range urls {
|
|
b.paths = append(b.paths, NewURLVisitor(b.mapper, httpAttemptCount, u, b.schema))
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Path accepts a set of paths that may be files, directories (all can contain
|
|
// one or more resources). Creates a FileVisitor for each file and then each
|
|
// FileVisitor is streaming the content to a StreamVisitor.
|
|
func (b *Builder) Path(recursive bool, paths ...string) *Builder {
|
|
for _, p := range paths {
|
|
_, err := os.Stat(p)
|
|
if os.IsNotExist(err) {
|
|
b.errs = append(b.errs, fmt.Errorf("the path %q does not exist", p))
|
|
continue
|
|
}
|
|
if err != nil {
|
|
b.errs = append(b.errs, fmt.Errorf("the path %q cannot be accessed: %v", p, err))
|
|
continue
|
|
}
|
|
|
|
visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, resource.FileExtensions, b.schema)
|
|
if err != nil {
|
|
b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err))
|
|
}
|
|
|
|
b.paths = append(b.paths, visitors...)
|
|
}
|
|
if len(b.paths) == 0 && len(b.errs) == 0 {
|
|
b.errs = append(b.errs, fmt.Errorf("error reading %v: recognized file extensions are %v", paths, resource.FileExtensions))
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Do returns a Result object with a Visitor for the resources identified by the Builder. Note that stream
|
|
// inputs are consumed by the first execution - use Infos() or Objects() on the Result to capture a list
|
|
// for further iteration.
|
|
func (b *Builder) Do() *Result {
|
|
r := b.visitorResult()
|
|
return r
|
|
}
|
|
|
|
func (b *Builder) visitorResult() *Result {
|
|
if len(b.errs) > 0 {
|
|
return &Result{err: utilerrors.NewAggregate(b.errs)}
|
|
}
|
|
|
|
// visit items specified by paths
|
|
if len(b.paths) != 0 {
|
|
return b.visitByPaths()
|
|
}
|
|
return &Result{err: errMissingResource}
|
|
}
|
|
|
|
func (b *Builder) visitByPaths() *Result {
|
|
result := &Result{}
|
|
|
|
result.visitor = VisitorList(b.paths)
|
|
return result
|
|
}
|
|
|
|
// expandIfFilePattern returns all the filenames that match the input pattern
|
|
// or the filename if it is a specific filename and not a pattern.
|
|
// If the input is a pattern and it yields no result it will result in an error.
|
|
func expandIfFilePattern(pattern string) ([]string, error) {
|
|
if _, err := os.Stat(pattern); os.IsNotExist(err) {
|
|
matches, err := filepath.Glob(pattern)
|
|
if err == nil && len(matches) == 0 {
|
|
return nil, fmt.Errorf("the path %q does not exist", pattern)
|
|
}
|
|
if errors.Is(err, filepath.ErrBadPattern) {
|
|
return nil, fmt.Errorf("pattern %q is not valid: %v", pattern, err)
|
|
}
|
|
return matches, err
|
|
}
|
|
return []string{pattern}, nil
|
|
}
|