@@ -8,10 +8,42 @@ import (
88 "os/exec"
99 "path/filepath"
1010 "runtime"
11+ "strings"
12+
13+ "gopkg.in/yaml.v3"
1114)
1215
16+ // TrivyPluginConfig represents the structure of the trivy plugin.yaml file
17+ type TrivyPluginConfig struct {
18+ Name string `yaml:"name"`
19+ Description string `yaml:"description"`
20+ Downloads []TrivyDownloadConfig `yaml:"downloads"`
21+ ArchMapping map [string ]string `yaml:"arch_mapping"`
22+ Binaries []TrivyBinaryConfig `yaml:"binaries"`
23+ }
24+
25+ // TrivyDownloadConfig represents the download configuration in plugin.yaml
26+ type TrivyDownloadConfig struct {
27+ OS []string `yaml:"os"`
28+ URLTemplate string `yaml:"url_template"`
29+ FileNameTemplate string `yaml:"file_name_template"`
30+ Extension TrivyExtensionConfig `yaml:"extension"`
31+ }
32+
33+ // TrivyExtensionConfig defines the file extension based on OS
34+ type TrivyExtensionConfig struct {
35+ Windows string `yaml:"windows"`
36+ Default string `yaml:"default"`
37+ }
38+
39+ // TrivyBinaryConfig represents a binary executable
40+ type TrivyBinaryConfig struct {
41+ Name string `yaml:"name"`
42+ Path string `yaml:"path"`
43+ }
44+
1345/*
14- * This installs Trivy using the official install script
46+ * This installs Trivy based on the plugin.yaml configuration
1547 */
1648func InstallTrivy (trivyConfig * Runtime , registry string ) error {
1749 log .Println ("Installing Trivy" )
@@ -20,66 +52,156 @@ func InstallTrivy(trivyConfig *Runtime, registry string) error {
2052 trivyFolder := fmt .Sprintf ("%s@%s" , trivyConfig .Name (), trivyConfig .Version ())
2153 installDir := filepath .Join (Config .ToolsDirectory (), trivyFolder )
2254
23- // Check if already installed
24- if isTrivyInstalled (trivyConfig ) {
25- fmt .Printf ("Trivy %s is already installed\n " , trivyConfig .Version ())
26- return nil
27- }
28-
2955 // Create installation directory
3056 err := os .MkdirAll (installDir , 0755 )
3157 if err != nil {
3258 return fmt .Errorf ("failed to create installation directory: %w" , err )
3359 }
3460
35- // Use the official install script to download and install Trivy
36- version := fmt .Sprintf ("v%s" , trivyConfig .Version ())
37- installScriptURL := "https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh"
61+ // Load the plugin configuration
62+ pluginPath := filepath .Join ("plugins" , "tools" , "trivy" , "plugin.yaml" )
63+ pluginConfig , err := loadTrivyPluginConfig (pluginPath )
64+ if err != nil {
65+ return fmt .Errorf ("failed to load Trivy plugin configuration: %w" , err )
66+ }
3867
39- log .Printf ("Installing Trivy %s using the official install script\n " , version )
68+ // Find the download configuration for the current OS
69+ var downloadConfig * TrivyDownloadConfig
70+ currentOS := runtime .GOOS
71+ if currentOS == "darwin" {
72+ currentOS = "macos"
73+ }
74+
75+ for i := range pluginConfig .Downloads {
76+ for _ , os := range pluginConfig .Downloads [i ].OS {
77+ if os == currentOS {
78+ downloadConfig = & pluginConfig .Downloads [i ]
79+ break
80+ }
81+ }
82+ if downloadConfig != nil {
83+ break
84+ }
85+ }
86+
87+ if downloadConfig == nil {
88+ return fmt .Errorf ("no download configuration found for OS %s" , runtime .GOOS )
89+ }
90+
91+ // Get the mapped architecture
92+ arch := runtime .GOARCH
93+ if mappedArch , ok := pluginConfig .ArchMapping [arch ]; ok {
94+ arch = mappedArch
95+ }
96+
97+ // Get the appropriate extension
98+ extension := downloadConfig .Extension .Default
99+ if runtime .GOOS == "windows" && downloadConfig .Extension .Windows != "" {
100+ extension = downloadConfig .Extension .Windows
101+ }
102+
103+ // Template substitution for URL
104+ version := trivyConfig .Version ()
105+ versionWithPrefix := fmt .Sprintf ("v%s" , version )
106+
107+ // Handle template substitution properly
108+ fileName := strings .ReplaceAll (downloadConfig .FileNameTemplate , "{{.Version}}" , version )
109+ fileName = strings .ReplaceAll (fileName , "{{.OS}}" , currentOS )
110+ fileName = strings .ReplaceAll (fileName , "{{.Arch}}" , arch )
111+
112+ // Generate URL, making sure to handle the version format
113+ url := strings .ReplaceAll (downloadConfig .URLTemplate , "{{.Version}}" , versionWithPrefix )
114+ // If URL template already has v prefix, we need to use version without prefix
115+ if strings .Contains (downloadConfig .URLTemplate , "/v{{.Version}}/" ) {
116+ url = strings .ReplaceAll (downloadConfig .URLTemplate , "{{.Version}}" , version )
117+ }
40118
41- // Create a temporary directory for the installation
119+ url = strings .ReplaceAll (url , "{{.FileName}}" , fileName )
120+ url = strings .ReplaceAll (url , "{{.Extension}}" , extension )
121+ url = strings .ReplaceAll (url , "{{.OS}}" , currentOS )
122+ url = strings .ReplaceAll (url , "{{.Arch}}" , arch )
123+
124+ log .Printf ("Using download URL from plugin configuration: %s\n " , url )
125+ log .Printf ("Using filename: %s.%s\n " , fileName , extension )
126+
127+ // Create a temporary directory for the download
42128 tempDir := filepath .Join (installDir , "temp" )
43129 err = os .MkdirAll (tempDir , 0755 )
44130 if err != nil {
45131 return fmt .Errorf ("failed to create temporary directory: %w" , err )
46132 }
133+ defer os .RemoveAll (tempDir )
47134
48- // Download and run the install script
49- cmd := exec .Command ("sh" , "-c" , fmt .Sprintf ("curl -sfL %s | sh -s -- -b %s %s" ,
50- installScriptURL , tempDir , version ))
51-
135+ // Download and extract
136+ downloadPath := filepath .Join (tempDir , fmt .Sprintf ("%s.%s" , fileName , extension ))
137+ cmd := exec .Command ("curl" , "-L" , "-o" , downloadPath , url )
52138 output , err := cmd .CombinedOutput ()
53139 if err != nil {
54- return fmt .Errorf ("failed to run Trivy install script : %w\n Output: %s" , err , string (output ))
140+ return fmt .Errorf ("failed to download Trivy: %w\n Output: %s" , err , string (output ))
55141 }
56142
57- log .Printf ("Install script output : %s\n " , string ( output ) )
143+ log .Printf ("Downloaded Trivy to : %s\n " , downloadPath )
58144
59- // Copy the Trivy binary to the final location
60- sourcePath := filepath .Join (tempDir , "trivy" )
61- if runtime .GOOS == "windows" {
62- sourcePath += ".exe"
145+ // Extract the archive
146+ extractDir := filepath .Join (tempDir , "extracted" )
147+ err = os .MkdirAll (extractDir , 0755 )
148+ if err != nil {
149+ return fmt .Errorf ("failed to create extraction directory: %w" , err )
63150 }
64151
65- binaryPath := filepath .Join (installDir , "trivy" )
152+ if extension == "zip" {
153+ cmd = exec .Command ("unzip" , "-q" , downloadPath , "-d" , extractDir )
154+ } else {
155+ cmd = exec .Command ("tar" , "-xzf" , downloadPath , "-C" , extractDir )
156+ }
157+ output , err = cmd .CombinedOutput ()
158+ if err != nil {
159+ return fmt .Errorf ("failed to extract Trivy: %w\n Output: %s" , err , string (output ))
160+ }
161+
162+ log .Printf ("Extracted Trivy to: %s\n " , extractDir )
163+
164+ // Find the binary from the plugin configuration
165+ var binaryConfig * TrivyBinaryConfig
166+ if len (pluginConfig .Binaries ) > 0 {
167+ binaryConfig = & pluginConfig .Binaries [0 ]
168+ } else {
169+ return fmt .Errorf ("no binary configuration found in the plugin" )
170+ }
171+
172+ // Find the binary in the extracted directory
173+ var binaryPath string
174+ binaryName := binaryConfig .Name
66175 if runtime .GOOS == "windows" {
67- binaryPath += ".exe"
176+ binaryName += ".exe"
68177 }
69178
70- // Check if the source binary exists
71- if _ , err := os .Stat (sourcePath ); os .IsNotExist (err ) {
72- return fmt .Errorf ("trivy binary not found at %s after installation" , sourcePath )
179+ err = filepath .Walk (extractDir , func (path string , info os.FileInfo , err error ) error {
180+ if err != nil {
181+ return err
182+ }
183+ if ! info .IsDir () && filepath .Base (path ) == binaryName {
184+ binaryPath = path
185+ return filepath .SkipDir
186+ }
187+ return nil
188+ })
189+
190+ if binaryPath == "" {
191+ return fmt .Errorf ("could not find %s binary in extracted files" , binaryName )
73192 }
74193
194+ log .Printf ("Found Trivy binary at: %s\n " , binaryPath )
195+
75196 // Copy the binary to the final location
76- source , err := os .Open (sourcePath )
197+ destPath := filepath .Join (installDir , binaryName )
198+ source , err := os .Open (binaryPath )
77199 if err != nil {
78200 return fmt .Errorf ("failed to open source binary: %w" , err )
79201 }
80202 defer source .Close ()
81203
82- destination , err := os .Create (binaryPath )
204+ destination , err := os .Create (destPath )
83205 if err != nil {
84206 return fmt .Errorf ("failed to create destination binary: %w" , err )
85207 }
@@ -90,19 +212,32 @@ func InstallTrivy(trivyConfig *Runtime, registry string) error {
90212 return fmt .Errorf ("failed to copy binary: %w" , err )
91213 }
92214
93- // Make the copied binary executable
94- err = os .Chmod (binaryPath , 0755 )
215+ // Make the binary executable
216+ err = os .Chmod (destPath , 0755 )
95217 if err != nil {
96218 return fmt .Errorf ("failed to make binary executable: %w" , err )
97219 }
98220
99- // Clean up the temporary directory
100- os .RemoveAll (tempDir )
101-
102221 log .Printf ("Successfully installed Trivy %s\n " , trivyConfig .Version ())
103222 return nil
104223}
105224
225+ // loadTrivyPluginConfig loads the Trivy plugin configuration from the plugin.yaml file
226+ func loadTrivyPluginConfig (path string ) (* TrivyPluginConfig , error ) {
227+ data , err := os .ReadFile (path )
228+ if err != nil {
229+ return nil , fmt .Errorf ("error reading plugin config file: %w" , err )
230+ }
231+
232+ var config TrivyPluginConfig
233+ err = yaml .Unmarshal (data , & config )
234+ if err != nil {
235+ return nil , fmt .Errorf ("error parsing plugin config file: %w" , err )
236+ }
237+
238+ return & config , nil
239+ }
240+
106241// isTrivyInstalled checks if Trivy is already installed
107242func isTrivyInstalled (trivyConfig * Runtime ) bool {
108243 trivyFolder := fmt .Sprintf ("%s@%s" , trivyConfig .Name (), trivyConfig .Version ())
0 commit comments