From 440c00986ac5924834749ad064f79522ef88c787 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Tue, 27 Jul 2021 15:50:24 +0300 Subject: [PATCH 1/6] Power-user directory source support Signed-off-by: Mikey Strauss Signed-off-by: houdini91 --- cmd/power_user.go | 5 ----- internal/err_helper.go | 24 ++++++++++++++++++++++ syft/file/classification_cataloger_test.go | 11 ++++++++-- syft/file/contents_cataloger.go | 8 +++++--- syft/file/digest_cataloger.go | 7 ++++++- syft/file/secrets_cataloger.go | 7 ++++++- syft/source/directory_resolver.go | 6 +++--- syft/source/directory_resolver_test.go | 15 +++++++++++--- syft/source/location.go | 8 ++++++++ test/cli/power_user_cmd_test.go | 23 +++++++++++++++++++++ 10 files changed, 96 insertions(+), 18 deletions(-) diff --git a/cmd/power_user.go b/cmd/power_user.go index e70cab218c8..4121322024d 100644 --- a/cmd/power_user.go +++ b/cmd/power_user.go @@ -102,11 +102,6 @@ func powerUserExecWorker(userInput string) <-chan error { } defer cleanup() - if src.Metadata.Scheme != source.ImageScheme { - errs <- fmt.Errorf("the power-user subcommand only allows for 'image' schemes, given %q", src.Metadata.Scheme) - return - } - analysisResults := poweruser.JSONDocumentConfig{ SourceMetadata: src.Metadata, ApplicationConfig: *appConfig, diff --git a/internal/err_helper.go b/internal/err_helper.go index 823f2912d3a..b2baea064b5 100644 --- a/internal/err_helper.go +++ b/internal/err_helper.go @@ -1,7 +1,9 @@ package internal import ( + "fmt" "io" + "os" "github.com/anchore/syft/internal/log" ) @@ -12,3 +14,25 @@ func CloseAndLogError(closer io.Closer, location string) { log.Warnf("unable to close file for location=%q: %+v", location, err) } } + +type ErrObserve struct { + Path string + Err error +} + +func (e ErrObserve) Error() string { + return fmt.Sprintf("unable to observe contents of %+v: %v", e.Path, e.Err) +} + +func IsErrObserve(err error) bool { + _, ok := err.(ErrObserve) + return ok +} + +func IsErrObservePermission(err error) bool { + observe_err, ok := err.(ErrObserve) + if ok { + return os.IsPermission(observe_err.Err) + } + return ok +} diff --git a/syft/file/classification_cataloger_test.go b/syft/file/classification_cataloger_test.go index 21c86947183..f8fd6033600 100644 --- a/syft/file/classification_cataloger_test.go +++ b/syft/file/classification_cataloger_test.go @@ -118,11 +118,18 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) { loc := source.NewLocation(test.location) - if _, ok := actualResults[loc]; !ok { + ok := false + for actual_loc, actual_classification := range actualResults { + if loc.RealPath == actual_loc.RealPath { + ok = true + assert.Equal(t, test.expected, actual_classification) + } + } + + if !ok { t.Fatalf("could not find test location=%q", test.location) } - assert.Equal(t, test.expected, actualResults[loc]) }) } } diff --git a/syft/file/contents_cataloger.go b/syft/file/contents_cataloger.go index 34d060fa860..542a7a6cca4 100644 --- a/syft/file/contents_cataloger.go +++ b/syft/file/contents_cataloger.go @@ -3,7 +3,6 @@ package file import ( "bytes" "encoding/base64" - "fmt" "io" "github.com/anchore/syft/internal" @@ -32,7 +31,6 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Lo if err != nil { return nil, err } - for _, location := range locations { metadata, err := resolver.FileMetadataByLocation(location) if err != nil { @@ -44,6 +42,10 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Lo } result, err := i.catalogLocation(resolver, location) + if internal.IsErrObservePermission(err) { + log.Debugf("file contents cataloger skipping - %+v", err) + continue + } if err != nil { return nil, err } @@ -63,7 +65,7 @@ func (i *ContentsCataloger) catalogLocation(resolver source.FileResolver, locati buf := &bytes.Buffer{} if _, err = io.Copy(base64.NewEncoder(base64.StdEncoding, buf), contentReader); err != nil { - return "", fmt.Errorf("unable to observe contents of %+v: %w", location.RealPath, err) + return "", internal.ErrObserve{Path: location.RealPath, Err: err} } return buf.String(), nil diff --git a/syft/file/digest_cataloger.go b/syft/file/digest_cataloger.go index b9808e76efd..d7240c119c9 100644 --- a/syft/file/digest_cataloger.go +++ b/syft/file/digest_cataloger.go @@ -39,6 +39,11 @@ func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Loc for _, location := range locations { stage.Current = location.RealPath result, err := i.catalogLocation(resolver, location) + if internal.IsErrObservePermission(err) { + log.Debugf("file digests cataloger skipping - %+v", err) + continue + } + if err != nil { return nil, err } @@ -67,7 +72,7 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio size, err := io.Copy(io.MultiWriter(writers...), contentReader) if err != nil { - return nil, fmt.Errorf("unable to observe contents of %+v: %+v", location.RealPath, err) + return nil, internal.ErrObserve{Path: location.RealPath, Err: err} } if size == 0 { diff --git a/syft/file/secrets_cataloger.go b/syft/file/secrets_cataloger.go index 95b1a11f76b..f964d57ae8a 100644 --- a/syft/file/secrets_cataloger.go +++ b/syft/file/secrets_cataloger.go @@ -50,6 +50,11 @@ func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Loc for _, location := range locations { stage.Current = location.RealPath result, err := i.catalogLocation(resolver, location) + if internal.IsErrObservePermission(err) { + log.Debugf("secrets cataloger skipping - %+v", err) + continue + } + if err != nil { return nil, err } @@ -77,7 +82,7 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio // TODO: in the future we can swap out search strategies here secrets, err := catalogLocationByLine(resolver, location, i.patterns) if err != nil { - return nil, err + return nil, internal.ErrObserve{Path: location.RealPath, Err: err} } if i.revealValues { diff --git a/syft/source/directory_resolver.go b/syft/source/directory_resolver.go index 0f886b22b5a..9e679dc26f0 100644 --- a/syft/source/directory_resolver.go +++ b/syft/source/directory_resolver.go @@ -234,7 +234,7 @@ func (r directoryResolver) FilesByGlob(patterns ...string) ([]Location, error) { return nil, err } for _, globResult := range globResults { - result = append(result, NewLocation(r.responsePath(string(globResult.MatchPath)))) + result = append(result, NewLocationFromDirectory(r.responsePath(string(globResult.MatchPath)), globResult.Reference)) } } @@ -267,7 +267,7 @@ func (r *directoryResolver) AllLocations() <-chan Location { go func() { defer close(results) for _, ref := range r.fileTree.AllFiles() { - results <- NewLocation(r.responsePath(string(ref.RealPath))) + results <- NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref) } }() return results @@ -276,7 +276,7 @@ func (r *directoryResolver) AllLocations() <-chan Location { func (r *directoryResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { info, exists := r.infos[location.ref.ID()] if !exists { - return FileMetadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrExist) + return FileMetadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist) } return FileMetadata{ diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index b0c3f71b44d..b9f7094bbee 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -171,6 +171,7 @@ func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { // all paths should be found (non filtering matches a path) refs, err := resolver.FilesByGlob("**/place") + assert.NoError(t, err) // 4: within target/ // 1: target/link --> relative path to "place" @@ -178,9 +179,17 @@ func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { assert.Len(t, refs, 6) // ensure that symlink indexing outside of root worked - assert.Contains(t, refs, Location{ - RealPath: "test-fixtures/system_paths/outside_root/link_target/place", - }) + ok := false + test_location := "test-fixtures/system_paths/outside_root/link_target/place" + for _, actual_loc := range refs { + if test_location == actual_loc.RealPath { + ok = true + } + } + + if !ok { + t.Fatalf("could not find test location=%q", test_location) + } } func TestDirectoryResolverUsesPathFilterFunction(t *testing.T) { diff --git a/syft/source/location.go b/syft/source/location.go index 532b0162394..fe06fba044b 100644 --- a/syft/source/location.go +++ b/syft/source/location.go @@ -45,6 +45,14 @@ func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Ima } } +// NewLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory. +func NewLocationFromDirectory(responsePath string, ref file.Reference) Location { + return Location{ + RealPath: responsePath, + ref: ref, + } +} + func NewLocationFromReference(ref file.Reference) Location { return Location{ VirtualPath: string(ref.RealPath), diff --git a/test/cli/power_user_cmd_test.go b/test/cli/power_user_cmd_test.go index 71445be626e..81a928fcdea 100644 --- a/test/cli/power_user_cmd_test.go +++ b/test/cli/power_user_cmd_test.go @@ -71,6 +71,29 @@ func TestPowerUserCmdFlags(t *testing.T) { assertSuccessfulReturnCode, }, }, + { + name: "default-dir-results-w-pkg-coverage", + args: []string{"power-user", "dir:test-fixtures/image-pkg-coverage"}, + assertions: []traitAssertion{ + assertNotInOutput(" command is deprecated"), // only the root command should be deprecated + assertInOutput(`"type": "RegularFile"`), // proof of file-metadata data + assertInOutput(`"algorithm": "sha256"`), // proof of file-metadata default digest algorithm of sha256 + assertInOutput(`"metadataType": "ApkMetadata"`), // proof of package artifacts data + assertSuccessfulReturnCode, + }, + }, + { + name: "defaut-secrets-dir-results-w-reveal-values", + env: map[string]string{ + "SYFT_SECRETS_REVEAL_VALUES": "true", + }, + args: []string{"power-user", "dir:test-fixtures/image-secrets"}, + assertions: []traitAssertion{ + assertInOutput(`"classification": "generic-api-key"`), // proof of the secrets cataloger finding something + assertInOutput(`"12345A7a901b345678901234567890123456789012345678901234567890"`), // proof of the secrets cataloger finding the api key + assertSuccessfulReturnCode, + }, + }, } for _, test := range tests { From 34d9dcd281edfee842bad1be2b1d521c17d5b8e7 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Tue, 27 Jul 2021 15:54:38 +0300 Subject: [PATCH 2/6] Remove newline Signed-off-by: houdini91 --- syft/source/directory_resolver_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index b9f7094bbee..9d85f21f5db 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -171,7 +171,6 @@ func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { // all paths should be found (non filtering matches a path) refs, err := resolver.FilesByGlob("**/place") - assert.NoError(t, err) // 4: within target/ // 1: target/link --> relative path to "place" From 6168a011dff68a309bf019af7160d020749baf3f Mon Sep 17 00:00:00 2001 From: houdini91 Date: Sun, 1 Aug 2021 13:12:07 +0300 Subject: [PATCH 3/6] Shared filetree (#1) * Shared directory resolver filetree Signed-off-by: houdini91 --- syft/source/directory_resolver.go | 57 ++++++++++++++++++--- syft/source/directory_resolver_test.go | 68 +++++++++++++++++++++++--- syft/source/source.go | 10 +++- 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/syft/source/directory_resolver.go b/syft/source/directory_resolver.go index 9e679dc26f0..85e508f7465 100644 --- a/syft/source/directory_resolver.go +++ b/syft/source/directory_resolver.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "strings" + "syscall" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" @@ -40,12 +41,19 @@ type directoryResolver struct { errPaths map[string]error } -func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryResolver, error) { +func newDirectoryResolver(fileTree *filetree.FileTree, root string, pathFilters ...pathFilterFn) (*directoryResolver, error) { cwd, err := os.Getwd() if err != nil { return nil, fmt.Errorf("could not create directory resolver: %w", err) } + var resolverFileTree *filetree.FileTree + if fileTree == nil { + resolverFileTree = filetree.NewFileTree() + } else { + resolverFileTree = fileTree + } + if pathFilters == nil { pathFilters = []pathFilterFn{isUnixSystemRuntimePath} } @@ -53,15 +61,32 @@ func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryR resolver := directoryResolver{ path: root, cwd: cwd, - fileTree: filetree.NewFileTree(), + fileTree: resolverFileTree, infos: make(map[file.ID]os.FileInfo), pathFilterFns: pathFilters, errPaths: make(map[string]error), } + if fileTree != nil { + resolver.CopyFromTree() + } + return &resolver, indexAllRoots(root, resolver.indexTree) } +func (r *directoryResolver) CopyFromTree() { + for _, ref := range r.fileTree.AllFiles() { + if _, ok := r.infos[ref.ID()]; !ok { + info, err := os.Stat(string(ref.RealPath)) + if err != nil { + log.Errorf("unable to copy path=%q: %+v", ref.RealPath, err) + continue + } + r.infos[ref.ID()] = info + } + } +} + func (r *directoryResolver) indexTree(root string, stager *progress.Stage) ([]string, error) { log.Infof("indexing filesystem path=%q", root) var err error @@ -218,7 +243,12 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error) continue } - references = append(references, NewLocation(r.responsePath(userStrPath))) + exists, ref, err := r.fileTree.File(file.Path(userStrPath)) + if err == nil && exists { + references = append(references, NewLocationFromDirectory(r.responsePath(userStrPath), *ref)) + } else { + log.Warnf("path (%s) not found in file tree: Exists: %t Err:%+v", userStrPath, exists, err) + } } return references, nil @@ -279,12 +309,19 @@ func (r *directoryResolver) FileMetadataByLocation(location Location) (FileMetad return FileMetadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist) } + uid := -1 + gid := -1 + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + uid = int(stat.Uid) + gid = int(stat.Gid) + } + return FileMetadata{ Mode: info.Mode(), Type: newFileTypeFromMode(info.Mode()), // unsupported across platforms - UserID: -1, - GroupID: -1, + UserID: uid, + GroupID: gid, }, nil } @@ -297,6 +334,8 @@ func indexAllRoots(root string, indexer func(string, *progress.Stage) ([]string, // in which case we need to additionally index where the link resolves to. it's for this reason why the filetree // must be relative to the root of the filesystem (and not just relative to the given path). pathsToIndex := []string{root} + fullPathsMap := map[string]struct{}{} + stager, prog := indexingProgress(root) defer prog.SetCompleted() loop: @@ -315,7 +354,13 @@ loop: if err != nil { return fmt.Errorf("unable to index filesystem path=%q: %w", currentPath, err) } - pathsToIndex = append(pathsToIndex, additionalRoots...) + + for _, newRoot := range additionalRoots { + if _, ok := fullPathsMap[newRoot]; !ok { + fullPathsMap[newRoot] = struct{}{} + pathsToIndex = append(pathsToIndex, newRoot) + } + } } return nil diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index 9d85f21f5db..8ae15f12ad8 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/filetree" "github.com/stretchr/testify/assert" "github.com/wagoodman/go-progress" ) @@ -65,7 +66,7 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver(c.root) + resolver, err := newDirectoryResolver(nil, c.root) assert.NoError(t, err) hasPath := resolver.HasPath(c.input) @@ -121,7 +122,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures") + resolver, err := newDirectoryResolver(nil, "./test-fixtures") assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input...) assert.NoError(t, err) @@ -133,8 +134,59 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { } } +func TestDirectoryResolver_SharedTreeMultipleFilesByPath(t *testing.T) { + cases := []struct { + name string + input []string + refCount int + }{ + { + name: "finds multiple files first resolver", + input: []string{"test-fixtures/image-symlinks/file-1.txt", "test-fixtures/image-symlinks/file-2.txt"}, + refCount: 2, + }, + { + name: "skips non-existing files", + input: []string{"test-fixtures/image-symlinks/bogus.txt", "test-fixtures/image-symlinks/file-1.txt"}, + refCount: 1, + }, + { + name: "does not return anything for non-existing directories", + input: []string{"test-fixtures/non-existing/bogus.txt", "test-fixtures/non-existing/file-1.txt"}, + refCount: 0, + }, + { + name: "find file second resolver", + input: []string{"test-fixtures/path-detected/.vimrc"}, + refCount: 1, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + fileTree := filetree.NewFileTree() + first_resolver, err := newDirectoryResolver(fileTree, "./test-fixtures/image-symlinks") + assert.NoError(t, err) + second_resolver, err := newDirectoryResolver(fileTree, "./test-fixtures/path-detected") + assert.NoError(t, err) + + first_refs, err := first_resolver.FilesByPath(c.input...) + assert.NoError(t, err) + second_refs, err := second_resolver.FilesByPath(c.input...) + assert.NoError(t, err) + + if len(first_refs) != len(second_refs) { + t.Errorf("unsynced number of refs: %d != %d", len(first_refs), len(second_refs)) + } + + if len(first_refs) != c.refCount { + t.Errorf("unexpected number of refs: %d != %d", len(first_refs), c.refCount) + } + }) + } +} + func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures") + resolver, err := newDirectoryResolver(nil, "./test-fixtures") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/image-symlinks/file*") assert.NoError(t, err) @@ -143,7 +195,7 @@ func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { } func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks") + resolver, err := newDirectoryResolver(nil, "./test-fixtures/image-symlinks") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) @@ -151,7 +203,7 @@ func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { } func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures") + resolver, err := newDirectoryResolver(nil, "./test-fixtures") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt") assert.NoError(t, err) @@ -162,7 +214,7 @@ func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) { func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { // let's make certain that "dev/place" is not ignored, since it is not "/dev/place" - resolver, err := newDirectoryResolver("test-fixtures/system_paths/target") + resolver, err := newDirectoryResolver(nil, "test-fixtures/system_paths/target") assert.NoError(t, err) // ensure the correct filter function is wired up by default expectedFn := reflect.ValueOf(isUnixSystemRuntimePath) @@ -198,7 +250,7 @@ func TestDirectoryResolverUsesPathFilterFunction(t *testing.T) { return strings.Contains(s, "dev/place") || strings.Contains(s, "proc/place") || strings.Contains(s, "sys/place") } - resolver, err := newDirectoryResolver("test-fixtures/system_paths/target", filter) + resolver, err := newDirectoryResolver(nil, "test-fixtures/system_paths/target", filter) assert.NoError(t, err) // ensure the correct filter function is wired up by default @@ -260,7 +312,7 @@ func Test_isUnixSystemRuntimePath(t *testing.T) { func Test_directoryResolver_index(t *testing.T) { // note: this test is testing the effects from newDirectoryResolver, indexTree, and addPathToIndex - r, err := newDirectoryResolver("test-fixtures/system_paths/target") + r, err := newDirectoryResolver(nil, "test-fixtures/system_paths/target") if err != nil { t.Fatalf("unable to get indexed dir resolver: %+v", err) } diff --git a/syft/source/source.go b/syft/source/source.go index 6ce6f228d61..d35f0022803 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -7,8 +7,10 @@ package source import ( "fmt" + "sync" "github.com/anchore/stereoscope" + "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" "github.com/spf13/afero" ) @@ -17,7 +19,9 @@ import ( // in cataloging (based on the data source and configuration) type Source struct { Image *image.Image // the image object to be cataloged (image only) + Tree *filetree.FileTree Metadata Metadata + Mutex *sync.Mutex } type sourceDetector func(string) (image.Source, string, error) @@ -68,6 +72,8 @@ func New(userInput string, registryOptions *image.RegistryOptions) (Source, func // NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively. func NewFromDirectory(path string) (Source, error) { return Source{ + Tree: filetree.NewFileTree(), + Mutex: &sync.Mutex{}, Metadata: Metadata{ Scheme: DirectoryScheme, Path: path, @@ -94,7 +100,9 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) { func (s Source) FileResolver(scope Scope) (FileResolver, error) { switch s.Metadata.Scheme { case DirectoryScheme: - return newDirectoryResolver(s.Metadata.Path) + s.Mutex.Lock() + defer s.Mutex.Unlock() + return newDirectoryResolver(s.Tree, s.Metadata.Path) case ImageScheme: switch scope { case SquashedScope: From 56f30ddda9311cffd35dcb7c1cb09bab4683c3c1 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Sun, 22 Aug 2021 09:20:44 +0300 Subject: [PATCH 4/6] PR - change error ErrObserve to ErrPath Signed-off-by: houdini91 --- internal/err_helper.go | 14 +++++++------- syft/file/contents_cataloger.go | 4 ++-- syft/file/digest_cataloger.go | 4 ++-- syft/file/secrets_cataloger.go | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/err_helper.go b/internal/err_helper.go index b2baea064b5..6bf0cd80c8d 100644 --- a/internal/err_helper.go +++ b/internal/err_helper.go @@ -15,24 +15,24 @@ func CloseAndLogError(closer io.Closer, location string) { } } -type ErrObserve struct { +type ErrPath struct { Path string Err error } -func (e ErrObserve) Error() string { +func (e ErrPath) Error() string { return fmt.Sprintf("unable to observe contents of %+v: %v", e.Path, e.Err) } -func IsErrObserve(err error) bool { - _, ok := err.(ErrObserve) +func IsErrPath(err error) bool { + _, ok := err.(ErrPath) return ok } -func IsErrObservePermission(err error) bool { - observe_err, ok := err.(ErrObserve) +func IsErrPathPermission(err error) bool { + path_err, ok := err.(ErrPath) if ok { - return os.IsPermission(observe_err.Err) + return os.IsPermission(path_err.Err) } return ok } diff --git a/syft/file/contents_cataloger.go b/syft/file/contents_cataloger.go index 542a7a6cca4..d4fa8f28920 100644 --- a/syft/file/contents_cataloger.go +++ b/syft/file/contents_cataloger.go @@ -42,7 +42,7 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Lo } result, err := i.catalogLocation(resolver, location) - if internal.IsErrObservePermission(err) { + if internal.IsErrPathPermission(err) { log.Debugf("file contents cataloger skipping - %+v", err) continue } @@ -65,7 +65,7 @@ func (i *ContentsCataloger) catalogLocation(resolver source.FileResolver, locati buf := &bytes.Buffer{} if _, err = io.Copy(base64.NewEncoder(base64.StdEncoding, buf), contentReader); err != nil { - return "", internal.ErrObserve{Path: location.RealPath, Err: err} + return "", internal.ErrPath{Path: location.RealPath, Err: err} } return buf.String(), nil diff --git a/syft/file/digest_cataloger.go b/syft/file/digest_cataloger.go index d7240c119c9..a4cc741381c 100644 --- a/syft/file/digest_cataloger.go +++ b/syft/file/digest_cataloger.go @@ -39,7 +39,7 @@ func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Loc for _, location := range locations { stage.Current = location.RealPath result, err := i.catalogLocation(resolver, location) - if internal.IsErrObservePermission(err) { + if internal.IsErrPathPermission(err) { log.Debugf("file digests cataloger skipping - %+v", err) continue } @@ -72,7 +72,7 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio size, err := io.Copy(io.MultiWriter(writers...), contentReader) if err != nil { - return nil, internal.ErrObserve{Path: location.RealPath, Err: err} + return nil, internal.ErrPath{Path: location.RealPath, Err: err} } if size == 0 { diff --git a/syft/file/secrets_cataloger.go b/syft/file/secrets_cataloger.go index f964d57ae8a..ea74af89659 100644 --- a/syft/file/secrets_cataloger.go +++ b/syft/file/secrets_cataloger.go @@ -50,7 +50,7 @@ func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Loc for _, location := range locations { stage.Current = location.RealPath result, err := i.catalogLocation(resolver, location) - if internal.IsErrObservePermission(err) { + if internal.IsErrPathPermission(err) { log.Debugf("secrets cataloger skipping - %+v", err) continue } @@ -82,7 +82,7 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio // TODO: in the future we can swap out search strategies here secrets, err := catalogLocationByLine(resolver, location, i.patterns) if err != nil { - return nil, internal.ErrObserve{Path: location.RealPath, Err: err} + return nil, internal.ErrPath{Path: location.RealPath, Err: err} } if i.revealValues { From a82eab0f66dfe4af39d23712b3e4c3803b2a3614 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Wed, 25 Aug 2021 14:02:36 +0300 Subject: [PATCH 5/6] PR - share directory resolver * Use pointer to source struct Signed-off-by: houdini91 --- cmd/packages.go | 2 +- cmd/power_user_tasks.go | 14 +++--- syft/lib.go | 2 +- syft/source/directory_resolver.go | 28 +---------- syft/source/directory_resolver_test.go | 68 +++----------------------- syft/source/source.go | 41 +++++++++------- syft/source/source_test.go | 62 +++++++++++++++++++++++ test/integration/utils_test.go | 4 +- 8 files changed, 106 insertions(+), 115 deletions(-) diff --git a/cmd/packages.go b/cmd/packages.go index 07351c8991c..fa60d363bf9 100644 --- a/cmd/packages.go +++ b/cmd/packages.go @@ -237,7 +237,7 @@ func packagesExecWorker(userInput string) <-chan error { return errs } -func runPackageSbomUpload(src source.Source, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) error { +func runPackageSbomUpload(src *source.Source, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) error { log.Infof("uploading results to %s", appConfig.Anchore.Host) if src.Metadata.Scheme != source.ImageScheme { diff --git a/cmd/power_user_tasks.go b/cmd/power_user_tasks.go index df543b9c984..8c06e15393d 100644 --- a/cmd/power_user_tasks.go +++ b/cmd/power_user_tasks.go @@ -10,7 +10,7 @@ import ( "github.com/anchore/syft/syft/source" ) -type powerUserTask func(*poweruser.JSONDocumentConfig, source.Source) error +type powerUserTask func(*poweruser.JSONDocumentConfig, *source.Source) error func powerUserTasks() ([]powerUserTask, error) { var tasks []powerUserTask @@ -42,7 +42,7 @@ func catalogPackagesTask() (powerUserTask, error) { return nil, nil } - task := func(results *poweruser.JSONDocumentConfig, src source.Source) error { + task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error { packageCatalog, theDistro, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt) if err != nil { return err @@ -64,7 +64,7 @@ func catalogFileMetadataTask() (powerUserTask, error) { metadataCataloger := file.NewMetadataCataloger() - task := func(results *poweruser.JSONDocumentConfig, src source.Source) error { + task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error { resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt) if err != nil { return err @@ -110,7 +110,7 @@ func catalogFileDigestsTask() (powerUserTask, error) { return nil, err } - task := func(results *poweruser.JSONDocumentConfig, src source.Source) error { + task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error { resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt) if err != nil { return err @@ -142,7 +142,7 @@ func catalogSecretsTask() (powerUserTask, error) { return nil, err } - task := func(results *poweruser.JSONDocumentConfig, src source.Source) error { + task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error { resolver, err := src.FileResolver(appConfig.Secrets.Cataloger.ScopeOpt) if err != nil { return err @@ -170,7 +170,7 @@ func catalogFileClassificationsTask() (powerUserTask, error) { return nil, err } - task := func(results *poweruser.JSONDocumentConfig, src source.Source) error { + task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error { resolver, err := src.FileResolver(appConfig.FileClassification.Cataloger.ScopeOpt) if err != nil { return err @@ -197,7 +197,7 @@ func catalogContentsTask() (powerUserTask, error) { return nil, err } - task := func(results *poweruser.JSONDocumentConfig, src source.Source) error { + task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error { resolver, err := src.FileResolver(appConfig.FileContents.Cataloger.ScopeOpt) if err != nil { return err diff --git a/syft/lib.go b/syft/lib.go index ed9a86ef611..4b76a9900f2 100644 --- a/syft/lib.go +++ b/syft/lib.go @@ -32,7 +32,7 @@ import ( // CatalogPackages takes an inventory of packages from the given image from a particular perspective // (e.g. squashed source, all-layers source). Returns the discovered set of packages, the identified Linux // distribution, and the source object used to wrap the data source. -func CatalogPackages(src source.Source, scope source.Scope) (*pkg.Catalog, *distro.Distro, error) { +func CatalogPackages(src *source.Source, scope source.Scope) (*pkg.Catalog, *distro.Distro, error) { resolver, err := src.FileResolver(scope) if err != nil { return nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err) diff --git a/syft/source/directory_resolver.go b/syft/source/directory_resolver.go index 85e508f7465..b34f2a96416 100644 --- a/syft/source/directory_resolver.go +++ b/syft/source/directory_resolver.go @@ -41,19 +41,12 @@ type directoryResolver struct { errPaths map[string]error } -func newDirectoryResolver(fileTree *filetree.FileTree, root string, pathFilters ...pathFilterFn) (*directoryResolver, error) { +func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryResolver, error) { cwd, err := os.Getwd() if err != nil { return nil, fmt.Errorf("could not create directory resolver: %w", err) } - var resolverFileTree *filetree.FileTree - if fileTree == nil { - resolverFileTree = filetree.NewFileTree() - } else { - resolverFileTree = fileTree - } - if pathFilters == nil { pathFilters = []pathFilterFn{isUnixSystemRuntimePath} } @@ -61,32 +54,15 @@ func newDirectoryResolver(fileTree *filetree.FileTree, root string, pathFilters resolver := directoryResolver{ path: root, cwd: cwd, - fileTree: resolverFileTree, + fileTree: filetree.NewFileTree(), infos: make(map[file.ID]os.FileInfo), pathFilterFns: pathFilters, errPaths: make(map[string]error), } - if fileTree != nil { - resolver.CopyFromTree() - } - return &resolver, indexAllRoots(root, resolver.indexTree) } -func (r *directoryResolver) CopyFromTree() { - for _, ref := range r.fileTree.AllFiles() { - if _, ok := r.infos[ref.ID()]; !ok { - info, err := os.Stat(string(ref.RealPath)) - if err != nil { - log.Errorf("unable to copy path=%q: %+v", ref.RealPath, err) - continue - } - r.infos[ref.ID()] = info - } - } -} - func (r *directoryResolver) indexTree(root string, stager *progress.Stage) ([]string, error) { log.Infof("indexing filesystem path=%q", root) var err error diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index 8ae15f12ad8..9d85f21f5db 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/filetree" "github.com/stretchr/testify/assert" "github.com/wagoodman/go-progress" ) @@ -66,7 +65,7 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver(nil, c.root) + resolver, err := newDirectoryResolver(c.root) assert.NoError(t, err) hasPath := resolver.HasPath(c.input) @@ -122,7 +121,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver(nil, "./test-fixtures") + resolver, err := newDirectoryResolver("./test-fixtures") assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input...) assert.NoError(t, err) @@ -134,59 +133,8 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { } } -func TestDirectoryResolver_SharedTreeMultipleFilesByPath(t *testing.T) { - cases := []struct { - name string - input []string - refCount int - }{ - { - name: "finds multiple files first resolver", - input: []string{"test-fixtures/image-symlinks/file-1.txt", "test-fixtures/image-symlinks/file-2.txt"}, - refCount: 2, - }, - { - name: "skips non-existing files", - input: []string{"test-fixtures/image-symlinks/bogus.txt", "test-fixtures/image-symlinks/file-1.txt"}, - refCount: 1, - }, - { - name: "does not return anything for non-existing directories", - input: []string{"test-fixtures/non-existing/bogus.txt", "test-fixtures/non-existing/file-1.txt"}, - refCount: 0, - }, - { - name: "find file second resolver", - input: []string{"test-fixtures/path-detected/.vimrc"}, - refCount: 1, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - fileTree := filetree.NewFileTree() - first_resolver, err := newDirectoryResolver(fileTree, "./test-fixtures/image-symlinks") - assert.NoError(t, err) - second_resolver, err := newDirectoryResolver(fileTree, "./test-fixtures/path-detected") - assert.NoError(t, err) - - first_refs, err := first_resolver.FilesByPath(c.input...) - assert.NoError(t, err) - second_refs, err := second_resolver.FilesByPath(c.input...) - assert.NoError(t, err) - - if len(first_refs) != len(second_refs) { - t.Errorf("unsynced number of refs: %d != %d", len(first_refs), len(second_refs)) - } - - if len(first_refs) != c.refCount { - t.Errorf("unexpected number of refs: %d != %d", len(first_refs), c.refCount) - } - }) - } -} - func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { - resolver, err := newDirectoryResolver(nil, "./test-fixtures") + resolver, err := newDirectoryResolver("./test-fixtures") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/image-symlinks/file*") assert.NoError(t, err) @@ -195,7 +143,7 @@ func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { } func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { - resolver, err := newDirectoryResolver(nil, "./test-fixtures/image-symlinks") + resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) @@ -203,7 +151,7 @@ func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { } func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) { - resolver, err := newDirectoryResolver(nil, "./test-fixtures") + resolver, err := newDirectoryResolver("./test-fixtures") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt") assert.NoError(t, err) @@ -214,7 +162,7 @@ func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) { func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { // let's make certain that "dev/place" is not ignored, since it is not "/dev/place" - resolver, err := newDirectoryResolver(nil, "test-fixtures/system_paths/target") + resolver, err := newDirectoryResolver("test-fixtures/system_paths/target") assert.NoError(t, err) // ensure the correct filter function is wired up by default expectedFn := reflect.ValueOf(isUnixSystemRuntimePath) @@ -250,7 +198,7 @@ func TestDirectoryResolverUsesPathFilterFunction(t *testing.T) { return strings.Contains(s, "dev/place") || strings.Contains(s, "proc/place") || strings.Contains(s, "sys/place") } - resolver, err := newDirectoryResolver(nil, "test-fixtures/system_paths/target", filter) + resolver, err := newDirectoryResolver("test-fixtures/system_paths/target", filter) assert.NoError(t, err) // ensure the correct filter function is wired up by default @@ -312,7 +260,7 @@ func Test_isUnixSystemRuntimePath(t *testing.T) { func Test_directoryResolver_index(t *testing.T) { // note: this test is testing the effects from newDirectoryResolver, indexTree, and addPathToIndex - r, err := newDirectoryResolver(nil, "test-fixtures/system_paths/target") + r, err := newDirectoryResolver("test-fixtures/system_paths/target") if err != nil { t.Fatalf("unable to get indexed dir resolver: %+v", err) } diff --git a/syft/source/source.go b/syft/source/source.go index d35f0022803..951de5549d3 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -10,7 +10,6 @@ import ( "sync" "github.com/anchore/stereoscope" - "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" "github.com/spf13/afero" ) @@ -18,61 +17,60 @@ import ( // Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used // in cataloging (based on the data source and configuration) type Source struct { - Image *image.Image // the image object to be cataloged (image only) - Tree *filetree.FileTree - Metadata Metadata - Mutex *sync.Mutex + Image *image.Image // the image object to be cataloged (image only) + DirectoryResolver *directoryResolver + Metadata Metadata + Mutex *sync.Mutex } type sourceDetector func(string) (image.Source, string, error) // New produces a Source based on userInput like dir: or image:tag -func New(userInput string, registryOptions *image.RegistryOptions) (Source, func(), error) { +func New(userInput string, registryOptions *image.RegistryOptions) (*Source, func(), error) { fs := afero.NewOsFs() parsedScheme, imageSource, location, err := detectScheme(fs, image.DetectSource, userInput) if err != nil { - return Source{}, func() {}, fmt.Errorf("unable to parse input=%q: %w", userInput, err) + return &Source{}, func() {}, fmt.Errorf("unable to parse input=%q: %w", userInput, err) } switch parsedScheme { case DirectoryScheme: fileMeta, err := fs.Stat(location) if err != nil { - return Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) + return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) } if !fileMeta.IsDir() { - return Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) + return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) } s, err := NewFromDirectory(location) if err != nil { - return Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) + return &Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) } - return s, func() {}, nil + return &s, func() {}, nil case ImageScheme: img, err := stereoscope.GetImageFromSource(location, imageSource, registryOptions) cleanup := stereoscope.Cleanup if err != nil || img == nil { - return Source{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err) + return &Source{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err) } s, err := NewFromImage(img, location) if err != nil { - return Source{}, cleanup, fmt.Errorf("could not populate source with image: %w", err) + return &Source{}, cleanup, fmt.Errorf("could not populate source with image: %w", err) } - return s, cleanup, nil + return &s, cleanup, nil } - return Source{}, func() {}, fmt.Errorf("unable to process input for scanning: '%s'", userInput) + return &Source{}, func() {}, fmt.Errorf("unable to process input for scanning: '%s'", userInput) } // NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively. func NewFromDirectory(path string) (Source, error) { return Source{ - Tree: filetree.NewFileTree(), Mutex: &sync.Mutex{}, Metadata: Metadata{ Scheme: DirectoryScheme, @@ -97,12 +95,19 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) { }, nil } -func (s Source) FileResolver(scope Scope) (FileResolver, error) { +func (s *Source) FileResolver(scope Scope) (FileResolver, error) { switch s.Metadata.Scheme { case DirectoryScheme: s.Mutex.Lock() defer s.Mutex.Unlock() - return newDirectoryResolver(s.Tree, s.Metadata.Path) + if s.DirectoryResolver == nil { + directoryResolver, err := newDirectoryResolver(s.Metadata.Path) + if err != nil { + return nil, err + } + s.DirectoryResolver = directoryResolver + } + return s.DirectoryResolver, nil case ImageScheme: switch scope { case SquashedScope: diff --git a/syft/source/source_test.go b/syft/source/source_test.go index 39be62fc385..fe096bf4322 100644 --- a/syft/source/source_test.go +++ b/syft/source/source_test.go @@ -89,6 +89,68 @@ func TestNewFromDirectory(t *testing.T) { } } +func TestNewFromDirectoryShared(t *testing.T) { + testCases := []struct { + desc string + input string + expString string + notExist string + inputPaths []string + expRefs int + }{ + { + desc: "path detected", + input: "test-fixtures", + notExist: "foobar/", + inputPaths: []string{"test-fixtures/path-detected/.vimrc"}, + expRefs: 1, + }, + { + desc: "directory ignored", + input: "test-fixtures", + notExist: "foobar/", + inputPaths: []string{"test-fixtures/path-detected"}, + expRefs: 0, + }, + { + desc: "no files-by-path detected", + input: "test-fixtures", + notExist: "foobar/", + inputPaths: []string{"test-fixtures/no-path-detected"}, + expRefs: 0, + }, + } + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + src, err := NewFromDirectory(test.input) + + if err != nil { + t.Errorf("could not create NewDirScope: %+v", err) + } + if src.Metadata.Path != test.input { + t.Errorf("mismatched stringer: '%s' != '%s'", src.Metadata.Path, test.input) + } + + _, err = src.FileResolver(SquashedScope) + assert.NoError(t, err) + + src.Metadata.Path = test.notExist + resolver2, err := src.FileResolver(SquashedScope) + assert.NoError(t, err) + + refs, err := resolver2.FilesByPath(test.inputPaths...) + if err != nil { + t.Errorf("FilesByPath call produced an error: %+v", err) + } + if len(refs) != test.expRefs { + t.Errorf("unexpected number of refs returned: %d != %d", len(refs), test.expRefs) + + } + + }) + } +} + func TestFilesByPathDoesNotExist(t *testing.T) { testCases := []struct { desc string diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go index 56cbc7b09c1..bd6b7435c73 100644 --- a/test/integration/utils_test.go +++ b/test/integration/utils_test.go @@ -10,7 +10,7 @@ import ( "github.com/anchore/syft/syft/source" ) -func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, *distro.Distro, source.Source) { +func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, *distro.Distro, *source.Source) { imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName) tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName) @@ -28,7 +28,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, * return pkgCatalog, actualDistro, theSource } -func catalogDirectory(t *testing.T, dir string) (*pkg.Catalog, *distro.Distro, source.Source) { +func catalogDirectory(t *testing.T, dir string) (*pkg.Catalog, *distro.Distro, *source.Source) { theSource, cleanupSource, err := source.New("dir:"+dir, nil) t.Cleanup(cleanupSource) if err != nil { From d1f97c681bd892d0ba3ac52b406ef12423fb2f08 Mon Sep 17 00:00:00 2001 From: houdini91 Date: Sun, 5 Sep 2021 09:14:39 +0300 Subject: [PATCH 6/6] Fix Lint Signed-off-by: houdini91 --- internal/err_helper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/err_helper.go b/internal/err_helper.go index 6bf0cd80c8d..dad5f9c3dcd 100644 --- a/internal/err_helper.go +++ b/internal/err_helper.go @@ -30,9 +30,9 @@ func IsErrPath(err error) bool { } func IsErrPathPermission(err error) bool { - path_err, ok := err.(ErrPath) + pathErr, ok := err.(ErrPath) if ok { - return os.IsPermission(path_err.Err) + return os.IsPermission(pathErr.Err) } return ok }