mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
e0156fa1f8
Previously if we provided a "root" to the bundle directory loader that was a relative path, it would matter whether or not it was prefixed with "./". The logic to trim that path from the paths found walking the root was not taking into account the prefix so the resulting ones that had "./" would leave behind the root path. Later on the bundle loader would generate a "full" path to set on the module file for its location which is the root+path.. which resulted in duplicate "root"s on those id's. To fix this we just normalize the relative paths in the directory loader so that we can not worry about what type of relative path it is. Fixes: #2117 Signed-off-by: Patrick East <east.patrick@gmail.com>
181 lines
4.3 KiB
Go
181 lines
4.3 KiB
Go
package bundle
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Descriptor contains information about a file and
|
|
// can be used to read the file contents.
|
|
type Descriptor struct {
|
|
path string
|
|
reader io.Reader
|
|
closer io.Closer
|
|
closeOnce *sync.Once
|
|
}
|
|
|
|
func newDescriptor(path string, reader io.Reader) *Descriptor {
|
|
return &Descriptor{
|
|
path: path,
|
|
reader: reader,
|
|
}
|
|
}
|
|
|
|
func (d *Descriptor) withCloser(closer io.Closer) *Descriptor {
|
|
d.closer = closer
|
|
d.closeOnce = new(sync.Once)
|
|
return d
|
|
}
|
|
|
|
// Path returns the path of the file.
|
|
func (d *Descriptor) Path() string {
|
|
return d.path
|
|
}
|
|
|
|
// Read will read all the contents from the file the Descriptor refers to
|
|
// into the dest writer up n bytes. Will return an io.EOF error
|
|
// if EOF is encountered before n bytes are read.
|
|
func (d *Descriptor) Read(dest io.Writer, n int64) (int64, error) {
|
|
n, err := io.CopyN(dest, d.reader, n)
|
|
return n, err
|
|
}
|
|
|
|
// Close the file, on some Loader implementations this might be a no-op.
|
|
// It should *always* be called regardless of file.
|
|
func (d *Descriptor) Close() error {
|
|
var err error
|
|
if d.closer != nil {
|
|
d.closeOnce.Do(func() {
|
|
err = d.closer.Close()
|
|
})
|
|
}
|
|
return err
|
|
}
|
|
|
|
// DirectoryLoader defines an interface which can be used to load
|
|
// files from a directory by iterating over each one in the tree.
|
|
type DirectoryLoader interface {
|
|
// NextFile must return io.EOF if there is no next value. The returned
|
|
// descriptor should *always* be closed when no longer needed.
|
|
NextFile() (*Descriptor, error)
|
|
}
|
|
|
|
type dirLoader struct {
|
|
root string
|
|
files []string
|
|
idx int
|
|
}
|
|
|
|
// NewDirectoryLoader returns a basic DirectoryLoader implementation
|
|
// that will load files from a given root directory path.
|
|
func NewDirectoryLoader(root string) DirectoryLoader {
|
|
|
|
if len(root) > 1 {
|
|
// Normalize relative directories, ex "./src/bundle" -> "src/bundle"
|
|
// We don't need an absolute path, but this makes the joined/trimmed
|
|
// paths more uniform.
|
|
if root[0] == '.' && root[1] == filepath.Separator {
|
|
if len(root) == 2 {
|
|
root = root[:1] // "./" -> "."
|
|
} else {
|
|
root = root[2:] // remove leading "./"
|
|
}
|
|
}
|
|
}
|
|
|
|
d := dirLoader{
|
|
root: root,
|
|
}
|
|
return &d
|
|
}
|
|
|
|
// NextFile iterates to the next file in the directory tree
|
|
// and returns a file Descriptor for the file.
|
|
func (d *dirLoader) NextFile() (*Descriptor, error) {
|
|
// build a list of all files we will iterate over and read, but only one time
|
|
if d.files == nil {
|
|
d.files = []string{}
|
|
err := filepath.Walk(d.root, func(path string, info os.FileInfo, err error) error {
|
|
if info != nil && info.Mode().IsRegular() {
|
|
d.files = append(d.files, filepath.ToSlash(path))
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to list files")
|
|
}
|
|
}
|
|
|
|
// If done reading files then just return io.EOF
|
|
// errors for each NextFile() call
|
|
if d.idx >= len(d.files) {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
fileName := d.files[d.idx]
|
|
d.idx++
|
|
fh, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to open file %s", fileName)
|
|
}
|
|
|
|
// Trim off the root directory and return path as if chrooted
|
|
cleanedPath := strings.TrimPrefix(fileName, d.root)
|
|
if !strings.HasPrefix(cleanedPath, "/") {
|
|
cleanedPath = "/" + cleanedPath
|
|
}
|
|
|
|
f := newDescriptor(cleanedPath, fh).withCloser(fh)
|
|
return f, nil
|
|
}
|
|
|
|
type tarballLoader struct {
|
|
r io.Reader
|
|
tr *tar.Reader
|
|
}
|
|
|
|
// NewTarballLoader returns a new DirectoryLoader that reads
|
|
// files out of a gzipped tar archive.
|
|
func NewTarballLoader(r io.Reader) DirectoryLoader {
|
|
l := tarballLoader{
|
|
r: r,
|
|
}
|
|
return &l
|
|
}
|
|
|
|
// NextFile iterates to the next file in the directory tree
|
|
// and returns a file Descriptor for the file.
|
|
func (t *tarballLoader) NextFile() (*Descriptor, error) {
|
|
if t.tr == nil {
|
|
gr, err := gzip.NewReader(t.r)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "archive read failed")
|
|
}
|
|
|
|
t.tr = tar.NewReader(gr)
|
|
}
|
|
|
|
for {
|
|
header, err := t.tr.Next()
|
|
// Eventually we will get an io.EOF error when finished
|
|
// iterating through the archive
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Keep iterating on the archive until we find a normal file
|
|
if header.Typeflag == tar.TypeReg {
|
|
// no need to close this descriptor after reading
|
|
f := newDescriptor(header.Name, t.tr)
|
|
return f, nil
|
|
}
|
|
}
|
|
}
|