Skip to content

Commit 00a46e9

Browse files
add tests and codacy vulns fixed
1 parent 078a26a commit 00a46e9

File tree

2 files changed

+373
-25
lines changed

2 files changed

+373
-25
lines changed

cmd/container_scan.go

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,47 @@ import (
2020
// Based on Docker image reference specification
2121
var validImageNamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._\-/:@]*$`)
2222

23+
// lookPath is a variable to allow mocking exec.LookPath in tests
24+
var lookPath = exec.LookPath
25+
26+
// exitFunc is a variable to allow mocking os.Exit in tests
27+
var exitFunc = os.Exit
28+
29+
// CommandRunner interface for running external commands (allows mocking in tests)
30+
type CommandRunner interface {
31+
Run(name string, args []string) error
32+
}
33+
34+
// ExecCommandRunner runs commands using exec.Command
35+
type ExecCommandRunner struct{}
36+
37+
// Run executes a command and returns its exit error
38+
func (r *ExecCommandRunner) Run(name string, args []string) error {
39+
// #nosec G204 -- name comes from exec.LookPath("trivy") with a literal string,
40+
// and args are validated by validateImageName() which checks for shell metacharacters.
41+
// exec.Command passes arguments directly without shell interpretation.
42+
cmd := exec.Command(name, args...)
43+
cmd.Stdout = os.Stdout
44+
cmd.Stderr = os.Stderr
45+
return cmd.Run()
46+
}
47+
48+
// commandRunner is the default command runner, can be replaced in tests
49+
var commandRunner CommandRunner = &ExecCommandRunner{}
50+
51+
// ExitCoder interface for errors that have an exit code
52+
type ExitCoder interface {
53+
ExitCode() int
54+
}
55+
56+
// getExitCode returns the exit code from an error if it implements ExitCoder
57+
func getExitCode(err error) int {
58+
if exitErr, ok := err.(ExitCoder); ok {
59+
return exitErr.ExitCode()
60+
}
61+
return -1
62+
}
63+
2364
// Flag variables for container-scan command
2465
var (
2566
severityFlag string
@@ -90,37 +131,52 @@ func validateImageName(imageName string) error {
90131
return nil
91132
}
92133

93-
// getTrivyPath returns the path to the Trivy binary or exits if not found
94-
func getTrivyPath() string {
95-
trivyPath, err := exec.LookPath("trivy")
134+
// getTrivyPath returns the path to the Trivy binary and an error if not found
135+
func getTrivyPath() (string, error) {
136+
trivyPath, err := lookPath("trivy")
96137
if err != nil {
97-
logger.Error("Trivy not found", logrus.Fields{"error": err.Error()})
98-
color.Red("❌ Error: Trivy is not installed or not found in PATH")
99-
fmt.Println("Please install Trivy to use container scanning.")
100-
fmt.Println("Visit: https://trivy.dev/latest/getting-started/installation/")
101-
fmt.Println("exit-code 2")
102-
os.Exit(2)
138+
return "", err
103139
}
104140
logger.Info("Found Trivy", logrus.Fields{"path": trivyPath})
105-
return trivyPath
141+
return trivyPath, nil
142+
}
143+
144+
// handleTrivyNotFound prints error message and exits with code 2
145+
func handleTrivyNotFound(err error) {
146+
logger.Error("Trivy not found", logrus.Fields{"error": err.Error()})
147+
color.Red("❌ Error: Trivy is not installed or not found in PATH")
148+
fmt.Println("Please install Trivy to use container scanning.")
149+
fmt.Println("Visit: https://trivy.dev/latest/getting-started/installation/")
150+
fmt.Println("exit-code 2")
151+
exitFunc(2)
106152
}
107153

108154
func runContainerScan(_ *cobra.Command, args []string) {
109-
imageNames := args
155+
exitCode := executeContainerScan(args)
156+
exitFunc(exitCode)
157+
}
110158

159+
// executeContainerScan performs the container scan and returns an exit code
160+
// Exit codes: 0 = success, 1 = vulnerabilities found, 2 = error
161+
func executeContainerScan(imageNames []string) int {
111162
// Validate all image names first
112163
for _, imageName := range imageNames {
113164
if err := validateImageName(imageName); err != nil {
114165
logger.Error("Invalid image name", logrus.Fields{"image": imageName, "error": err.Error()})
115166
color.Red("❌ Error: %v", err)
116167
fmt.Println("exit-code 2")
117-
os.Exit(2)
168+
return 2
118169
}
119170
}
120171

121172
logger.Info("Starting container scan", logrus.Fields{"images": imageNames, "count": len(imageNames)})
122173

123-
trivyPath := getTrivyPath()
174+
trivyPath, err := getTrivyPath()
175+
if err != nil {
176+
handleTrivyNotFound(err)
177+
return 2 // This won't be reached due to exitFunc, but needed for testing
178+
}
179+
124180
hasVulnerabilities := false
125181

126182
for i, imageName := range imageNames {
@@ -131,24 +187,18 @@ func runContainerScan(_ *cobra.Command, args []string) {
131187
fmt.Printf("🔍 Scanning container image: %s\n\n", imageName)
132188
}
133189

134-
// #nosec G204 -- imageName is validated by validateImageName() which checks for
135-
// shell metacharacters and enforces a strict character allowlist. Additionally,
136-
// exec.Command passes arguments directly without shell interpretation.
137-
trivyCmd := exec.Command(trivyPath, buildTrivyArgs(imageName)...)
138-
trivyCmd.Stdout = os.Stdout
139-
trivyCmd.Stderr = os.Stderr
140-
141-
logger.Info("Running Trivy container scan", logrus.Fields{"command": trivyCmd.String()})
190+
args := buildTrivyArgs(imageName)
191+
logger.Info("Running Trivy container scan", logrus.Fields{"command": fmt.Sprintf("%s %v", trivyPath, args)})
142192

143-
if err := trivyCmd.Run(); err != nil {
144-
if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 {
193+
if err := commandRunner.Run(trivyPath, args); err != nil {
194+
if getExitCode(err) == 1 {
145195
logger.Warn("Vulnerabilities found in image", logrus.Fields{"image": imageName})
146196
hasVulnerabilities = true
147197
} else {
148198
logger.Error("Failed to run Trivy", logrus.Fields{"error": err.Error(), "image": imageName})
149199
color.Red("❌ Error: Failed to run Trivy for %s: %v", imageName, err)
150200
fmt.Println("exit-code 2")
151-
os.Exit(2)
201+
return 2
152202
}
153203
} else {
154204
logger.Info("No vulnerabilities found in image", logrus.Fields{"image": imageName})
@@ -161,12 +211,13 @@ func runContainerScan(_ *cobra.Command, args []string) {
161211
logger.Warn("Container scan completed with vulnerabilities", logrus.Fields{"images": imageNames})
162212
color.Red("❌ Scanning failed: vulnerabilities found in one or more container images")
163213
fmt.Println("exit-code 1")
164-
os.Exit(1)
214+
return 1
165215
}
166216

167217
logger.Info("Container scan completed successfully", logrus.Fields{"images": imageNames})
168218
color.Green("✅ Success: No vulnerabilities found matching the specified criteria")
169219
fmt.Println("exit-code 0")
220+
return 0
170221
}
171222

172223
// buildTrivyArgs constructs the Trivy command arguments based on flags

0 commit comments

Comments
 (0)