Skip to content

Commit 372adc0

Browse files
committed
fix: check if trivy exist
Signed-off-by: Sertac Ozercan <[email protected]>
1 parent 9d3c0da commit 372adc0

File tree

4 files changed

+188
-7
lines changed

4 files changed

+188
-7
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ ENTRYPOINT ["/remover"]
6666

6767
FROM --platform=$TARGETPLATFORM gcr.io/distroless/static:latest as trivy-scanner
6868
COPY --from=trivy-scanner-build /workspace/out/trivy-scanner /
69-
COPY --from=trivy-binary /usr/local/bin/trivy /
69+
COPY --from=trivy-binary /usr/local/bin/trivy /trivy
7070
WORKDIR /var/lib/trivy
7171
ENTRYPOINT ["/trivy-scanner"]
7272

docs/docs/trivy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ title: Trivy
33
---
44

55
## Trivy Provider Options
6-
The Trivy provider is used in Eraser for image scanning and detecting vulnerabilities. See [Customization](https://eraser-dev.github.io/eraser/docs/customization#scanner-options) for more details on configuring the scanner.
6+
The Trivy provider is used in Eraser for image scanning and detecting vulnerabilities. The scanner will first look for the trivy executable at `/trivy` (the default path in the container), and if not found, will fall back to searching for `trivy` in the system PATH. See [Customization](https://eraser-dev.github.io/eraser/docs/customization#scanner-options) for more details on configuring the scanner.

pkg/scanners/trivy/types.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import (
1414
"github.com/eraser-dev/eraser/pkg/utils"
1515
)
1616

17+
// currentExecutingLookPath is a variable that points to exec.LookPath by default,
18+
// but can be overridden for testing purposes
19+
var currentExecutingLookPath = exec.LookPath
20+
1721
const (
1822
StatusFailed ScanStatus = iota
1923
StatusNonCompliant
@@ -172,12 +176,33 @@ type ImageScanner struct {
172176
timer *time.Timer
173177
}
174178

179+
func (s *ImageScanner) findTrivyExecutable() (string, error) {
180+
// First, check if trivy exists at the hardcoded path
181+
if _, err := os.Stat(trivyCommandName); err == nil {
182+
return trivyCommandName, nil
183+
}
184+
185+
// If not found at hardcoded path, try to find it in PATH
186+
path, err := currentExecutingLookPath("trivy")
187+
if err != nil {
188+
return "", fmt.Errorf("trivy executable not found at %s and not found in PATH: %w", trivyCommandName, err)
189+
}
190+
191+
return path, nil
192+
}
193+
175194
func (s *ImageScanner) Scan(img unversioned.Image) (ScanStatus, error) {
176195
refs := make([]string, 0, len(img.Names)+len(img.Digests))
177196
refs = append(refs, img.Digests...)
178197
refs = append(refs, img.Names...)
179198
scanSucceeded := false
180199

200+
// Find trivy executable path
201+
trivyPath, err := s.findTrivyExecutable()
202+
if err != nil {
203+
return StatusFailed, err
204+
}
205+
181206
log.Info("scanning image with id", "imageID", img.ImageID, "refs", refs)
182207
for i := 0; i < len(refs) && !scanSucceeded; i++ {
183208
log.Info("scanning image with ref", "ref", refs[i])
@@ -186,13 +211,13 @@ func (s *ImageScanner) Scan(img unversioned.Image) (ScanStatus, error) {
186211
stderr := new(bytes.Buffer)
187212

188213
cliArgs := s.config.cliArgs(refs[i])
189-
cmd := exec.Command(trivyCommandName, cliArgs...)
214+
cmd := exec.Command(trivyPath, cliArgs...)
190215
cmd.Stdout = stdout
191216
cmd.Stderr = stderr
192217
cmd.Env = append(cmd.Env, os.Environ()...)
193218
cmd.Env = setRuntimeSocketEnvVars(cmd, s.config.Runtime)
194219

195-
log.V(1).Info("scanning image ref", "ref", refs[i], "cli_invocation", fmt.Sprintf("%s %s", trivyCommandName, strings.Join(cliArgs, " ")), "env", cmd.Env)
220+
log.V(1).Info("scanning image ref", "ref", refs[i], "cli_invocation", fmt.Sprintf("%s %s", trivyPath, strings.Join(cliArgs, " ")), "env", cmd.Env)
196221
if err := cmd.Run(); err != nil {
197222
log.Error(err, "error scanning image", "imageID", img.ImageID, "reference", refs[i], "stderr", stderr.String())
198223
continue

pkg/scanners/trivy/types_test.go

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import (
44
"strings"
55
"testing"
66

7+
"errors"
8+
"fmt"
9+
710
"github.com/eraser-dev/eraser/api/unversioned"
11+
12+
"github.com/stretchr/testify/assert"
813
)
914

1015
const ref = "image:tag"
1116

1217
var testDuration = unversioned.Duration(100000000000)
1318

14-
func init() {
15-
}
16-
1719
func TestCLIArgs(t *testing.T) {
1820
type testCell struct {
1921
desc string
@@ -168,3 +170,157 @@ func TestCLIArgs(t *testing.T) {
168170
})
169171
}
170172
}
173+
174+
// TestImageScanner_findTrivyExecutable tests the findTrivyExecutable method in isolation.
175+
func TestImageScanner_findTrivyExecutable(t *testing.T) {
176+
// Store original function to restore after tests
177+
originalLookPath := currentExecutingLookPath
178+
defer func() { currentExecutingLookPath = originalLookPath }()
179+
180+
scanner := &ImageScanner{}
181+
182+
testCases := []struct {
183+
name string
184+
lookPathSetup func()
185+
expectedPath string
186+
expectedError bool
187+
expectedErrorMatch string
188+
}{
189+
{
190+
name: "Trivy found in PATH only",
191+
lookPathSetup: func() {
192+
currentExecutingLookPath = func(file string) (string, error) {
193+
if file == "trivy" {
194+
return "/usr/bin/trivy", nil
195+
}
196+
return "", errors.New("not found")
197+
}
198+
},
199+
expectedPath: "/usr/bin/trivy",
200+
expectedError: false,
201+
},
202+
{
203+
name: "Trivy not found anywhere",
204+
lookPathSetup: func() {
205+
currentExecutingLookPath = func(file string) (string, error) {
206+
return "", errors.New("executable file not found in $PATH")
207+
}
208+
},
209+
expectedPath: "",
210+
expectedError: true,
211+
expectedErrorMatch: "trivy executable not found at /trivy and not found in PATH",
212+
},
213+
}
214+
215+
for _, tc := range testCases {
216+
t.Run(tc.name, func(t *testing.T) {
217+
tc.lookPathSetup()
218+
219+
path, err := scanner.findTrivyExecutable()
220+
221+
if tc.expectedError {
222+
assert.Error(t, err)
223+
assert.Contains(t, err.Error(), tc.expectedErrorMatch)
224+
assert.Empty(t, path)
225+
} else {
226+
assert.NoError(t, err)
227+
assert.Equal(t, tc.expectedPath, path)
228+
}
229+
})
230+
}
231+
}
232+
233+
// TestImageScanner_Scan_TrivyPathLookup tests the logic for finding the trivy executable.
234+
func TestImageScanner_Scan_TrivyPathLookup(t *testing.T) {
235+
// Store original function to restore after tests
236+
originalLookPath := currentExecutingLookPath
237+
defer func() { currentExecutingLookPath = originalLookPath }()
238+
239+
// Base configuration for the scanner
240+
baseConfig := DefaultConfig()
241+
scanner := &ImageScanner{
242+
config: *baseConfig,
243+
}
244+
// Dummy image for testing
245+
img := unversioned.Image{ImageID: "test-image-id", Names: []string{"test-image:latest"}}
246+
247+
// Expected error message prefix when trivy is not found
248+
expectedNotFoundErrorMsgPrefix := fmt.Sprintf("trivy executable not found at %s", trivyCommandName)
249+
250+
testCases := []struct {
251+
name string
252+
lookPathSetup func() // Sets up the mock for exec.LookPath
253+
expectedStatus ScanStatus
254+
expectNotFoundError bool // True if we expect the specific "trivy not found by LookPath" error
255+
expectedErrorMsgContains string // The prefix for the "not found" error message
256+
}{
257+
{
258+
name: "Trivy found at hardcoded path /trivy",
259+
lookPathSetup: func() {
260+
currentExecutingLookPath = func(file string) (string, error) {
261+
if file == "trivy" {
262+
return "/usr/bin/trivy", nil // Found in PATH
263+
}
264+
return originalLookPath(file) // Fallback for any other calls
265+
}
266+
},
267+
// Scan will likely still fail due to inability to run actual scan in test,
268+
// but it should not be the "trivy not found by LookPath" error.
269+
expectedStatus: StatusFailed,
270+
expectNotFoundError: false,
271+
},
272+
{
273+
name: "Trivy found in $PATH, not at /trivy",
274+
lookPathSetup: func() {
275+
currentExecutingLookPath = func(file string) (string, error) {
276+
if file == "trivy" {
277+
return "/usr/bin/trivy", nil // Found in $PATH
278+
}
279+
return originalLookPath(file)
280+
}
281+
},
282+
expectedStatus: StatusFailed, // Similar to above, subsequent scan steps will fail.
283+
expectNotFoundError: false,
284+
},
285+
{
286+
name: "Trivy not found anywhere",
287+
lookPathSetup: func() {
288+
currentExecutingLookPath = func(file string) (string, error) {
289+
if file == "trivy" {
290+
return "", errors.New("mock: trivy not in $PATH")
291+
}
292+
return originalLookPath(file)
293+
}
294+
},
295+
expectedStatus: StatusFailed,
296+
expectNotFoundError: true,
297+
expectedErrorMsgContains: expectedNotFoundErrorMsgPrefix,
298+
},
299+
}
300+
301+
for _, tc := range testCases {
302+
t.Run(tc.name, func(t *testing.T) {
303+
tc.lookPathSetup()
304+
305+
status, err := scanner.Scan(img)
306+
307+
if tc.expectNotFoundError {
308+
assert.Error(t, err, "Expected an error when trivy is not found")
309+
if err != nil { // Check prefix only if error is not nil
310+
assert.True(t, strings.HasPrefix(err.Error(), tc.expectedErrorMsgContains),
311+
"Error message should start with '%s'. Got: %s", tc.expectedErrorMsgContains, err.Error())
312+
}
313+
assert.Equal(t, tc.expectedStatus, status, "ScanStatus should be StatusFailed")
314+
} else {
315+
// If trivy was "found" by LookPath, any error should be from subsequent operations (e.g., cmd.Run, JSON unmarshal),
316+
// not the specific "trivy executable not found by LookPath..." error.
317+
if err != nil {
318+
assert.False(t, strings.HasPrefix(err.Error(), expectedNotFoundErrorMsgPrefix),
319+
"Error should not be the 'trivy not found by LookPath' error. Got: %s", err.Error())
320+
}
321+
// The status might still be StatusFailed due to these subsequent errors,
322+
// which is acceptable for this test's focus on path lookup.
323+
}
324+
})
325+
}
326+
}

0 commit comments

Comments
 (0)