diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 2029ee79fa..74f9bf7a15 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -5,7 +5,6 @@ import ( "context" "io" "os" - "time" "github.com/aquasecurity/fanal/utils" @@ -33,6 +32,10 @@ var ( ErrNoPkgsDetected = xerrors.New("No packages detected") ) +type AnalyzerConfig struct { + Extractor extractor.Extractor +} + type OSAnalyzer interface { Analyze(extractor.FileMap) (OS, error) RequiredFiles() []string @@ -113,28 +116,19 @@ func RequiredFilenames() []string { return filenames } -func Analyze(ctx context.Context, imageName string, opts ...types.DockerOption) (fileMap extractor.FileMap, err error) { - // default docker option - opt := types.DockerOption{ - Timeout: 600 * time.Second, - SkipPing: true, - } - if len(opts) > 0 { - opt = opts[0] - } - - e := docker.NewDockerExtractor(opt) - r, err := e.SaveLocalImage(ctx, imageName) +// TODO: Remove opts as they're no longer needed +func (ac AnalyzerConfig) Analyze(ctx context.Context, imageName string, opts ...types.DockerOption) (fileMap extractor.FileMap, err error) { + r, err := ac.Extractor.SaveLocalImage(ctx, imageName) if err != nil { // when no docker daemon is installed or no image exists in the local machine - fileMap, err = e.Extract(ctx, imageName, RequiredFilenames()) + fileMap, err = ac.Extractor.Extract(ctx, imageName, RequiredFilenames()) if err != nil { return nil, xerrors.Errorf("failed to extract files: %w", err) } return fileMap, nil } - fileMap, err = e.ExtractFromFile(ctx, r, RequiredFilenames()) + fileMap, err = ac.Extractor.ExtractFromFile(ctx, r, RequiredFilenames()) if err != nil { return nil, xerrors.Errorf("failed to extract files from saved tar: %w", err) } diff --git a/analyzer/analyzer_test.go b/analyzer/analyzer_test.go new file mode 100644 index 0000000000..ae9641b465 --- /dev/null +++ b/analyzer/analyzer_test.go @@ -0,0 +1,45 @@ +package analyzer + +import ( + "context" + "io" + "testing" + + "github.com/aquasecurity/fanal/extractor" + + "github.com/stretchr/testify/assert" +) + +type mockDockerExtractor struct { + saveLocalImage func(ctx context.Context, imageName string) (io.Reader, error) + extractFromFile func(ctx context.Context, r io.Reader, filenames []string) (extractor.FileMap, error) +} + +func (mde mockDockerExtractor) Extract(ctx context.Context, imageName string, filenames []string) (extractor.FileMap, error) { + panic("implement me") +} + +func (mde mockDockerExtractor) ExtractFromFile(ctx context.Context, r io.Reader, filenames []string) (extractor.FileMap, error) { + if mde.extractFromFile != nil { + return mde.extractFromFile(ctx, r, filenames) + } + return extractor.FileMap{}, nil +} + +func (mde mockDockerExtractor) SaveLocalImage(ctx context.Context, imageName string) (io.Reader, error) { + if mde.saveLocalImage != nil { + return mde.saveLocalImage(ctx, imageName) + } + return nil, nil +} + +func (mde mockDockerExtractor) ExtractFiles(layer io.Reader, filenames []string) (extractor.FileMap, extractor.OPQDirs, error) { + panic("implement me") +} + +func TestAnalyze(t *testing.T) { + ac := AnalyzerConfig{Extractor: mockDockerExtractor{}} + fm, err := ac.Analyze(context.TODO(), "foo") + assert.NoError(t, err) + assert.NotNil(t, fm) +} diff --git a/extractor/docker/docker.go b/extractor/docker/docker.go index 4c47438ceb..f6596c075a 100644 --- a/extractor/docker/docker.go +++ b/extractor/docker/docker.go @@ -60,19 +60,17 @@ type layer struct { Content io.ReadCloser } -type opqDirs []string - type DockerExtractor struct { Option types.DockerOption } -func NewDockerExtractor(option types.DockerOption) DockerExtractor { +func NewDockerExtractor(option types.DockerOption) extractor.Extractor { RegisterRegistry(&gcr.GCR{}) RegisterRegistry(&ecr.ECR{}) return DockerExtractor{Option: option} } -func applyLayers(layerPaths []string, filesInLayers map[string]extractor.FileMap, opqInLayers map[string]opqDirs) (extractor.FileMap, error) { +func applyLayers(layerPaths []string, filesInLayers map[string]extractor.FileMap, opqInLayers map[string]extractor.OPQDirs) (extractor.FileMap, error) { sep := "/" nestedMap := nested.Nested{} for _, layerPath := range layerPaths { @@ -190,7 +188,8 @@ func (d DockerExtractor) Extract(ctx context.Context, imageName string, filename ch := make(chan layer) errCh := make(chan error) - layerIDs := []string{} + var layerIDs []string + for _, ref := range m.Manifest.Layers { layerIDs = append(layerIDs, string(ref.Digest)) go func(d digest.Digest) { @@ -203,6 +202,7 @@ func (d DockerExtractor) Extract(ctx context.Context, imageName string, filename errCh <- xerrors.Errorf("failed to download the layer(%s): %w", d, err) return } + rc, err = cache.Set(string(d), rc) if err != nil { log.Print(err) @@ -218,7 +218,7 @@ func (d DockerExtractor) Extract(ctx context.Context, imageName string, filename } filesInLayers := make(map[string]extractor.FileMap) - opqInLayers := make(map[string]opqDirs) + opqInLayers := make(map[string]extractor.OPQDirs) for i := 0; i < len(m.Manifest.Layers); i++ { var l layer select { @@ -262,7 +262,7 @@ func (d DockerExtractor) Extract(ctx context.Context, imageName string, filename func (d DockerExtractor) ExtractFromFile(ctx context.Context, r io.Reader, filenames []string) (extractor.FileMap, error) { manifests := make([]manifest, 0) filesInLayers := map[string]extractor.FileMap{} - opqInLayers := make(map[string]opqDirs) + opqInLayers := make(map[string]extractor.OPQDirs) tarFiles := make(map[string][]byte) @@ -330,9 +330,9 @@ func (d DockerExtractor) ExtractFromFile(ctx context.Context, r io.Reader, filen return fileMap, nil } -func (d DockerExtractor) ExtractFiles(layer io.Reader, filenames []string) (extractor.FileMap, opqDirs, error) { +func (d DockerExtractor) ExtractFiles(layer io.Reader, filenames []string) (extractor.FileMap, extractor.OPQDirs, error) { data := make(map[string][]byte) - opqDirs := opqDirs{} + opqDirs := extractor.OPQDirs{} tr := tar.NewReader(layer) for { diff --git a/extractor/docker/docker_test.go b/extractor/docker/docker_test.go index e625693185..6d351676ef 100644 --- a/extractor/docker/docker_test.go +++ b/extractor/docker/docker_test.go @@ -122,7 +122,7 @@ func TestExtractFiles(t *testing.T) { file string // Test input file filenames []string // Target files FileMap extractor.FileMap // Expected output - opqDirs opqDirs // Expected output + opqDirs OPQDirs // Expected output err error // Expected error to occur }{ { @@ -167,7 +167,7 @@ func TestExtractFiles(t *testing.T) { t.Errorf("err: got %v, want %v", v.err, err) } if !reflect.DeepEqual(opqDirs, v.opqDirs) { - t.Errorf("opqDirs: got %v, want %v", opqDirs, v.opqDirs) + t.Errorf("OPQDirs: got %v, want %v", opqDirs, v.opqDirs) } if !reflect.DeepEqual(fm, v.FileMap) { t.Errorf("FilesMap: got %v, want %v", fm, v.FileMap) diff --git a/extractor/extractor.go b/extractor/extractor.go index 56579b55a4..eb5ffd8267 100644 --- a/extractor/extractor.go +++ b/extractor/extractor.go @@ -6,8 +6,11 @@ import ( ) type FileMap map[string][]byte +type OPQDirs []string type Extractor interface { Extract(ctx context.Context, imageName string, filenames []string) (FileMap, error) ExtractFromFile(ctx context.Context, r io.Reader, filenames []string) (FileMap, error) + SaveLocalImage(ctx context.Context, imageName string) (io.Reader, error) + ExtractFiles(layer io.Reader, filenames []string) (FileMap, OPQDirs, error) }