Skip to content

Commit

Permalink
feat(fs): customize error callback during fs walk (#4038)
Browse files Browse the repository at this point in the history
  • Loading branch information
knqyf263 committed Apr 17, 2023
1 parent 3f02fee commit 914c6f0
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 28 deletions.
18 changes: 15 additions & 3 deletions pkg/fanal/artifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
misconf "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/fanal/walker"
)

type Option struct {
Expand All @@ -19,22 +20,33 @@ type Option struct {
NoProgress bool
Offline bool
AppDirs []string
RepoBranch string
RepoCommit string
RepoTag string
SBOMSources []string
RekorURL string
Platform string
Slow bool // Lower CPU and memory
AWSRegion string
FileChecksum bool // For SPDX

// Git repositories
RepoBranch string
RepoCommit string
RepoTag string

// For OCI registries
types.RemoteOptions

MisconfScannerOption misconf.ScannerOption
SecretScannerOption analyzer.SecretScannerOption
LicenseScannerOption analyzer.LicenseScannerOption

// File walk
WalkOption WalkOption
}

// WalkOption is a struct that allows users to define a custom walking behavior.
// This option is only available when using Trivy as an imported library and not through CLI flags.
type WalkOption struct {
ErrorCallback walker.ErrorCallback
}

func (o *Option) Sort() {
Expand Down
7 changes: 4 additions & 3 deletions pkg/fanal/artifact/local/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (a
}

return Artifact{
rootPath: filepath.Clean(rootPath),
cache: c,
walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs), opt.Slow),
rootPath: filepath.Clean(rootPath),
cache: c,
walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs),
opt.Slow, opt.WalkOption.ErrorCallback),
analyzer: a,
handlerManager: handlerManager,

Expand Down
41 changes: 26 additions & 15 deletions pkg/fanal/walker/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,28 @@ import (
"github.com/aquasecurity/trivy/pkg/log"
)

type ErrorCallback func(pathname string, err error) error

type FS struct {
walker
errCallback ErrorCallback
}

func NewFS(skipFiles, skipDirs []string, slow bool) FS {
func NewFS(skipFiles, skipDirs []string, slow bool, errCallback ErrorCallback) FS {
if errCallback == nil {
errCallback = func(pathname string, err error) error {
// ignore permission errors
if os.IsPermission(err) {
return nil
}
// halt traversal on any other error
return xerrors.Errorf("unknown error with %s: %w", pathname, err)
}
}

return FS{
walker: newWalker(skipFiles, skipDirs, slow),
walker: newWalker(skipFiles, skipDirs, slow),
errCallback: errCallback,
}
}

Expand Down Expand Up @@ -47,33 +62,26 @@ func (w FS) Walk(root string, fn WalkFunc) error {
return nil
}

if err := fn(relPath, fi, w.fileOpener(pathname)); err != nil {
if err = fn(relPath, fi, w.fileOpener(pathname)); err != nil {
return xerrors.Errorf("failed to analyze file: %w", err)
}
return nil
}

if w.slow {
// In series: fast, with higher CPU/memory
return walkSlow(root, walkFn)
return w.walkSlow(root, walkFn)
}

// In parallel: slow, with lower CPU/memory
return walkFast(root, walkFn)
return w.walkFast(root, walkFn)
}

type fastWalkFunc func(pathname string, fi os.FileInfo) error

func walkFast(root string, walkFn fastWalkFunc) error {
func (w FS) walkFast(root string, walkFn fastWalkFunc) error {
// error function called for every error encountered
errorCallbackOption := swalker.WithErrorCallback(func(pathname string, err error) error {
// ignore permission errors
if os.IsPermission(err) {
return nil
}
// halt traversal on any other error
return xerrors.Errorf("unknown error with %s: %w", pathname, err)
})
errorCallbackOption := swalker.WithErrorCallback(w.errCallback)

// Multiple goroutines stat the filesystem concurrently. The provided
// walkFn must be safe for concurrent use.
Expand All @@ -84,9 +92,12 @@ func walkFast(root string, walkFn fastWalkFunc) error {
return nil
}

func walkSlow(root string, walkFn fastWalkFunc) error {
func (w FS) walkSlow(root string, walkFn fastWalkFunc) error {
log.Logger.Debugf("Walk the file tree rooted at '%s' in series", root)
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return w.errCallback(path, err)
}
info, err := d.Info()
if err != nil {
return xerrors.Errorf("file info error: %w", err)
Expand Down
27 changes: 20 additions & 7 deletions pkg/fanal/walker/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"io"
"os"
"path/filepath"
"strings"
"testing"

Expand All @@ -17,8 +16,9 @@ import (

func TestDir_Walk(t *testing.T) {
type fields struct {
skipFiles []string
skipDirs []string
skipFiles []string
skipDirs []string
errCallback walker.ErrorCallback
}
tests := []struct {
name string
Expand All @@ -29,7 +29,7 @@ func TestDir_Walk(t *testing.T) {
}{
{
name: "happy path",
rootDir: filepath.Join("testdata", "fs"),
rootDir: "testdata/fs",
analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
if filePath == "testdata/fs/bar" {
got, err := opener()
Expand All @@ -45,7 +45,7 @@ func TestDir_Walk(t *testing.T) {
},
{
name: "skip file",
rootDir: filepath.Join("testdata", "fs"),
rootDir: "testdata/fs",
fields: fields{
skipFiles: []string{"testdata/fs/bar"},
},
Expand All @@ -69,9 +69,22 @@ func TestDir_Walk(t *testing.T) {
return nil
},
},
{
name: "ignore all errors",
rootDir: "testdata/fs/nosuch",
fields: fields{
errCallback: func(pathname string, err error) error {
return nil
},
},
analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
// Ignore errors
return nil
},
},
{
name: "sad path",
rootDir: filepath.Join("testdata", "fs"),
rootDir: "testdata/fs",
analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error {
return errors.New("error")
},
Expand All @@ -80,7 +93,7 @@ func TestDir_Walk(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, true)
w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, true, tt.fields.errCallback)

err := w.Walk(tt.rootDir, tt.analyzeFn)
if tt.wantErr != "" {
Expand Down

0 comments on commit 914c6f0

Please sign in to comment.