Skip to content
This repository has been archived by the owner on Jun 21, 2022. It is now read-only.

fix(analyzer): improve performance #314

Merged
merged 29 commits into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b1d66aa
feat(walker) new file opener architecture
masahiro331 Oct 23, 2021
b266d7a
fix filepath bugs
masahiro331 Oct 23, 2021
abe0b9b
refactor(walk) change error message
masahiro331 Oct 23, 2021
330d450
refactor(walk) add comment
masahiro331 Oct 23, 2021
c082605
refactor(walk) change cleaner
masahiro331 Oct 23, 2021
052c990
change(walk) do not close file
masahiro331 Oct 23, 2021
c19ef32
fix(analyzer) contents read closer close in goroutine
masahiro331 Oct 23, 2021
46939a6
fix(analyzer) wait for goroutine to complete of cleaner
masahiro331 Oct 23, 2021
7c8bed0
chore(image) fix indent
masahiro331 Oct 23, 2021
6c7b008
refactor(walk) change N name
masahiro331 Oct 23, 2021
824a36b
refactor(walk) cache clean
masahiro331 Oct 23, 2021
467f0b3
refactor(analyzer) defer cleaner
masahiro331 Oct 23, 2021
ec8585f
refactor(analyzer) resolve gosimple warning
masahiro331 Oct 23, 2021
8f9f08b
change(walker) threshold size
masahiro331 Oct 23, 2021
263d08b
fix review
masahiro331 Dec 19, 2021
ad858d8
change(cloudformation) content to content reader
masahiro331 Dec 19, 2021
7f2bf58
fix(walker) clean for all temporary data after all analyzes
masahiro331 Dec 19, 2021
0fa0f71
Revert "fix(walker) clean for all temporary data after all analyzes"
knqyf263 Dec 23, 2021
8246fd8
refactor(analyzer): define tarFile
knqyf263 Dec 23, 2021
62b2ee4
refactor: rename
knqyf263 Dec 23, 2021
b69bfed
fix(go/binary): remove threshold
knqyf263 Dec 23, 2021
298ff82
feat(jar): pass file size
knqyf263 Dec 23, 2021
f1b20c5
feat: use ReadSeekCloserAt
knqyf263 Dec 23, 2021
012a31a
chore(mod): update go-dep-parser
knqyf263 Dec 23, 2021
cc29de2
Merge branch 'main' into feat/walk_process
knqyf263 Dec 23, 2021
152e7c2
resolve conflicts
knqyf263 Dec 23, 2021
1ce6627
refactor
knqyf263 Dec 23, 2021
0a98587
fix(rpm): close a temp file before the DB open
knqyf263 Dec 23, 2021
7713eea
test: fix
knqyf263 Dec 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
aos "github.com/aquasecurity/fanal/analyzer/os"
"github.com/aquasecurity/fanal/log"
"github.com/aquasecurity/fanal/types"
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
)

var (
Expand All @@ -30,7 +31,8 @@ var (
type AnalysisTarget struct {
Dir string
FilePath string
Content []byte
Info os.FileInfo
Content dio.ReadSeekerAt
}

type analyzer interface {
Expand All @@ -55,7 +57,7 @@ func RegisterConfigAnalyzer(analyzer configAnalyzer) {
configAnalyzers[analyzer.Type()] = analyzer
}

type Opener func() ([]byte, error)
type Opener func() (dio.ReadSeekCloserAt, error)

type AnalysisResult struct {
m sync.Mutex
Expand Down Expand Up @@ -122,9 +124,7 @@ func (r *AnalysisResult) Merge(new *AnalysisResult) {
r.Applications = append(r.Applications, new.Applications...)
}

for _, m := range new.Configs {
r.Configs = append(r.Configs, m)
}
r.Configs = append(r.Configs, new.Configs...)

r.SystemInstalledFiles = append(r.SystemInstalledFiles, new.SystemInstalledFiles...)
}
Expand Down Expand Up @@ -195,9 +195,9 @@ func (a Analyzer) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, limit *se
if !d.Required(strings.TrimLeft(filePath, "/"), info) {
continue
}
b, err := opener()
rc, err := opener()
if err != nil {
return xerrors.Errorf("unable to open a file (%s): %w", filePath, err)
return xerrors.Errorf("unable to open %s: %w", filePath, err)
}

if err = limit.Acquire(ctx, 1); err != nil {
Expand All @@ -208,15 +208,17 @@ func (a Analyzer) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, limit *se
go func(a analyzer, target AnalysisTarget) {
defer limit.Release(1)
defer wg.Done()
defer rc.Close()

ret, err := a.Analyze(ctx, target)
if err != nil && !xerrors.Is(err, aos.AnalyzeOSError) {
log.Logger.Debugf("Analysis error: %s", err)
return
}
result.Merge(ret)
}(d, AnalysisTarget{Dir: dir, FilePath: filePath, Content: b})
}(d, AnalysisTarget{Dir: dir, FilePath: filePath, Info: info, Content: rc})
}

return nil
}

Expand Down
17 changes: 10 additions & 7 deletions analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
aos "github.com/aquasecurity/fanal/analyzer/os"
_ "github.com/aquasecurity/fanal/hook/all"
"github.com/aquasecurity/fanal/types"
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
)

type mockConfigAnalyzer struct{}
Expand Down Expand Up @@ -349,7 +350,7 @@ func TestAnalyzeFile(t *testing.T) {
filePath: "/lib/apk/db/installed",
testFilePath: "testdata/error",
},
wantErr: "unable to open a file (/lib/apk/db/installed)",
wantErr: "unable to open /lib/apk/db/installed",
},
}
for _, tt := range tests {
Expand All @@ -364,12 +365,14 @@ func TestAnalyzeFile(t *testing.T) {
require.NoError(t, err)

ctx := context.Background()
err = a.AnalyzeFile(ctx, &wg, limit, got, "", tt.args.filePath, info, func() ([]byte, error) {
if tt.args.testFilePath == "testdata/error" {
return nil, xerrors.New("error")
}
return os.ReadFile(tt.args.testFilePath)
})
err = a.AnalyzeFile(ctx, &wg, limit, got, "", tt.args.filePath, info,
func() (dio.ReadSeekCloserAt, error) {
if tt.args.testFilePath == "testdata/error" {
return nil, xerrors.New("error")
}
return os.Open(tt.args.testFilePath)
},
)

wg.Wait()
if tt.wantErr != "" {
Expand Down
8 changes: 7 additions & 1 deletion analyzer/config/cloudformation/cloudformation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package cloudformation

import (
"context"
"io"
"os"
"path/filepath"
"regexp"

"github.com/aquasecurity/fanal/analyzer"
"github.com/aquasecurity/fanal/types"
"golang.org/x/xerrors"
)

const version = 1
Expand All @@ -29,8 +31,12 @@ func NewConfigAnalyzer() ConfigAnalyzer {

// Analyze returns a results of CloudFormation file
func (a ConfigAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisTarget) (*analyzer.AnalysisResult, error) {
content, err := io.ReadAll(target.Content)
if err != nil {
return nil, xerrors.Errorf("failed to read the CloudFormation file: %w", err)
}

if looksLikeCloudFormation(target.Content) {
if looksLikeCloudFormation(content) {
return &analyzer.AnalysisResult{
Configs: []types.Config{
{
Expand Down
25 changes: 14 additions & 11 deletions analyzer/config/cloudformation/cloudformation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package cloudformation

import (
"context"
"strings"
"testing"

"github.com/aquasecurity/fanal/analyzer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/aquasecurity/fanal/analyzer"
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
)

func TestConfigAnalyzer_Required(t *testing.T) {
Expand Down Expand Up @@ -48,25 +51,25 @@ func TestConfigAnalyzer_Required(t *testing.T) {
func TestConfigAnalyzer_Analyzed(t *testing.T) {
tests := []struct {
name string
content string
content dio.ReadSeekerAt
filePath string
want int
}{
{
name: "CloudFormation yaml",
content: `---
content: strings.NewReader(`---
Parameters:
SomeParameter:
Resources:
SomeResource:
Type: Something
`,
`),
filePath: "main.yaml",
want: 1,
},
{
name: "Cloudformation JSON",
content: `{
content: strings.NewReader(`{
"Parameters": {
"SomeParameter": null
},
Expand All @@ -75,13 +78,13 @@ Resources:
"Type": "Something"
}
}
}`,
}`),
filePath: "main.json",
want: 1,
},
{
name: "non CloudFormation yaml",
content: `---
content: strings.NewReader(`---
apiVersion: apps/v1
kind: Deployment
metadata:
Expand All @@ -101,20 +104,20 @@ spec:
image: nginx:1.14.2
ports:
- containerPort: 80
`,
`),
filePath: "k8s.yaml",
want: 0,
},
{
name: "non CloudFormation json",
content: `{
content: strings.NewReader(`{
"foo": [
"baaaaa",
"bar",
"baa"
]
}
`,
`),
filePath: "random.yaml",
want: 0,
},
Expand All @@ -124,7 +127,7 @@ spec:
a := ConfigAnalyzer{}
got, err := a.Analyze(context.Background(), analyzer.AnalysisTarget{
FilePath: tt.filePath,
Content: []byte(tt.content),
Content: tt.content,
})
require.NoError(t, err)
assert.Len(t, got.Configs, tt.want)
Expand Down
7 changes: 4 additions & 3 deletions analyzer/config/docker/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package docker_test

import (
"context"
"io/ioutil"
"os"
"regexp"
"testing"

Expand Down Expand Up @@ -158,14 +158,15 @@ func Test_dockerConfigAnalyzer_Analyze(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := ioutil.ReadFile(tt.inputFile)
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()

a := docker.NewConfigAnalyzer(nil)
ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisTarget{
FilePath: tt.inputFile,
Content: b,
Content: f,
})

if tt.wantErr != "" {
Expand Down
10 changes: 8 additions & 2 deletions analyzer/config/hcl/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hcl

import (
"context"
"io"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -52,13 +53,18 @@ func (a ConfigAnalyzer) analyze(target analyzer.AnalysisTarget) (interface{}, er
var errs error
var parsed interface{}

if err := hcl2.Unmarshal(target.Content, &parsed); err != nil {
content, err := io.ReadAll(target.Content)
if err != nil {
return nil, xerrors.Errorf("failed to read the HCL2 file: %w", err)
}

if err := hcl2.Unmarshal(content, &parsed); err != nil {
errs = multierror.Append(errs, xerrors.Errorf("unable to parse HCL2 (%s): %w", target.FilePath, err))
} else {
return parsed, nil
}

if err := hcl.Unmarshal(target.Content, &parsed); err != nil {
if err := hcl.Unmarshal(content, &parsed); err != nil {
errs = multierror.Append(errs, xerrors.Errorf("unable to parse HCL1 (%s): %w", target.FilePath, err))
} else {
return parsed, nil
Expand Down
5 changes: 3 additions & 2 deletions analyzer/config/hcl/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,16 @@ func TestConfigAnalyzer_Analyze(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := os.ReadFile(tt.inputFile)
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()

a := hcl.NewConfigAnalyzer(nil)
require.NoError(t, err)
ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisTarget{
FilePath: tt.inputFile,
Content: b,
Content: f,
})

if tt.wantErr != "" {
Expand Down
4 changes: 2 additions & 2 deletions analyzer/config/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer {

func (a ConfigAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisTarget) (*analyzer.AnalysisResult, error) {
var parsed interface{}
if err := json.Unmarshal(target.Content, &parsed); err != nil {
return nil, xerrors.Errorf("unable to parse JSON (%s): %w", target.FilePath, err)
if err := json.NewDecoder(target.Content).Decode(&parsed); err != nil {
return nil, xerrors.Errorf("unable to decode JSON (%s): %w", target.FilePath, err)
}

return &analyzer.AnalysisResult{
Expand Down
9 changes: 5 additions & 4 deletions analyzer/config/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package json_test

import (
"context"
"io/ioutil"
"os"
"regexp"
"testing"

Expand Down Expand Up @@ -122,21 +122,22 @@ func Test_jsonConfigAnalyzer_Analyze(t *testing.T) {
policyPaths: []string{"../testdata/kubernetes.rego"},
},
inputFile: "testdata/broken.json",
wantErr: "unable to parse JSON",
wantErr: "unable to decode JSON",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := ioutil.ReadFile(tt.inputFile)
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()

s := json.NewConfigAnalyzer(nil)

ctx := context.Background()
got, err := s.Analyze(ctx, analyzer.AnalysisTarget{
FilePath: tt.inputFile,
Content: b,
Content: f,
})

if tt.wantErr != "" {
Expand Down
6 changes: 3 additions & 3 deletions analyzer/config/toml/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"path/filepath"
"regexp"

"github.com/BurntSushi/toml"
"golang.org/x/xerrors"

"github.com/BurntSushi/toml"
"github.com/aquasecurity/fanal/analyzer"
"github.com/aquasecurity/fanal/types"
)
Expand All @@ -29,8 +29,8 @@ func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer {

func (a ConfigAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisTarget) (*analyzer.AnalysisResult, error) {
var parsed interface{}
if err := toml.Unmarshal(target.Content, &parsed); err != nil {
return nil, xerrors.Errorf("unable to parse TOML (%s): %w", target.FilePath, err)
if _, err := toml.NewDecoder(target.Content).Decode(&parsed); err != nil {
return nil, xerrors.Errorf("unable to decode TOML (%s): %w", target.FilePath, err)
}

return &analyzer.AnalysisResult{
Expand Down
Loading