Refactor source API (#1846)

* refactor source API and syft json source block

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update source detection and format test utils

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* generate list of all source metadata types

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* extract base and root normalization into helper functions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* preserve syftjson model package name import ref

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* alias should not be a pointer

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2023-06-30 14:19:16 +00:00
committed by GitHub
parent 608dbded06
commit 4da3be864f
126 changed files with 7384 additions and 3190 deletions
@@ -0,0 +1,165 @@
package fileresolver
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/anchore/syft/syft/internal/windows"
)
// ChrootContext helps to modify path from a real filesystem to a chroot-like filesystem, taking into account
// the user given root, the base path (if any) to consider as the root, and the current working directory.
// Note: this only works on a real filesystem, not on a virtual filesystem (such as a stereoscope filetree).
type ChrootContext struct {
root string
base string
cwd string
cwdRelativeToRoot string
}
func NewChrootContextFromCWD(root, base string) (*ChrootContext, error) {
currentWD, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("could not get current working directory: %w", err)
}
return NewChrootContext(root, base, currentWD)
}
func NewChrootContext(root, base, cwd string) (*ChrootContext, error) {
cleanRoot, err := NormalizeRootDirectory(root)
if err != nil {
return nil, err
}
cleanBase, err := NormalizeBaseDirectory(base)
if err != nil {
return nil, err
}
chroot := &ChrootContext{
root: cleanRoot,
base: cleanBase,
cwd: cwd,
}
return chroot, chroot.ChangeDirectory(cwd)
}
func NormalizeRootDirectory(root string) (string, error) {
cleanRoot, err := filepath.EvalSymlinks(root)
if err != nil {
return "", fmt.Errorf("could not evaluate root=%q symlinks: %w", root, err)
}
return cleanRoot, nil
}
func NormalizeBaseDirectory(base string) (string, error) {
if base == "" {
return "", nil
}
cleanBase, err := filepath.EvalSymlinks(base)
if err != nil {
return "", fmt.Errorf("could not evaluate base=%q symlinks: %w", base, err)
}
return filepath.Abs(cleanBase)
}
// Root returns the root path with all symlinks evaluated.
func (r ChrootContext) Root() string {
return r.root
}
// Base returns the absolute base path with all symlinks evaluated.
func (r ChrootContext) Base() string {
return r.base
}
// ChangeRoot swaps the path for the chroot.
func (r *ChrootContext) ChangeRoot(dir string) error {
newR, err := NewChrootContext(dir, r.base, r.cwd)
if err != nil {
return fmt.Errorf("could not change root: %w", err)
}
*r = *newR
return nil
}
// ChangeDirectory changes the current working directory so that any relative paths passed
// into ToNativePath() and ToChrootPath() honor the new CWD. If the process changes the CWD in-flight, this should be
// called again to ensure correct functionality of ToNativePath() and ToChrootPath().
func (r *ChrootContext) ChangeDirectory(dir string) error {
var (
cwdRelativeToRoot string
err error
)
dir, err = filepath.Abs(dir)
if err != nil {
return fmt.Errorf("could not determine absolute path to CWD: %w", err)
}
if path.IsAbs(r.root) {
cwdRelativeToRoot, err = filepath.Rel(dir, r.root)
if err != nil {
return fmt.Errorf("could not determine given root path to CWD: %w", err)
}
} else {
cwdRelativeToRoot = filepath.Clean(r.root)
}
r.cwd = dir
r.cwdRelativeToRoot = cwdRelativeToRoot
return nil
}
// ToNativePath takes a path in the context of the chroot-like filesystem and converts it to a path in the underlying fs domain.
func (r ChrootContext) ToNativePath(chrootPath string) (string, error) {
responsePath := chrootPath
if filepath.IsAbs(responsePath) {
// don't allow input to potentially hop above root path
responsePath = path.Join(r.root, responsePath)
} else {
// ensure we take into account any relative difference between the root path and the CWD for relative requests
responsePath = path.Join(r.cwdRelativeToRoot, responsePath)
}
var err error
responsePath, err = filepath.Abs(responsePath)
if err != nil {
return "", err
}
return responsePath, nil
}
// ToChrootPath takes a path from the underlying fs domain and converts it to a path that is relative to the current root context.
func (r ChrootContext) ToChrootPath(nativePath string) string {
responsePath := nativePath
// check to see if we need to encode back to Windows from posix
if windows.HostRunningOnWindows() {
responsePath = windows.FromPosix(responsePath)
}
// clean references to the request path (either the root, or the base if set)
if filepath.IsAbs(responsePath) {
var prefix string
if r.base != "" {
prefix = r.base
} else {
// we need to account for the cwd relative to the running process and the given root for the directory resolver
prefix = filepath.Clean(filepath.Join(r.cwd, r.cwdRelativeToRoot))
prefix += string(filepath.Separator)
}
responsePath = strings.TrimPrefix(responsePath, prefix)
}
return responsePath
}
@@ -0,0 +1,481 @@
package fileresolver
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ChrootContext_RequestResponse(t *testing.T) {
// /
// somewhere/
// outside.txt
// root-link -> ./
// path/
// to/
// abs-inside.txt -> /path/to/the/file.txt # absolute link to somewhere inside of the root
// rel-inside.txt -> ./the/file.txt # relative link to somewhere inside of the root
// the/
// file.txt
// abs-outside.txt -> /somewhere/outside.txt # absolute link to outside of the root
// rel-outside -> ../../../somewhere/outside.txt # relative link to outside of the root
//
testDir, err := os.Getwd()
require.NoError(t, err)
relative := filepath.Join("test-fixtures", "req-resp")
absolute := filepath.Join(testDir, relative)
absPathToTheFile := filepath.Join(absolute, "path", "to", "the", "file.txt")
absAbsInsidePath := filepath.Join(absolute, "path", "to", "abs-inside.txt")
absAbsOutsidePath := filepath.Join(absolute, "path", "to", "the", "abs-outside.txt")
absRelOutsidePath := filepath.Join(absolute, "path", "to", "the", "rel-outside.txt")
relViaLink := filepath.Join(relative, "root-link")
absViaLink := filepath.Join(absolute, "root-link")
absViaLinkPathToTheFile := filepath.Join(absViaLink, "path", "to", "the", "file.txt")
absViaLinkAbsOutsidePath := filepath.Join(absViaLink, "path", "to", "the", "abs-outside.txt")
absViaLinkRelOutsidePath := filepath.Join(absViaLink, "path", "to", "the", "rel-outside.txt")
relViaDoubleLink := filepath.Join(relative, "root-link", "root-link")
absViaDoubleLink := filepath.Join(absolute, "root-link", "root-link")
absViaDoubleLinkPathToTheFile := filepath.Join(absViaDoubleLink, "path", "to", "the", "file.txt")
absViaDoubleLinkRelOutsidePath := filepath.Join(absViaDoubleLink, "path", "to", "the", "rel-outside.txt")
cleanup := func() {
_ = os.Remove(absAbsInsidePath)
_ = os.Remove(absAbsOutsidePath)
}
// ensure the absolute symlinks are cleaned up from any previous runs
cleanup()
require.NoError(t, os.Symlink(filepath.Join(absolute, "path", "to", "the", "file.txt"), absAbsInsidePath))
require.NoError(t, os.Symlink(filepath.Join(absolute, "somewhere", "outside.txt"), absAbsOutsidePath))
t.Cleanup(cleanup)
cases := []struct {
name string
cwd string
root string
base string
input string
expectedNativePath string
expectedChrootPath string
}{
{
name: "relative root, relative request, direct",
root: relative,
input: "path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, relative request, direct",
root: absolute,
input: "path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "relative root, abs request, direct",
root: relative,
input: "/path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, abs request, direct",
root: absolute,
input: "/path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
// cwd within root...
{
name: "relative root, relative request, direct, cwd within root",
cwd: filepath.Join(relative, "path/to"),
root: "../../",
input: "path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, relative request, direct, cwd within root",
cwd: filepath.Join(relative, "path/to"),
root: absolute,
input: "path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "relative root, abs request, direct, cwd within root",
cwd: filepath.Join(relative, "path/to"),
root: "../../",
input: "/path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, abs request, direct, cwd within root",
cwd: filepath.Join(relative, "path/to"),
root: absolute,
input: "/path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
// cwd within symlink root...
{
name: "relative root, relative request, direct, cwd within symlink root",
cwd: relViaLink,
root: "./",
input: "path/to/the/file.txt",
expectedNativePath: absViaLinkPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, relative request, direct, cwd within symlink root",
cwd: relViaLink,
root: absViaLink,
input: "path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "relative root, abs request, direct, cwd within symlink root",
cwd: relViaLink,
root: "./",
input: "/path/to/the/file.txt",
expectedNativePath: absViaLinkPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, abs request, direct, cwd within symlink root",
cwd: relViaLink,
root: absViaLink,
input: "/path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
// cwd within symlink root, request nested within...
{
name: "relative root, relative nested request, direct, cwd within symlink root",
cwd: relViaLink,
root: "./path",
input: "to/the/file.txt",
expectedNativePath: absViaLinkPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "abs root, relative nested request, direct, cwd within symlink root",
cwd: relViaLink,
root: filepath.Join(absViaLink, "path"),
input: "to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "relative root, abs nested request, direct, cwd within symlink root",
cwd: relViaLink,
root: "./path",
input: "/to/the/file.txt",
expectedNativePath: absViaLinkPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "abs root, abs nested request, direct, cwd within symlink root",
cwd: relViaLink,
root: filepath.Join(absViaLink, "path"),
input: "/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
// cwd within DOUBLE symlink root...
{
name: "relative root, relative request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: "./",
input: "path/to/the/file.txt",
expectedNativePath: absViaDoubleLinkPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, relative request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: absViaDoubleLink,
input: "path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "relative root, abs request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: "./",
input: "/path/to/the/file.txt",
expectedNativePath: absViaDoubleLinkPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
{
name: "abs root, abs request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: absViaDoubleLink,
input: "/path/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "path/to/the/file.txt",
},
// cwd within DOUBLE symlink root, request nested within...
{
name: "relative root, relative nested request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: "./path",
input: "to/the/file.txt",
expectedNativePath: absViaDoubleLinkPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "abs root, relative nested request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: filepath.Join(absViaDoubleLink, "path"),
input: "to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "relative root, abs nested request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: "./path",
input: "/to/the/file.txt",
expectedNativePath: absViaDoubleLinkPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "abs root, abs nested request, direct, cwd within (double) symlink root",
cwd: relViaDoubleLink,
root: filepath.Join(absViaDoubleLink, "path"),
input: "/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
// cwd within DOUBLE symlink root, request nested DEEP within...
{
name: "relative root, relative nested request, direct, cwd deep within (double) symlink root",
cwd: filepath.Join(relViaDoubleLink, "path", "to"),
root: "../",
input: "to/the/file.txt",
expectedNativePath: absViaDoubleLinkPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "abs root, relative nested request, direct, cwd deep within (double) symlink root",
cwd: filepath.Join(relViaDoubleLink, "path", "to"),
root: filepath.Join(absViaDoubleLink, "path"),
input: "to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "relative root, abs nested request, direct, cwd deep within (double) symlink root",
cwd: filepath.Join(relViaDoubleLink, "path", "to"),
root: "../",
input: "/to/the/file.txt",
expectedNativePath: absViaDoubleLinkPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
{
name: "abs root, abs nested request, direct, cwd deep within (double) symlink root",
cwd: filepath.Join(relViaDoubleLink, "path", "to"),
root: filepath.Join(absViaDoubleLink, "path"),
input: "/to/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "to/the/file.txt",
},
// link to outside of root cases...
{
name: "relative root, relative request, abs indirect (outside of root)",
root: filepath.Join(relative, "path"),
input: "to/the/abs-outside.txt",
expectedNativePath: absAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "abs root, relative request, abs indirect (outside of root)",
root: filepath.Join(absolute, "path"),
input: "to/the/abs-outside.txt",
expectedNativePath: absAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "relative root, abs request, abs indirect (outside of root)",
root: filepath.Join(relative, "path"),
input: "/to/the/abs-outside.txt",
expectedNativePath: absAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "abs root, abs request, abs indirect (outside of root)",
root: filepath.Join(absolute, "path"),
input: "/to/the/abs-outside.txt",
expectedNativePath: absAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "relative root, relative request, relative indirect (outside of root)",
root: filepath.Join(relative, "path"),
input: "to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "abs root, relative request, relative indirect (outside of root)",
root: filepath.Join(absolute, "path"),
input: "to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "relative root, abs request, relative indirect (outside of root)",
root: filepath.Join(relative, "path"),
input: "/to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "abs root, abs request, relative indirect (outside of root)",
root: filepath.Join(absolute, "path"),
input: "/to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
// link to outside of root cases... cwd within symlink root
{
name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: "path",
input: "to/the/abs-outside.txt",
expectedNativePath: absViaLinkAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: filepath.Join(absolute, "path"),
input: "to/the/abs-outside.txt",
expectedNativePath: absAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: "path",
input: "/to/the/abs-outside.txt",
expectedNativePath: absViaLinkAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: filepath.Join(absolute, "path"),
input: "/to/the/abs-outside.txt",
expectedNativePath: absAbsOutsidePath,
expectedChrootPath: "to/the/abs-outside.txt",
},
{
name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: "path",
input: "to/the/rel-outside.txt",
expectedNativePath: absViaLinkRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: filepath.Join(absolute, "path"),
input: "to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: "path",
input: "/to/the/rel-outside.txt",
expectedNativePath: absViaLinkRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root",
cwd: relViaLink,
root: filepath.Join(absolute, "path"),
input: "/to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
cwd: relViaDoubleLink,
root: "path",
input: "to/the/rel-outside.txt",
expectedNativePath: absViaDoubleLinkRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
cwd: relViaDoubleLink,
root: filepath.Join(absolute, "path"),
input: "to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
cwd: relViaDoubleLink,
root: "path",
input: "/to/the/rel-outside.txt",
expectedNativePath: absViaDoubleLinkRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
{
name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
cwd: relViaDoubleLink,
root: filepath.Join(absolute, "path"),
input: "/to/the/rel-outside.txt",
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
// we need to mimic a shell, otherwise we won't get a path within a symlink
targetPath := filepath.Join(testDir, c.cwd)
t.Setenv("PWD", filepath.Clean(targetPath))
require.NoError(t, err)
require.NoError(t, os.Chdir(targetPath))
t.Cleanup(func() {
require.NoError(t, os.Chdir(testDir))
})
chroot, err := NewChrootContextFromCWD(c.root, c.base)
require.NoError(t, err)
require.NotNil(t, chroot)
req, err := chroot.ToNativePath(c.input)
require.NoError(t, err)
assert.Equal(t, c.expectedNativePath, req, "native path different")
resp := chroot.ToChrootPath(req)
assert.Equal(t, c.expectedChrootPath, resp, "chroot path different")
})
}
}
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -513,6 +514,26 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
}
func compareLocations(t *testing.T, expected, actual []file.Location) {
t.Helper()
ignoreUnexported := cmpopts.IgnoreUnexported(file.LocationData{})
ignoreMetadata := cmpopts.IgnoreFields(file.LocationMetadata{}, "Annotations")
ignoreFS := cmpopts.IgnoreFields(file.Coordinates{}, "FileSystemID")
sort.Sort(file.Locations(expected))
sort.Sort(file.Locations(actual))
if d := cmp.Diff(expected, actual,
ignoreUnexported,
ignoreFS,
ignoreMetadata,
); d != "" {
t.Errorf("unexpected locations (-want +got):\n%s", d)
}
}
func TestSquashResolver_AllLocations(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted")
+22 -113
View File
@@ -5,19 +5,14 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"runtime"
"strings"
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/windows"
)
const WindowsOS = "windows"
var unixSystemRuntimePrefixes = []string{
"/proc",
"/dev",
@@ -30,14 +25,12 @@ var _ file.Resolver = (*Directory)(nil)
// Directory implements path and content access for the directory data source.
type Directory struct {
path string
base string
currentWdRelativeToRoot string
currentWd string
tree filetree.Reader
index filetree.IndexReader
searchContext filetree.Searcher
indexer *directoryIndexer
path string
chroot ChrootContext
tree filetree.Reader
index filetree.IndexReader
searchContext filetree.Searcher
indexer *directoryIndexer
}
func NewFromDirectory(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
@@ -50,46 +43,20 @@ func NewFromDirectory(root string, base string, pathFilters ...PathIndexVisitor)
}
func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
currentWD, err := os.Getwd()
chroot, err := NewChrootContextFromCWD(root, base)
if err != nil {
return nil, fmt.Errorf("could not get CWD: %w", err)
return nil, fmt.Errorf("unable to interpret chroot context: %w", err)
}
cleanRoot, err := filepath.EvalSymlinks(root)
if err != nil {
return nil, fmt.Errorf("could not evaluate root=%q symlinks: %w", root, err)
}
cleanBase := ""
if base != "" {
cleanBase, err = filepath.EvalSymlinks(base)
if err != nil {
return nil, fmt.Errorf("could not evaluate base=%q symlinks: %w", base, err)
}
cleanBase, err = filepath.Abs(cleanBase)
if err != nil {
return nil, err
}
}
var currentWdRelRoot string
if path.IsAbs(cleanRoot) {
currentWdRelRoot, err = filepath.Rel(currentWD, cleanRoot)
if err != nil {
return nil, fmt.Errorf("could not determine given root path to CWD: %w", err)
}
} else {
currentWdRelRoot = filepath.Clean(cleanRoot)
}
cleanRoot := chroot.Root()
cleanBase := chroot.Base()
return &Directory{
path: cleanRoot,
base: cleanBase,
currentWd: currentWD,
currentWdRelativeToRoot: currentWdRelRoot,
tree: filetree.New(),
index: filetree.NewIndex(),
indexer: newDirectoryIndexer(cleanRoot, cleanBase, pathFilters...),
path: cleanRoot,
chroot: *chroot,
tree: filetree.New(),
index: filetree.NewIndex(),
indexer: newDirectoryIndexer(cleanRoot, cleanBase, pathFilters...),
}, nil
}
@@ -110,43 +77,12 @@ func (r *Directory) buildIndex() error {
}
func (r Directory) requestPath(userPath string) (string, error) {
if filepath.IsAbs(userPath) {
// don't allow input to potentially hop above root path
userPath = path.Join(r.path, userPath)
} else {
// ensure we take into account any relative difference between the root path and the CWD for relative requests
userPath = path.Join(r.currentWdRelativeToRoot, userPath)
}
var err error
userPath, err = filepath.Abs(userPath)
if err != nil {
return "", err
}
return userPath, nil
return r.chroot.ToNativePath(userPath)
}
// responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the directory resolver.
func (r Directory) responsePath(path string) string {
// check to see if we need to encode back to Windows from posix
if runtime.GOOS == WindowsOS {
path = posixToWindows(path)
}
// clean references to the request path (either the root, or the base if set)
if filepath.IsAbs(path) {
var prefix string
if r.base != "" {
prefix = r.base
} else {
// we need to account for the cwd relative to the running process and the given root for the directory resolver
prefix = filepath.Clean(filepath.Join(r.currentWd, r.currentWdRelativeToRoot))
prefix += string(filepath.Separator)
}
path = strings.TrimPrefix(path, prefix)
}
return path
return r.chroot.ToChrootPath(path)
}
// HasPath indicates if the given path exists in the underlying source.
@@ -196,8 +132,8 @@ func (r Directory) FilesByPath(userPaths ...string) ([]file.Location, error) {
continue
}
if runtime.GOOS == WindowsOS {
userStrPath = windowsToPosix(userStrPath)
if windows.HostRunningOnWindows() {
userStrPath = windows.ToPosix(userStrPath)
}
if ref.HasReference() {
@@ -286,8 +222,8 @@ func (r Directory) FileContentsByLocation(location file.Location) (io.ReadCloser
// RealPath is posix so for windows directory resolver we need to translate
// to its true on disk path.
filePath := string(location.Reference().RealPath)
if runtime.GOOS == WindowsOS {
filePath = posixToWindows(filePath)
if windows.HostRunningOnWindows() {
filePath = windows.FromPosix(filePath)
}
return stereoscopeFile.NewLazyReadCloser(filePath), nil
@@ -338,30 +274,3 @@ func (r *Directory) FilesByMIMEType(types ...string) ([]file.Location, error) {
return uniqueLocations, nil
}
func windowsToPosix(windowsPath string) (posixPath string) {
// volume should be encoded at the start (e.g /c/<path>) where c is the volume
volumeName := filepath.VolumeName(windowsPath)
pathWithoutVolume := strings.TrimPrefix(windowsPath, volumeName)
volumeLetter := strings.ToLower(strings.TrimSuffix(volumeName, ":"))
// translate non-escaped backslash to forwardslash
translatedPath := strings.ReplaceAll(pathWithoutVolume, "\\", "/")
// always have `/` as the root... join all components, e.g.:
// convert: C:\\some\windows\Place
// into: /c/some/windows/Place
return path.Clean("/" + strings.Join([]string{volumeLetter, translatedPath}, "/"))
}
func posixToWindows(posixPath string) (windowsPath string) {
// decode the volume (e.g. /c/<path> --> C:\\) - There should always be a volume name.
pathFields := strings.Split(posixPath, "/")
volumeName := strings.ToUpper(pathFields[1]) + `:\\`
// translate non-escaped forward slashes into backslashes
remainingTranslatedPath := strings.Join(pathFields[2:], "\\")
// combine volume name and backslash components
return filepath.Clean(volumeName + remainingTranslatedPath)
}
@@ -7,7 +7,6 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/wagoodman/go-partybus"
@@ -19,6 +18,7 @@ import (
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/internal/windows"
)
type PathIndexVisitor func(string, os.FileInfo, error) error
@@ -263,8 +263,8 @@ func (r *directoryIndexer) indexPath(path string, info os.FileInfo, err error) (
}
// here we check to see if we need to normalize paths to posix on the way in coming from windows
if runtime.GOOS == WindowsOS {
path = windowsToPosix(path)
if windows.HostRunningOnWindows() {
path = windows.ToPosix(path)
}
newRoot, err := r.addPathToIndex(path, info)
+2 -2
View File
@@ -16,9 +16,9 @@ type excluding struct {
excludeFn excludeFn
}
// NewExcluding create a new resolver which wraps the provided delegate and excludes
// NewExcludingDecorator create a new resolver which wraps the provided delegate and excludes
// entries based on a provided path exclusion function
func NewExcluding(delegate file.Resolver, excludeFn excludeFn) file.Resolver {
func NewExcludingDecorator(delegate file.Resolver, excludeFn excludeFn) file.Resolver {
return &excluding{
delegate,
excludeFn,
@@ -56,7 +56,7 @@ func TestExcludingResolver(t *testing.T) {
resolver := &mockResolver{
locations: test.locations,
}
er := NewExcluding(resolver, test.excludeFn)
er := NewExcludingDecorator(resolver, test.excludeFn)
locations, _ := er.FilesByPath()
assert.ElementsMatch(t, locationPaths(locations), test.expected)
@@ -0,0 +1,2 @@
path/to/abs-inside.txt
path/to/the/abs-outside.txt
@@ -14,7 +14,6 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -1263,23 +1262,3 @@ func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T))
case <-done:
}
}
func compareLocations(t *testing.T, expected, actual []file.Location) {
t.Helper()
ignoreUnexported := cmpopts.IgnoreFields(file.LocationData{}, "ref")
ignoreMetadata := cmpopts.IgnoreFields(file.LocationMetadata{}, "Annotations")
ignoreFS := cmpopts.IgnoreFields(file.Coordinates{}, "FileSystemID")
sort.Sort(file.Locations(expected))
sort.Sort(file.Locations(actual))
if d := cmp.Diff(expected, actual,
ignoreUnexported,
ignoreFS,
ignoreMetadata,
); d != "" {
t.Errorf("unexpected locations (-want +got):\n%s", d)
}
}