mirror of
https://github.com/anchore/syft.git
synced 2026-10-11 21:57:22 +02:00
Fix missing file hashes when running on windows system (#5341)
* test: add windows unit test job and capture dir resolver path bug - add a `Unit tests (Windows)` job running a new `make unit:windows` task, scoped to the packages that own host path handling (tests needing linux docker fixtures are skipped) - `Unit tests` is now a gate job over the Linux and Windows unit jobs - add a resolver test asserting that locations from `AllLocations` resolve again via `FilesByPath` and use posix paths. This is expected to fail on windows today, which is why file digests come back empty for `dir:` scans (#5325) - fix windows-only test files that no longer compiled Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * chore: drop verbose output from integration tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: windows path handling outside of the file lookup bug - chroot paths are always posix (`ToChrootPath` no longer returns backslash paths on windows), same for the directory source name derived from `base` - `windows.FromPosix` no longer panics on input that isn't volume-encoded (e.g. relative or already native paths) - skip tests on windows that rely on symlink fixtures, shell script fixtures, or emulate windows paths on a posix host - disable `core.autocrlf` on the windows CI job so fixture digests match linux This leaves only the `FilesByPath` lookup failures from #5325. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test: skip posix-only fileresolver tests on windows Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: split contained paths on posix separators on windows - `allContainedPaths` cleaned with the native separator but split on `/`, so a root reached through a symlink never had its ancestors indexed on windows - index assertions in tests now key on the posix path the index actually stores - skip symlink fixture tests and a posix-only test helper on windows Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * chore: revert unrelated go.sum churn and allContainedPaths change - restore `go.sum` and `.make/go.sum` (accidentally populated by a local `-mod=mod` invocation) - revert the `allContainedPaths` separator change: production passes native windows paths there, and splitting them on `/` produces drive-relative ancestors, so skip the posix-only test on windows instead - annotate the remaining `filepath` usages that operate on native paths Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix: search the file tree with posix paths on windows (#5325) The directory and file resolvers key their file tree on posix paths (`/c/some/path`), but `FilesByPath`, `HasPath` and root-anchored `FilesByGlob` searched it with the native path from the chroot context (`C:\some\path`). On windows every lookup silently missed, so anything that resolves files by path found nothing. This is why file digests were empty for `dir:` scans. Requests are now converted to posix before searching the tree. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# check out text files with lf on every platform (regardless of core.autocrlf), letting git detect binary files
|
||||
* text=auto eol=lf
|
||||
|
||||
# fixtures that must keep their crlf line endings byte for byte
|
||||
syft/linux/testdata/unprintable -text
|
||||
syft/pkg/cataloger/bitnami/testdata/no-rel/opt/bitnami/redis/.spdx-redis.spdx -text
|
||||
syft/pkg/cataloger/java/testdata/jar-metadata/**/META-INF/MANIFEST.MF -text
|
||||
@@ -49,6 +49,13 @@ jobs:
|
||||
Unit-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-latest
|
||||
needs: [Unit-Test-Linux, Unit-Test-Windows]
|
||||
steps:
|
||||
- run: echo All unit tests passed!
|
||||
|
||||
Unit-Test-Linux:
|
||||
name: "Unit tests (Linux)"
|
||||
runs-on: *test-runner
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -68,6 +75,27 @@ jobs:
|
||||
- name: Check for capability drift
|
||||
run: make check-capability-drift
|
||||
|
||||
Unit-Test-Windows:
|
||||
name: "Unit tests (Windows)"
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# go-make's setup only (not ./.github/actions/bootstrap): no apt, binny tools, or fixture cache are needed
|
||||
- name: Setup go + go-make tooling
|
||||
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
|
||||
with:
|
||||
# keep in sync with the go-version default in ./.github/actions/bootstrap
|
||||
go-version: "1.26.2"
|
||||
|
||||
# the full unit suite leans on linux docker fixtures, so this runs the windows-relevant subset
|
||||
- name: Run unit tests
|
||||
run: make unit:windows
|
||||
|
||||
Integration-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Integration tests"
|
||||
|
||||
+19
-1
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -52,7 +53,7 @@ func main() {
|
||||
raceFlag = " -race"
|
||||
}
|
||||
Run(
|
||||
"go test -count=1 -timeout=30m -v"+raceFlag+" ./cmd/syft/internal/test/integration/...",
|
||||
"go test -count=1 -timeout=30m"+raceFlag+" ./cmd/syft/internal/test/integration/...",
|
||||
run.Env("GODEBUG", "dontfreezetheworld=1"),
|
||||
)
|
||||
},
|
||||
@@ -96,6 +97,23 @@ func main() {
|
||||
},
|
||||
},
|
||||
|
||||
// windows unit tests: deliberately not hooked into "test" (no RunsOn). The full unit suite leans on
|
||||
// linux docker fixture images and posix-only assumptions, so this covers the packages that own
|
||||
// host path handling (where windows bugs live) and skips the tests that need docker image fixtures.
|
||||
// ponytail: curated package list, widen it as more of the suite is made windows-safe.
|
||||
Task{
|
||||
Name: "unit:windows",
|
||||
Description: "run the windows-relevant subset of unit tests",
|
||||
Run: func() {
|
||||
Run("go test -count=1"+
|
||||
` -skip "Image|Squash|AllLayers|MixFileTypes|GivenCoordinates"`+
|
||||
" ./syft/internal/windows/... ./syft/internal/fileresolver/... ./syft/source/directorysource/... ./syft/source/filesource/..."+
|
||||
" ./syft/file ./syft/file/cataloger/filedigest/... ./syft/file/cataloger/filecontent/...",
|
||||
run.Stdout(os.Stderr),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// default validation pipeline (replaces Taskfile `default`/`pr-validations`/`validations`).
|
||||
Task{
|
||||
Name: "default",
|
||||
|
||||
@@ -216,5 +216,8 @@ func (r ChrootContext) ToChrootPath(nativePath string) string {
|
||||
responsePath = strings.TrimPrefix(responsePath, prefix)
|
||||
}
|
||||
|
||||
return responsePath
|
||||
// chroot paths are posix regardless of host (these end up as location paths in the SBOM). This uses
|
||||
// filepath (not path) since responsePath is native here (FromPosix above, then trimmed of a native prefix),
|
||||
// and only filepath.ToSlash knows the host separator to convert.
|
||||
return filepath.ToSlash(responsePath)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package fileresolver
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -10,6 +11,9 @@ import (
|
||||
)
|
||||
|
||||
func Test_ChrootContext_RequestResponse(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("fixture relies on symlinks, which git does not check out as links on windows by default")
|
||||
}
|
||||
// /
|
||||
// somewhere/
|
||||
// outside.txt
|
||||
@@ -563,6 +567,9 @@ func TestNewChrootContext_BaseValidation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToNativeGlob(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("chroot context and expectations are posix paths")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
chrootContext ChrootContext
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/internal/windows"
|
||||
)
|
||||
|
||||
type indexerMock struct {
|
||||
@@ -138,6 +138,9 @@ func TestDirectoryIndexer_handleFileAccessErr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryIndexer_IncludeRootPathInIndex(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("indexes the posix filesystem root")
|
||||
}
|
||||
filterFn := func(_, path string, _ os.FileInfo, _ error) error {
|
||||
if path != "/" {
|
||||
return fs.SkipDir
|
||||
@@ -206,11 +209,7 @@ func TestDirectoryIndexer_index(t *testing.T) {
|
||||
info, err := os.Stat(test.path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// note: the index uses absolute paths, so assertions MUST keep this in mind
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
p := file.Path(path.Join(cwd, test.path))
|
||||
p := indexedPath(t, test.path)
|
||||
assert.Equal(t, true, tree.HasPath(p))
|
||||
exists, ref, err := tree.File(p)
|
||||
assert.Equal(t, true, exists)
|
||||
@@ -226,6 +225,9 @@ func TestDirectoryIndexer_index(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryIndexer_index_for_AncestorSymlinks(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("fixture relies on symlinks, which git does not check out as links on windows by default")
|
||||
}
|
||||
// note: this test is testing the effects from NewFromDirectory, indexTree, and addPathToIndex
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
require.True(t, ok)
|
||||
@@ -256,11 +258,7 @@ func TestDirectoryIndexer_index_for_AncestorSymlinks(t *testing.T) {
|
||||
info, err := os.Stat(test.path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// note: the index uses absolute paths, so assertions MUST keep this in mind
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
p := file.Path(path.Join(cwd, test.path))
|
||||
p := indexedPath(t, test.path)
|
||||
assert.Equal(t, true, tree.HasPath(p))
|
||||
exists, ref, err := tree.File(p)
|
||||
assert.Equal(t, true, exists)
|
||||
@@ -287,6 +285,9 @@ func TestDirectoryIndexer_index_survive_badSymlink(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryIndexer_index_survive_inaccessibleSymlinkTarget(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("relies on symlinks and posix permissions")
|
||||
}
|
||||
// a symlink that resolves into a directory we don't have permission to traverse should be skipped
|
||||
// with a warning like any other inaccessible path, not abort the entire scan (see #3286).
|
||||
if os.Geteuid() == 0 {
|
||||
@@ -323,6 +324,9 @@ func TestDirectoryIndexer_index_survive_inaccessibleSymlinkTarget(t *testing.T)
|
||||
}
|
||||
|
||||
func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("fixture relies on symlinks, which git does not check out as links on windows by default")
|
||||
}
|
||||
var observedPaths []string
|
||||
pathObserver := func(_, p string, _ os.FileInfo, _ error) error {
|
||||
fields := strings.Split(p, "testdata/symlinks-prune-indexing")
|
||||
@@ -430,6 +434,11 @@ func TestDirectoryIndexer_IndexesAllTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_allContainedPaths(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// ponytail: production passes native windows paths here, which these posix cases don't represent.
|
||||
// Ancestor indexing for a root reached through a symlink is likely broken on windows; fix separately.
|
||||
t.Skip("cases are posix paths")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -485,6 +494,9 @@ func Test_allContainedPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_relativePath(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("exercises a posix-only test helper")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
basePath string
|
||||
@@ -548,3 +560,17 @@ func relativePath(basePath, givenPath string) string {
|
||||
|
||||
return relPath
|
||||
}
|
||||
|
||||
// indexedPath returns how the given cwd-relative path is keyed in the index (absolute and always posix)
|
||||
func indexedPath(t *testing.T, rel string) file.Path {
|
||||
t.Helper()
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
// filepath (not path): cwd is a native host path, so it must be joined with the host separator before
|
||||
// being converted to the posix form the index is keyed on
|
||||
p := filepath.Join(cwd, rel)
|
||||
if windows.HostRunningOnWindows() {
|
||||
p = windows.ToPosix(p)
|
||||
}
|
||||
return file.Path(p)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package fileresolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
// a location handed out by the resolver should be resolvable again by its own path. This is what the file
|
||||
// digest cataloger relies on, and it silently found nothing on windows (see #5325).
|
||||
func TestDirectory_LocationPathsRoundTrip(t *testing.T) {
|
||||
// resolve symlinks up front (e.g. /var -> /private/var on macOS) so the cwd prefix matches the indexed paths
|
||||
root, err := filepath.EvalSymlinks(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(root, "sub", "dir"), 0o755))
|
||||
for _, p := range []string{"top.txt", filepath.Join("sub", "mid.txt"), filepath.Join("sub", "dir", "deep.txt")} {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(root, p), []byte(p), 0o600))
|
||||
}
|
||||
|
||||
// mirror `syft dir:.` from within the scanned directory
|
||||
t.Chdir(root)
|
||||
|
||||
resolver, err := NewFromDirectory(".", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
var regular int
|
||||
for loc := range resolver.AllLocations(context.Background()) {
|
||||
md, err := resolver.FileMetadataByLocation(loc)
|
||||
require.NoError(t, err)
|
||||
if md.Type != stereoscopeFile.TypeRegular {
|
||||
continue
|
||||
}
|
||||
regular++
|
||||
|
||||
assert.NotContains(t, loc.RealPath, `\`, "location paths should be posix")
|
||||
|
||||
found, err := resolver.FilesByPath(loc.RealPath)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, found, 1, "unable to resolve %q by its own path", loc.RealPath)
|
||||
}
|
||||
assert.Equal(t, 3, regular)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package fileresolver
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_windowsToPosix(t *testing.T) {
|
||||
type args struct {
|
||||
windowsPath string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantPosixPath string
|
||||
}{
|
||||
{
|
||||
name: "basic case",
|
||||
args: args{
|
||||
windowsPath: `C:\some\windows\place`,
|
||||
},
|
||||
wantPosixPath: "/c/some/windows/place",
|
||||
},
|
||||
{
|
||||
name: "escaped case",
|
||||
args: args{
|
||||
windowsPath: `C:\\some\\windows\\place`,
|
||||
},
|
||||
wantPosixPath: "/c/some/windows/place",
|
||||
},
|
||||
{
|
||||
name: "forward slash",
|
||||
args: args{
|
||||
windowsPath: `C:/foo/bar`,
|
||||
},
|
||||
wantPosixPath: "/c/foo/bar",
|
||||
},
|
||||
{
|
||||
name: "mix slash",
|
||||
args: args{
|
||||
windowsPath: `C:\foo/bar\`,
|
||||
},
|
||||
wantPosixPath: "/c/foo/bar",
|
||||
},
|
||||
{
|
||||
name: "case sensitive case",
|
||||
args: args{
|
||||
windowsPath: `C:\Foo/bAr\`,
|
||||
},
|
||||
wantPosixPath: "/c/Foo/bAr",
|
||||
},
|
||||
{
|
||||
name: "special char case",
|
||||
args: args{
|
||||
windowsPath: `C:\ふー\バー`,
|
||||
},
|
||||
wantPosixPath: "/c/ふー/バー",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotPosixPath := windowsToPosix(tt.args.windowsPath); gotPosixPath != tt.wantPosixPath {
|
||||
t.Errorf("windowsToPosix() = %v, want %v", gotPosixPath, tt.wantPosixPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_posixToWindows(t *testing.T) {
|
||||
type args struct {
|
||||
posixPath string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantWindowsPath string
|
||||
}{
|
||||
{
|
||||
name: "basic case",
|
||||
args: args{
|
||||
posixPath: "/c/some/windows/place",
|
||||
},
|
||||
wantWindowsPath: `C:\some\windows\place`,
|
||||
},
|
||||
{
|
||||
name: "escaped case",
|
||||
args: args{
|
||||
posixPath: "/c/some/windows/place",
|
||||
},
|
||||
wantWindowsPath: `C:\\some\\windows\\place`,
|
||||
},
|
||||
{
|
||||
name: "forward slash",
|
||||
args: args{
|
||||
posixPath: "/c/foo/bar",
|
||||
},
|
||||
wantWindowsPath: `C:/foo/bar`,
|
||||
},
|
||||
{
|
||||
name: "mix slash",
|
||||
args: args{
|
||||
posixPath: "/c/foo/bar",
|
||||
},
|
||||
wantWindowsPath: `C:\foo/bar\`,
|
||||
},
|
||||
{
|
||||
name: "case sensitive case",
|
||||
args: args{
|
||||
posixPath: "/c/Foo/bAr",
|
||||
},
|
||||
wantWindowsPath: `C:\Foo/bAr\`,
|
||||
},
|
||||
{
|
||||
name: "special char case",
|
||||
args: args{
|
||||
posixPath: "/c/ふー/バー",
|
||||
},
|
||||
wantWindowsPath: `C:\ふー\バー`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotWindowsPath := posixToWindows(tt.args.posixPath); gotWindowsPath != tt.wantWindowsPath {
|
||||
t.Errorf("posixToWindows() = %v, want %v", gotWindowsPath, tt.wantWindowsPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,11 @@ package fileresolver
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
// - Verify that both the parent and the path are indexed
|
||||
@@ -38,11 +36,7 @@ func Test_index(t *testing.T) {
|
||||
info, err := os.Stat(test.path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// note: the index uses absolute paths, so assertions MUST keep this in mind
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
p := file.Path(path.Join(cwd, test.path))
|
||||
p := indexedPath(t, test.path)
|
||||
assert.Equal(t, true, tree.HasPath(p))
|
||||
exists, ref, err := tree.File(p)
|
||||
assert.Equal(t, true, exists)
|
||||
@@ -71,7 +65,9 @@ func Test_ignoresPathIfFiltered(t *testing.T) {
|
||||
testPath := "testdata/system_paths/target/home/place"
|
||||
cwd, cwdErr := os.Getwd()
|
||||
require.NoError(t, cwdErr)
|
||||
ignorePath := path.Join(cwd, testPath)
|
||||
// filepath (not path): filters see the native absolute path (before posix normalization), so the
|
||||
// expected value must be joined with the host separator to match on windows
|
||||
ignorePath := filepath.Join(cwd, testPath)
|
||||
filterFn := func(_, path string, _ os.FileInfo, _ error) error {
|
||||
if path == ignorePath {
|
||||
return ErrSkipPath
|
||||
@@ -91,7 +87,8 @@ func Test_ignoresPathIfParentFiltered(t *testing.T) {
|
||||
|
||||
cwd, cwdErr := os.Getwd()
|
||||
require.NoError(t, cwdErr)
|
||||
ignorePath := path.Join(cwd, parentPath)
|
||||
// filepath (not path): see Test_ignoresPathIfFiltered
|
||||
ignorePath := filepath.Join(cwd, parentPath)
|
||||
filterFn := func(_, path string, _ os.FileInfo, _ error) error {
|
||||
if path == ignorePath {
|
||||
return fs.SkipDir
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
@@ -34,8 +35,17 @@ func nativeOSFileOpener(ref stereoscopeFile.Reference) (io.ReadCloser, error) {
|
||||
return stereoscopeFile.NewLazyReadCloser(filePath), nil
|
||||
}
|
||||
|
||||
// requestPath converts a user (chroot) path into the path the file tree is keyed on. The tree is always posix
|
||||
// (windows paths are volume-encoded, e.g. /c/some/path), while ToNativePath yields a native host path.
|
||||
func (r *FiletreeResolver) requestPath(userPath string) (string, error) {
|
||||
return r.Chroot.ToNativePath(userPath)
|
||||
nativePath, err := r.Chroot.ToNativePath(userPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if windows.HostRunningOnWindows() {
|
||||
return windows.ToPosix(nativePath), nil
|
||||
}
|
||||
return nativePath, nil
|
||||
}
|
||||
|
||||
// responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the file resolver.
|
||||
@@ -85,10 +95,6 @@ func (r FiletreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err
|
||||
continue
|
||||
}
|
||||
|
||||
if windows.HostRunningOnWindows() {
|
||||
userStrPath = windows.ToPosix(userStrPath)
|
||||
}
|
||||
|
||||
if ref.HasReference() {
|
||||
references = append(references,
|
||||
file.NewVirtualLocationFromDirectory(
|
||||
@@ -103,8 +109,19 @@ func (r FiletreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err
|
||||
return references, nil
|
||||
}
|
||||
|
||||
// requestGlob is the glob equivalent of requestPath.
|
||||
func (r FiletreeResolver) requestGlob(pattern string) (string, error) {
|
||||
return r.Chroot.ToNativeGlob(pattern)
|
||||
glob, err := r.Chroot.ToNativeGlob(pattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// only a glob that was anchored to the root comes back native. Globs that start with a wildcard
|
||||
// (e.g. **/foo) are returned untouched and are already posix, and ToPosix would root them at "/".
|
||||
// This uses filepath (not path) since we are asking whether this is a native absolute path.
|
||||
if windows.HostRunningOnWindows() && filepath.IsAbs(glob) {
|
||||
return windows.ToPosix(glob), nil
|
||||
}
|
||||
return glob, nil
|
||||
}
|
||||
|
||||
// FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
|
||||
|
||||
@@ -35,6 +35,10 @@ func ToPosix(windowsPath string) (posixPath string) {
|
||||
func FromPosix(posixPath string) (windowsPath string) {
|
||||
// decode the volume (e.g. /c/<path> --> C:\\) - There should always be a volume name.
|
||||
// The volume may be a UNC path (e.g. /\\localhost\C$\ --> \\localhost\C$\)
|
||||
if !strings.HasPrefix(posixPath, "/") {
|
||||
// not a volume-encoded posix path (e.g. relative or already native), so there is nothing to decode
|
||||
return posixPath
|
||||
}
|
||||
pathFields := strings.Split(posixPath, "/")
|
||||
if len(pathFields) < 2 {
|
||||
// no separator means no volume to decode
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToPosix(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
`C:\some\windows\place`: "/c/some/windows/place",
|
||||
`C:\\some\\windows\\place`: "/c/some/windows/place",
|
||||
`C:/foo/bar`: "/c/foo/bar",
|
||||
`C:\foo/bar\`: "/c/foo/bar",
|
||||
`C:\Foo/bAr\`: "/c/Foo/bAr",
|
||||
`C:\ふー\バー`: "/c/ふー/バー",
|
||||
}
|
||||
for in, want := range tests {
|
||||
assert.Equal(t, want, ToPosix(in), in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromPosix(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"/c/some/windows/place": `C:\some\windows\place`,
|
||||
"/c/Foo/bAr": `C:\Foo\bAr`,
|
||||
"/c/ふー/バー": `C:\ふー\バー`,
|
||||
// non volume-encoded input is left alone
|
||||
"relative/path": "relative/path",
|
||||
`D:\already\native`: `D:\already\native`,
|
||||
}
|
||||
for in, want := range tests {
|
||||
assert.Equal(t, want, FromPosix(in), in)
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,8 @@ func cleanDirPath(path, base string) string {
|
||||
|
||||
relPath, err := filepath.Rel(cleanBase, cleanRoot)
|
||||
if err == nil {
|
||||
path = relPath
|
||||
// filepath.Rel yields a native path, but source names are posix like every other path syft reports
|
||||
path = filepath.ToSlash(relPath)
|
||||
}
|
||||
// this is odd, but this means we can't use base
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -325,6 +326,9 @@ func Test_DirectorySource_Exclusions(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_getDirectoryExclusionFunctions_crossPlatform(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("emulates windows paths on a posix host, see directory_source_win_test.go for the windows equivalent")
|
||||
}
|
||||
testCases := []struct {
|
||||
desc string
|
||||
root string
|
||||
@@ -465,10 +469,11 @@ func Test_DirectorySource_ID(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/testdata
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
want artifact.ID
|
||||
wantErr require.ErrorAssertionFunc
|
||||
name string
|
||||
cfg Config
|
||||
want artifact.ID
|
||||
wantErr require.ErrorAssertionFunc
|
||||
skipOnWindows bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -486,6 +491,8 @@ func Test_DirectorySource_ID(t *testing.T) {
|
||||
name: "with odd unclean path through non-existent directory",
|
||||
cfg: Config{Path: "testdata/does-not-exist/../"},
|
||||
wantErr: require.Error,
|
||||
// windows cleans the path lexically, so the non-existent directory is never visited
|
||||
skipOnWindows: true,
|
||||
},
|
||||
{
|
||||
name: "to a file (not a directory)",
|
||||
@@ -543,6 +550,9 @@ func Test_DirectorySource_ID(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipOnWindows && runtime.GOOS == "windows" {
|
||||
t.Skip("posix-specific path behavior")
|
||||
}
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
@@ -56,8 +56,9 @@ func Test_DirectorySource_crossPlatformExclusions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, f := range fns {
|
||||
result := f(test.path, nil, nil)
|
||||
require.Equal(t, test.match, result)
|
||||
// a matched exclusion is signaled by a skip error
|
||||
err := f("", test.path, nil, nil)
|
||||
require.Equal(t, test.match, err != nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
@@ -96,6 +97,9 @@ func TestNewFromFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewFromFile_WithArchive(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("archive fixtures are generated with a shell script")
|
||||
}
|
||||
testutil.Chdir(t, "..") // run with source/testdata
|
||||
|
||||
testCases := []struct {
|
||||
|
||||
Reference in New Issue
Block a user