diff --git a/pkg/parser/fsreader.go b/pkg/parser/fsreader.go index ff0927a45..a9c7cd40d 100644 --- a/pkg/parser/fsreader.go +++ b/pkg/parser/fsreader.go @@ -24,6 +24,14 @@ import ( "github.com/spf13/afero" ) +var _ AnnotatedReadCloser = &FsReadCloser{} + +// FsReadCloserAnnotation annotates data for an FsReadCloser. +type FsReadCloserAnnotation struct { + path string + position int +} + // FsReadCloser implements io.ReadCloser for an Afero filesystem. type FsReadCloser struct { fs afero.Fs @@ -32,6 +40,7 @@ type FsReadCloser struct { index int position int writeBreak bool + wroteBreak bool } // A FilterFn filters files when the FsReadCloser walks the filesystem. @@ -56,6 +65,13 @@ func SkipDirs() FilterFn { } } +// SkipEmpty skips empty files. +func SkipEmpty() FilterFn { + return func(path string, info os.FileInfo) (bool, error) { + return info.Size() == 0, nil + } +} + // SkipNotYAML skips files that do not have YAML extension. func SkipNotYAML() FilterFn { return func(path string, info os.FileInfo) (bool, error) { @@ -94,24 +110,29 @@ func NewFsReadCloser(fs afero.Fs, dir string, fns ...FilterFn) (*FsReadCloser, e index: 0, position: 0, writeBreak: false, + wroteBreak: false, }, err } func (r *FsReadCloser) Read(p []byte) (n int, err error) { + if r.wroteBreak { + r.index++ + r.position = 0 + r.wroteBreak = false + } if r.index == len(r.paths) { return 0, io.EOF } if r.writeBreak { n = copy(p, "\n---\n") r.writeBreak = false + r.wroteBreak = true return n, nil } b, err := afero.ReadFile(r.fs, r.paths[r.index]) n = copy(p, b[r.position:]) r.position += n if err == io.EOF || n == 0 { - r.position = 0 - r.index++ r.writeBreak = true err = nil } @@ -122,3 +143,17 @@ func (r *FsReadCloser) Read(p []byte) (n int, err error) { func (r *FsReadCloser) Close() error { return nil } + +// Annotate returns additional about the data currently being read. +func (r *FsReadCloser) Annotate() interface{} { + // Index will be out of bounds if we error after the final file has been + // read. + index := r.index + if index == len(r.paths) { + index-- + } + return FsReadCloserAnnotation{ + path: r.paths[index], + position: r.position, + } +} diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index ea9b36097..13a3ea2ec 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -19,10 +19,12 @@ package parser import ( "bufio" "context" + "fmt" "io" "io/ioutil" "strings" + "github.com/pkg/errors" "github.com/spf13/afero" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -31,6 +33,13 @@ import ( "k8s.io/client-go/kubernetes" ) +// AnnotatedReadCloser is a wrapper around io.ReadCloser that allows +// implementations to supply additional information about data that is read. +type AnnotatedReadCloser interface { + io.ReadCloser + Annotate() interface{} +} + // ObjectCreaterTyper know how to create and determine the type of objects. type ObjectCreaterTyper interface { runtime.ObjectCreater @@ -105,6 +114,9 @@ func (p *PackageParser) Parse(ctx context.Context, reader io.ReadCloser) (*Packa if err != nil { o, _, err := do.Decode(bytes, nil, nil) if err != nil { + if anno, ok := reader.(AnnotatedReadCloser); ok { + return pkg, errors.Wrap(err, fmt.Sprintf("%+v", anno.Annotate())) + } return pkg, err } pkg.objects = append(pkg.objects, o) diff --git a/pkg/parser/parser_test.go b/pkg/parser/parser_test.go index 50bc363b2..dc97c0cbf 100644 --- a/pkg/parser/parser_test.go +++ b/pkg/parser/parser_test.go @@ -131,7 +131,7 @@ func TestParser(t *testing.T) { "FsBackendSkip": { reason: "should skip empty files and files without yaml extension", parser: New(metaScheme, objScheme), - backend: NewFsBackend(emptyFs, FsDir("."), FsFilters(SkipDirs(), SkipNotYAML())), + backend: NewFsBackend(emptyFs, FsDir("."), FsFilters(SkipDirs(), SkipEmpty(), SkipNotYAML())), pkg: NewPackage(), }, }