Skip to content

Commit c8c5182

Browse files
add container-scan command to trivy scan containers
1 parent e1bb1bc commit c8c5182

File tree

3 files changed

+158
-2
lines changed

3 files changed

+158
-2
lines changed

cli-v2.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ func main() {
3939
}
4040
}
4141

42-
// Check if command is init/update/version/help - these don't require configuration
42+
// Check if command is init/update/version/help/container-scan - these don't require configuration
4343
if len(os.Args) > 1 {
4444
cmdName := os.Args[1]
45-
if cmdName == "init" || cmdName == "update" || cmdName == "version" || cmdName == "help" {
45+
if cmdName == "init" || cmdName == "update" || cmdName == "version" || cmdName == "help" || cmdName == "container-scan" {
4646
cmd.Execute()
4747
return
4848
}

cmd/container_scan.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
8+
"codacy/cli-v2/utils/logger"
9+
10+
"github.com/fatih/color"
11+
"github.com/sirupsen/logrus"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
// Flag variables for container-scan command
16+
var (
17+
severityFlag string
18+
pkgTypesFlag string
19+
ignoreUnfixedFlag bool
20+
)
21+
22+
func init() {
23+
containerScanCmd.Flags().StringVar(&severityFlag, "severity", "", "Comma-separated list of severities to scan for (default: HIGH,CRITICAL)")
24+
containerScanCmd.Flags().StringVar(&pkgTypesFlag, "pkg-types", "", "Comma-separated list of package types to scan (default: os)")
25+
containerScanCmd.Flags().BoolVar(&ignoreUnfixedFlag, "ignore-unfixed", true, "Ignore unfixed vulnerabilities")
26+
rootCmd.AddCommand(containerScanCmd)
27+
}
28+
29+
var containerScanCmd = &cobra.Command{
30+
Use: "container-scan [FLAGS] <IMAGE_NAME>",
31+
Short: "Scan container images for vulnerabilities using Trivy",
32+
Long: `Scan container images for vulnerabilities using Trivy.
33+
34+
By default, scans for HIGH and CRITICAL vulnerabilities in OS packages,
35+
ignoring unfixed issues. Use flags to override these defaults.
36+
37+
The --exit-code 1 flag is always applied (not user-configurable) to ensure
38+
the command fails when vulnerabilities are found.`,
39+
Example: ` # Default behavior (HIGH,CRITICAL severity, os packages only)
40+
codacy-cli container-scan myapp:latest
41+
42+
# Scan only for CRITICAL vulnerabilities
43+
codacy-cli container-scan --severity CRITICAL myapp:latest
44+
45+
# Scan all severities and package types
46+
codacy-cli container-scan --severity LOW,MEDIUM,HIGH,CRITICAL --pkg-types os,library myapp:latest
47+
48+
# Include unfixed vulnerabilities
49+
codacy-cli container-scan --ignore-unfixed=false myapp:latest`,
50+
Args: cobra.ExactArgs(1),
51+
Run: runContainerScan,
52+
}
53+
54+
func runContainerScan(cmd *cobra.Command, args []string) {
55+
imageName := args[0]
56+
57+
logger.Info("Starting container scan", logrus.Fields{
58+
"image": imageName,
59+
})
60+
61+
// Check if Trivy is installed
62+
trivyPath, err := exec.LookPath("trivy")
63+
if err != nil {
64+
logger.Error("Trivy not found", logrus.Fields{
65+
"error": err.Error(),
66+
})
67+
color.Red("❌ Error: Trivy is not installed or not found in PATH")
68+
fmt.Println("Please install Trivy to use container scanning.")
69+
fmt.Println("Visit: https://trivy.dev/latest/getting-started/installation/")
70+
os.Exit(1)
71+
}
72+
73+
logger.Info("Found Trivy", logrus.Fields{
74+
"path": trivyPath,
75+
})
76+
77+
// Build Trivy command arguments
78+
trivyArgs := buildTrivyArgs(imageName)
79+
80+
trivyCmd := exec.Command(trivyPath, trivyArgs...)
81+
trivyCmd.Stdout = os.Stdout
82+
trivyCmd.Stderr = os.Stderr
83+
84+
logger.Info("Running Trivy container scan", logrus.Fields{
85+
"command": trivyCmd.String(),
86+
})
87+
88+
fmt.Printf("🔍 Scanning container image: %s\n\n", imageName)
89+
90+
err = trivyCmd.Run()
91+
if err != nil {
92+
// Check if the error is due to exit code 1 (vulnerabilities found)
93+
if exitError, ok := err.(*exec.ExitError); ok {
94+
exitCode := exitError.ExitCode()
95+
logger.Warn("Container scan completed with vulnerabilities", logrus.Fields{
96+
"image": imageName,
97+
"exit_code": exitCode,
98+
})
99+
if exitCode == 1 {
100+
fmt.Println()
101+
color.Red("❌ Scanning failed: vulnerabilities found in the container image")
102+
os.Exit(1)
103+
}
104+
}
105+
106+
// Other errors
107+
logger.Error("Failed to run Trivy", logrus.Fields{
108+
"error": err.Error(),
109+
})
110+
color.Red("❌ Error: Failed to run Trivy: %v", err)
111+
os.Exit(1)
112+
}
113+
114+
logger.Info("Container scan completed successfully", logrus.Fields{
115+
"image": imageName,
116+
})
117+
118+
fmt.Println()
119+
color.Green("✅ Success: No vulnerabilities found matching the specified criteria")
120+
}
121+
122+
// buildTrivyArgs constructs the Trivy command arguments based on flags
123+
func buildTrivyArgs(imageName string) []string {
124+
args := []string{
125+
"image",
126+
"--scanners", "vuln",
127+
}
128+
129+
// Apply --ignore-unfixed if enabled (default: true)
130+
if ignoreUnfixedFlag {
131+
args = append(args, "--ignore-unfixed")
132+
}
133+
134+
// Apply --severity (use default if not specified)
135+
severity := severityFlag
136+
if severity == "" {
137+
severity = "HIGH,CRITICAL"
138+
}
139+
args = append(args, "--severity", severity)
140+
141+
// Apply --pkg-types (use default if not specified)
142+
pkgTypes := pkgTypesFlag
143+
if pkgTypes == "" {
144+
pkgTypes = "os"
145+
}
146+
args = append(args, "--pkg-types", pkgTypes)
147+
148+
// Always apply --exit-code 1 (not user-configurable)
149+
args = append(args, "--exit-code", "1")
150+
151+
// Add the image name as the last argument
152+
args = append(args, imageName)
153+
154+
return args
155+
}

cmd/validation.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func shouldSkipValidation(cmdName string) bool {
8383
"reset", // config reset should work even with empty/invalid codacy.yaml
8484
"codacy-cli", // root command when called without subcommands
8585
"update",
86+
"container-scan", // container scanning doesn't need codacy.yaml
8687
}
8788

8889
for _, skipCmd := range skipCommands {

0 commit comments

Comments
 (0)