Skip to content

Commit

Permalink
adjust multi level configuration + faster tests
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed May 16, 2024
1 parent e5ed88d commit ca5d50c
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 93 deletions.
45 changes: 28 additions & 17 deletions cmd/grype/cli/options/datasources.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,62 @@
package options

import (
"time"

"github.com/anchore/grype/grype/matcher/java"
)

const (
defaultMavenBaseURL = "https://search.maven.org/solrsearch/select"
defaultAbortAfter = "10m"
defaultAbortAfter = 10 * time.Minute
)

type externalSources struct {
Enable bool `yaml:"enable" json:"enable" mapstructure:"enable"`
AbortAfter string `yaml:"abort-after" json:"abortAfter" mapstructure:"abort-after"`
Maven maven `yaml:"maven" json:"maven" mapstructure:"maven"`
Enable bool `yaml:"enable" json:"enable" mapstructure:"enable"`
AbortAfter *time.Duration `yaml:"abort-after" json:"abortAfter" mapstructure:"abort-after"`
Maven maven `yaml:"maven" json:"maven" mapstructure:"maven"`
}

type maven struct {
SearchUpstreamBySha1 bool `yaml:"search-upstream" json:"searchUpstreamBySha1" mapstructure:"search-maven-upstream"`
BaseURL string `yaml:"base-url" json:"baseUrl" mapstructure:"base-url"`
AbortAfter *string `yaml:"abort-after" json:"abortAfter" mapstructure:"abort-after"`
SearchUpstreamBySha1 bool `yaml:"search-upstream" json:"searchUpstreamBySha1" mapstructure:"search-maven-upstream"`
BaseURL string `yaml:"base-url" json:"baseUrl" mapstructure:"base-url"`
AbortAfter *time.Duration `yaml:"abort-after" json:"abortAfter" mapstructure:"abort-after"`
}

func defaultExternalSources() externalSources {
return externalSources{
AbortAfter: defaultAbortAfter,
Maven: maven{
SearchUpstreamBySha1: true,
BaseURL: defaultMavenBaseURL,
},
}
}

func (cfg externalSources) ToJavaMatcherConfig() java.ExternalSearchConfig {
func (cfg *externalSources) PostLoad() error {
// always respect if global config is disabled
smu := cfg.Maven.SearchUpstreamBySha1
if !cfg.Enable {
smu = cfg.Enable
cfg.Maven.SearchUpstreamBySha1 = false
}

abortAfter := cfg.AbortAfter
if cfg.Maven.AbortAfter != nil {
abortAfter = *cfg.Maven.AbortAfter
}
cfg.Maven.AbortAfter = multiLevelOption[time.Duration](defaultAbortAfter, cfg.AbortAfter, cfg.Maven.AbortAfter)

return nil
}

func (cfg externalSources) ToJavaMatcherConfig() java.ExternalSearchConfig {
return java.ExternalSearchConfig{
SearchMavenUpstream: smu,
SearchMavenUpstream: cfg.Maven.SearchUpstreamBySha1,
MavenBaseURL: cfg.Maven.BaseURL,
AbortAfter: abortAfter,
AbortAfter: *cfg.Maven.AbortAfter,
}
}

func multiLevelOption[T any](defaultValue T, option ...*T) *T {
result := defaultValue
for _, opt := range option {
if opt != nil {
result = *opt
}
}
return &result
}
14 changes: 8 additions & 6 deletions grype/matcher/java/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Matcher struct {
type ExternalSearchConfig struct {
SearchMavenUpstream bool
MavenBaseURL string
AbortAfter string
AbortAfter time.Duration
}

type MatcherConfig struct {
Expand Down Expand Up @@ -56,12 +56,14 @@ func (m *Matcher) Type() match.MatcherType {
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
var matches []match.Match
if m.cfg.SearchMavenUpstream {
timeout, err := time.ParseDuration(m.cfg.AbortAfter)
if err != nil {
return nil, fmt.Errorf("failed to parse timeout duration: %w", err)
timeout := m.cfg.AbortAfter
ctx := context.Background()
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, m.cfg.AbortAfter)
defer cancel()
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

upstreamMatches, err := m.matchUpstreamMavenPackages(ctx, store, d, p)
if err != nil {
log.Debugf("failed to match against upstream data for %s: %v", p.Name, err)
Expand Down
47 changes: 32 additions & 15 deletions grype/matcher/java/matcher_mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package java

import (
"context"
"fmt"
"testing"
"time"

"github.com/anchore/grype/grype/distro"
Expand Down Expand Up @@ -53,25 +53,42 @@ func newMockProvider() *mockProvider {
}

type mockMavenSearcher struct {
pkg pkg.Package
tb testing.TB
pkg *pkg.Package
work *time.Duration
}

func (m mockMavenSearcher) GetMavenPackageBySha(ctx context.Context, sha1 string) (*pkg.Package, error) {
deadline, ok := ctx.Deadline()
fmt.Println("GetMavenPackageBySha called with deadline:", deadline, "deadline set:", ok)
// Sleep for a duration longer than the context's deadline
select {
case <-time.After(10 * time.Second):
return &m.pkg, nil
case <-ctx.Done():
// If the context is done before the sleep is over, return a context.DeadlineExceeded error
return nil, ctx.Err()
func newMockSearcher(tb testing.TB) mockMavenSearcher {
return mockMavenSearcher{
tb: tb,
}
}

func newMockSearcher(pkg pkg.Package) MavenSearcher {
return mockMavenSearcher{
pkg: pkg,
func (m mockMavenSearcher) WithPackage(p pkg.Package) mockMavenSearcher {
m.pkg = &p
return m
}

func (m mockMavenSearcher) WithWorkDuration(duration time.Duration) mockMavenSearcher {
m.work = &duration
return m
}

func (m mockMavenSearcher) GetMavenPackageBySha(ctx context.Context, sha1 string) (*pkg.Package, error) {
deadline, ok := ctx.Deadline()

m.tb.Log("GetMavenPackageBySha called with deadline:", deadline, "deadline set:", ok)

if m.work != nil {
select {
case <-time.After(*m.work):
return m.pkg, nil
case <-ctx.Done():
// If the context is done before the sleep is over, return a context.DeadlineExceeded error
return m.pkg, ctx.Err()
}
} else {
return m.pkg, ctx.Err()
}
}

Expand Down
62 changes: 7 additions & 55 deletions grype/matcher/java/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package java

import (
"context"
"errors"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -40,11 +38,12 @@ func TestMatcherJava_matchUpstreamMavenPackage(t *testing.T) {
},
UseCPEs: false,
},
MavenSearcher: newMockSearcher(p),
// no duration will return immediately with the mock data
MavenSearcher: newMockSearcher(t).WithPackage(p),
}
store := newMockProvider()
context := context.Background()
actual, _ := matcher.matchUpstreamMavenPackages(context, store, nil, p)
ctx := context.Background()
actual, _ := matcher.matchUpstreamMavenPackages(ctx, store, nil, p)

assert.Len(t, actual, 2, "unexpected matches count")

Expand Down Expand Up @@ -92,62 +91,15 @@ func TestMatcherJava_TestMatchUpstreamMavenPackagesTimeout(t *testing.T) {
},
UseCPEs: false,
},
MavenSearcher: newMockSearcher(p),
MavenSearcher: newMockSearcher(t).WithPackage(p).WithWorkDuration(10 * time.Second),
}
store := newMockProvider()

// Create a context with a very short timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()

_, err := matcher.matchUpstreamMavenPackages(ctx, store, nil, p)
// Check if the error is a context deadline exceeded error
if err == nil {
t.Errorf("expected an error but got none")
} else if !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("expected a context deadline exceeded error but got a different error: %v", err)
}
}

func TestMatcherJava_TestMatchTimeoutExtraction(t *testing.T) {
// Create a Matcher with an invalid AbortAfter value
matcher := Matcher{
cfg: MatcherConfig{
ExternalSearchConfig: ExternalSearchConfig{
SearchMavenUpstream: true,
AbortAfter: "invalid_duration", // This cannot be parsed into a duration
},
UseCPEs: false,
},
MavenSearcher: newMockSearcher(pkg.Package{}),
}

// Call Match
_, err := matcher.Match(nil, nil, pkg.Package{})
if err == nil {
t.Error("expected an error but got none")
} else if !strings.Contains(err.Error(), "failed to parse timeout duration") {
t.Errorf("expected a 'failed to parse timeout duration' error but got %v", err)
}
}

func TestMatcherJava_TestMatchWithCorrectAbortAfterVal(t *testing.T) {
// Create a Matcher with a valid AbortAfter value
matcher := Matcher{
cfg: MatcherConfig{
ExternalSearchConfig: ExternalSearchConfig{
SearchMavenUpstream: true,
AbortAfter: "1ns",
},
UseCPEs: false,
},
MavenSearcher: newMockSearcher(pkg.Package{}),
}

// Call Match
store := newMockProvider()
_, err := matcher.Match(store, nil, pkg.Package{})
if err != nil {
t.Errorf("expected no error but got %v", err)
}
require.ErrorIs(t, err, context.DeadlineExceeded)
}

0 comments on commit ca5d50c

Please sign in to comment.