Skip to content

Commit eebe5b9

Browse files
committed
address review comments
Signed-off-by: Sertac Ozercan <[email protected]>
1 parent 38bb712 commit eebe5b9

File tree

3 files changed

+183
-93
lines changed

3 files changed

+183
-93
lines changed

pkg/scanners/trivy/trivy.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"net/http"
99
"os"
10+
"os/exec"
1011
"time"
1112

1213
"github.com/eraser-dev/eraser/api/unversioned"
@@ -147,19 +148,28 @@ func runProfileServer() {
147148
log.Error(err, "pprof server failed")
148149
}
149150

150-
func findTrivyExecutable() (string, error) {
151-
// First, check if trivy exists at the hardcoded path
152-
if _, err := os.Stat(trivyCommandName); err == nil {
153-
return trivyCommandName, nil
151+
// ensureTrivyExecutable ensures that a trivy executable is available at the target path.
152+
// If the executable is not found at the target path, it searches for trivy in PATH
153+
// and creates a symlink at the target path pointing to the found executable.
154+
func ensureTrivyExecutable(targetPath string) error {
155+
// Check if trivy already exists at the target path
156+
if _, err := os.Stat(targetPath); err == nil {
157+
return nil
154158
}
155159

156-
// If not found at hardcoded path, try to find it in PATH
157-
path, err := currentExecutingLookPath("trivy")
160+
// If not found at target path, try to find it in PATH
161+
pathLocation, err := exec.LookPath("trivy")
158162
if err != nil {
159-
return "", fmt.Errorf("trivy executable not found at %s and not found in PATH: %w", trivyCommandName, err)
163+
return fmt.Errorf("trivy executable not found at %s and not found in PATH: %w", targetPath, err)
160164
}
161165

162-
return path, nil
166+
// Create symlink at target path pointing to the found executable
167+
if err := os.Symlink(pathLocation, targetPath); err != nil {
168+
return fmt.Errorf("failed to create symlink from %s to %s: %w", targetPath, pathLocation, err)
169+
}
170+
171+
log.Info("created trivy symlink", "from", targetPath, "to", pathLocation)
172+
return nil
163173
}
164174

165175
func initScanner(userConfig *Config) (Scanner, error) {
@@ -180,19 +190,17 @@ func initScanner(userConfig *Config) (Scanner, error) {
180190
Address: utils.CRIPath,
181191
}
182192

183-
// Find trivy executable path during initialization
184-
trivyPath, err := findTrivyExecutable()
185-
if err != nil {
193+
// Ensure trivy executable is available at the hardcoded path
194+
if err := ensureTrivyExecutable(trivyCommandName); err != nil {
186195
return nil, err
187196
}
188197

189198
totalTimeout := time.Duration(userConfig.Timeout.Total)
190199
timer := time.NewTimer(totalTimeout)
191200

192201
var s Scanner = &ImageScanner{
193-
config: *userConfig,
194-
timer: timer,
195-
trivyPath: trivyPath,
202+
config: *userConfig,
203+
timer: timer,
196204
}
197205
return s, nil
198206
}

pkg/scanners/trivy/types.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ 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-
2117
const (
2218
StatusFailed ScanStatus = iota
2319
StatusNonCompliant
@@ -172,9 +168,8 @@ func (c *Config) getRuntimeVar() (string, error) {
172168
}
173169

174170
type ImageScanner struct {
175-
config Config
176-
timer *time.Timer
177-
trivyPath string
171+
config Config
172+
timer *time.Timer
178173
}
179174

180175
func (s *ImageScanner) Scan(img unversioned.Image) (ScanStatus, error) {
@@ -191,13 +186,13 @@ func (s *ImageScanner) Scan(img unversioned.Image) (ScanStatus, error) {
191186
stderr := new(bytes.Buffer)
192187

193188
cliArgs := s.config.cliArgs(refs[i])
194-
cmd := exec.Command(s.trivyPath, cliArgs...) // nolint:gosec // G204: Subprocess launched with variable
189+
cmd := exec.Command(trivyCommandName, cliArgs...)
195190
cmd.Stdout = stdout
196191
cmd.Stderr = stderr
197192
cmd.Env = append(cmd.Env, os.Environ()...)
198193
cmd.Env = setRuntimeSocketEnvVars(cmd, s.config.Runtime)
199194

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

pkg/scanners/trivy/types_test.go

Lines changed: 157 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package main
22

33
import (
4-
"errors"
4+
"os"
5+
"path/filepath"
56
"strings"
67
"testing"
78

89
"github.com/eraser-dev/eraser/api/unversioned"
9-
1010
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
1112
)
1213

1314
const (
@@ -172,103 +173,189 @@ func TestCLIArgs(t *testing.T) {
172173
}
173174
}
174175

175-
// TestFindTrivyExecutable tests the findTrivyExecutable function in isolation.
176-
func TestFindTrivyExecutable(t *testing.T) {
177-
// Store original function to restore after tests
178-
originalLookPath := currentExecutingLookPath
179-
defer func() { currentExecutingLookPath = originalLookPath }()
176+
// TestEnsureTrivyExecutable tests the ensureTrivyExecutable function in isolation.
177+
func TestEnsureTrivyExecutable(t *testing.T) {
178+
// Save original PATH to restore after tests
179+
originalPath := os.Getenv("PATH")
180+
defer func() { os.Setenv("PATH", originalPath) }()
180181

181182
testCases := []struct {
182-
name string
183-
lookPathSetup func()
184-
expectedPath string
185-
expectedError bool
186-
expectedErrorMatch string
183+
name string
184+
setupFunc func(t *testing.T) (targetPath string, cleanup func())
185+
expectedError bool
186+
expectedErrorMsg string
187+
validateFunc func(t *testing.T, targetPath string)
187188
}{
188189
{
189-
name: "Trivy found in PATH only",
190-
lookPathSetup: func() {
191-
currentExecutingLookPath = func(file string) (string, error) {
192-
if file == trivyExecutableName {
193-
return trivyPathBin, nil
194-
}
195-
return "", errors.New("not found")
196-
}
190+
name: "Trivy already exists at target path",
191+
setupFunc: func(t *testing.T) (string, func()) {
192+
tempDir := t.TempDir()
193+
targetPath := filepath.Join(tempDir, "trivy")
194+
195+
// Create a trivy executable at the target path
196+
file, err := os.Create(targetPath)
197+
require.NoError(t, err)
198+
file.Close()
199+
err = os.Chmod(targetPath, 0o755)
200+
require.NoError(t, err)
201+
202+
return targetPath, func() {}
197203
},
198-
expectedPath: trivyPathBin,
199204
expectedError: false,
205+
validateFunc: func(t *testing.T, targetPath string) {
206+
// Should not create a symlink, original file should still exist
207+
info, err := os.Lstat(targetPath)
208+
require.NoError(t, err)
209+
assert.Equal(t, os.FileMode(0o755), info.Mode().Perm(), "Original file should be preserved")
210+
211+
// Verify it's not a symlink
212+
assert.Equal(t, 0, int(info.Mode()&os.ModeSymlink), "Should not be a symlink")
213+
},
214+
},
215+
{
216+
name: "Trivy found in PATH, symlink created successfully",
217+
setupFunc: func(t *testing.T) (string, func()) {
218+
tempDir := t.TempDir()
219+
pathDir := filepath.Join(tempDir, "bin")
220+
err := os.Mkdir(pathDir, 0o755)
221+
require.NoError(t, err)
222+
223+
// Create a trivy executable in the PATH directory
224+
trivyInPath := filepath.Join(pathDir, "trivy")
225+
file, err := os.Create(trivyInPath)
226+
require.NoError(t, err)
227+
file.Close()
228+
err = os.Chmod(trivyInPath, 0o755)
229+
require.NoError(t, err)
230+
231+
// Set PATH to include our temp bin directory
232+
os.Setenv("PATH", pathDir)
233+
234+
// Target path where symlink should be created
235+
targetPath := filepath.Join(tempDir, "target_trivy")
236+
237+
return targetPath, func() {}
238+
},
239+
expectedError: false,
240+
validateFunc: func(t *testing.T, targetPath string) {
241+
// Should create a symlink at target path
242+
info, err := os.Lstat(targetPath)
243+
require.NoError(t, err)
244+
245+
// Verify it's a symlink
246+
assert.NotEqual(t, 0, int(info.Mode()&os.ModeSymlink), "Should be a symlink")
247+
248+
// Verify symlink points to the correct location
249+
linkTarget, err := os.Readlink(targetPath)
250+
require.NoError(t, err)
251+
assert.Contains(t, linkTarget, "trivy", "Symlink should point to trivy executable")
252+
},
200253
},
201254
{
202255
name: "Trivy not found anywhere",
203-
lookPathSetup: func() {
204-
currentExecutingLookPath = func(_ string) (string, error) {
205-
return "", errors.New("executable file not found in $PATH")
256+
setupFunc: func(t *testing.T) (string, func()) {
257+
tempDir := t.TempDir()
258+
emptyPathDir := filepath.Join(tempDir, "empty")
259+
err := os.Mkdir(emptyPathDir, 0o755)
260+
require.NoError(t, err)
261+
262+
// Set PATH to empty directory without trivy
263+
os.Setenv("PATH", emptyPathDir)
264+
265+
targetPath := filepath.Join(tempDir, "target_trivy")
266+
return targetPath, func() {}
267+
},
268+
expectedError: true,
269+
expectedErrorMsg: "trivy executable not found",
270+
validateFunc: func(t *testing.T, targetPath string) {
271+
// Should not create any file or symlink
272+
_, err := os.Lstat(targetPath)
273+
assert.True(t, os.IsNotExist(err), "Target path should not exist when trivy is not found")
274+
},
275+
},
276+
{
277+
name: "Symlink creation fails due to permission",
278+
setupFunc: func(t *testing.T) (string, func()) {
279+
if os.Getuid() == 0 {
280+
t.Skip("Skipping permission test when running as root")
281+
}
282+
283+
tempDir := t.TempDir()
284+
pathDir := filepath.Join(tempDir, "bin")
285+
err := os.Mkdir(pathDir, 0o755)
286+
require.NoError(t, err)
287+
288+
// Create a trivy executable in PATH
289+
trivyInPath := filepath.Join(pathDir, "trivy")
290+
file, err := os.Create(trivyInPath)
291+
require.NoError(t, err)
292+
file.Close()
293+
err = os.Chmod(trivyInPath, 0o755)
294+
require.NoError(t, err)
295+
296+
os.Setenv("PATH", pathDir)
297+
298+
// Try to create symlink in root directory (should fail for non-root users)
299+
targetPath := "/trivy_test_symlink"
300+
301+
return targetPath, func() {
302+
// Clean up any created symlink
303+
os.Remove(targetPath)
206304
}
207305
},
208-
expectedPath: "",
209-
expectedError: true,
210-
expectedErrorMatch: "trivy executable not found at /trivy and not found in PATH",
306+
expectedError: true,
307+
expectedErrorMsg: "failed to create symlink",
308+
validateFunc: func(t *testing.T, targetPath string) {
309+
// Should not create symlink due to permission error
310+
_, err := os.Lstat(targetPath)
311+
assert.True(t, os.IsNotExist(err), "Target path should not exist when symlink creation fails")
312+
},
211313
},
212314
}
213315

214316
for _, tc := range testCases {
215317
t.Run(tc.name, func(t *testing.T) {
216-
tc.lookPathSetup()
318+
targetPath, cleanup := tc.setupFunc(t)
319+
defer cleanup()
217320

218-
path, err := findTrivyExecutable()
321+
// Test the ensureTrivyExecutable function
322+
err := ensureTrivyExecutable(targetPath)
219323

220324
if tc.expectedError {
221325
assert.Error(t, err)
222-
assert.Contains(t, err.Error(), tc.expectedErrorMatch)
223-
assert.Empty(t, path)
326+
assert.Contains(t, err.Error(), tc.expectedErrorMsg)
224327
} else {
225328
assert.NoError(t, err)
226-
assert.Equal(t, tc.expectedPath, path)
329+
}
330+
331+
if tc.validateFunc != nil {
332+
tc.validateFunc(t, targetPath)
227333
}
228334
})
229335
}
230336
}
231337

232-
// TestImageScanner_Scan_TrivyPathLookup tests the logic for using the trivy executable path.
233-
func TestImageScanner_Scan_TrivyPathLookup(t *testing.T) {
234-
// Base configuration for the scanner
235-
baseConfig := DefaultConfig()
236-
// Dummy image for testing
237-
img := unversioned.Image{ImageID: "test-image-id", Names: []string{"test-image:latest"}}
338+
// TestInitScanner_TrivySymlinkCreation tests the symlink creation logic in initScanner.
339+
func TestInitScanner_TrivySymlinkCreation(t *testing.T) {
340+
// This test verifies that initScanner properly calls ensureTrivyExecutable
341+
// and handles errors appropriately. Since ensureTrivyExecutable is tested
342+
// comprehensively above, this focuses on the integration aspect.
238343

239-
testCases := []struct {
240-
name string
241-
trivyPath string
242-
expectedStatus ScanStatus
243-
}{
244-
{
245-
name: "Trivy path set to hardcoded path /trivy",
246-
trivyPath: trivyCommandName,
247-
expectedStatus: StatusFailed, // Will fail during actual execution but not due to path issues
248-
},
249-
{
250-
name: "Trivy path set to system PATH location",
251-
trivyPath: trivyPathBin,
252-
expectedStatus: StatusFailed, // Will fail during actual execution but not due to path issues
253-
},
254-
}
344+
// Save original PATH to restore after test
345+
originalPath := os.Getenv("PATH")
346+
defer func() { os.Setenv("PATH", originalPath) }()
255347

256-
for _, tc := range testCases {
257-
t.Run(tc.name, func(t *testing.T) {
258-
scanner := &ImageScanner{
259-
config: *baseConfig,
260-
trivyPath: tc.trivyPath,
261-
}
348+
t.Run("initScanner fails when trivy not found", func(t *testing.T) {
349+
// Set PATH to empty directory
350+
tempDir := t.TempDir()
351+
os.Setenv("PATH", tempDir)
262352

263-
status, err := scanner.Scan(img)
353+
config := DefaultConfig()
354+
scanner, err := initScanner(config)
264355

265-
// The scan will likely fail due to inability to run actual scan in test,
266-
// but it should not be a "trivy not found" error since the path is already set
267-
assert.Equal(t, tc.expectedStatus, status, "ScanStatus should be StatusFailed")
268-
if err != nil {
269-
assert.NotContains(t, err.Error(), "trivy executable not found",
270-
"Error should not be about trivy not being found since path is pre-set")
271-
}
272-
})
273-
}
356+
// Should fail because trivy is not found
357+
assert.Error(t, err)
358+
assert.Contains(t, err.Error(), "trivy executable not found")
359+
assert.Nil(t, scanner)
360+
})
274361
}

0 commit comments

Comments
 (0)