github.com/wolfi-dev/wolfictl@v0.16.11/pkg/cli/advisory_list.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 8 "github.com/spf13/cobra" 9 v2 "github.com/wolfi-dev/wolfictl/pkg/configs/advisory/v2" 10 rwos "github.com/wolfi-dev/wolfictl/pkg/configs/rwfs/os" 11 "github.com/wolfi-dev/wolfictl/pkg/distro" 12 ) 13 14 func cmdAdvisoryList() *cobra.Command { 15 p := &listParams{} 16 cmd := &cobra.Command{ 17 Use: "list", 18 Aliases: []string{"ls"}, 19 Short: "List advisories for specific packages, vulnerabilities, or the entire data set", 20 Long: `List advisories for specific packages, vulnerabilities, or the entire data set. 21 22 The 'list' (or 'ls') command prints a list of advisories based on the given 23 selection criteria. By default, all advisories in the current advisory data set 24 will be listed. 25 26 FILTERING 27 28 You can list advisories for a single package: 29 30 wolfictl adv ls -p glibc 31 32 You can list all advisories for a given vulnerability ID across all packages: 33 34 wolfictl adv ls -V CVE-2023-38545 35 36 You can show only advisories that are considered not to be "resolved": 37 38 wolfictl adv ls --unresolved 39 40 And you can combine the above flags as needed. 41 42 HISTORY 43 44 Using the --history flag, you can list advisory events instead of just 45 advisories' latest states. This is useful for viewing a summary of an 46 investigation over time for a given package/vulnerability match.' 47 `, 48 SilenceErrors: true, 49 Args: cobra.NoArgs, 50 RunE: func(cmd *cobra.Command, _ []string) error { 51 advisoriesRepoDir := resolveAdvisoriesDirInput(p.advisoriesRepoDir) 52 if advisoriesRepoDir == "" { 53 if p.doNotDetectDistro { 54 return fmt.Errorf("no advisories repo dir specified") 55 } 56 57 d, err := distro.Detect() 58 if err != nil { 59 return fmt.Errorf("no advisories repo dir specified, and distro auto-detection failed: %w", err) 60 } 61 62 advisoriesRepoDir = d.Local.AdvisoriesRepo.Dir 63 _, _ = fmt.Fprint(os.Stderr, renderDetectedDistro(d)) 64 } 65 66 advisoriesFsys := rwos.DirFS(advisoriesRepoDir) 67 advisoryCfgs, err := v2.NewIndex(cmd.Context(), advisoriesFsys) 68 if err != nil { 69 return err 70 } 71 72 var cfgs []v2.Document 73 if pkg := p.packageName; pkg != "" { 74 cfgs = advisoryCfgs.Select().WhereName(pkg).Configurations() 75 } else { 76 cfgs = advisoryCfgs.Select().Configurations() 77 } 78 79 var output string 80 81 for _, cfg := range cfgs { 82 for _, adv := range cfg.Advisories { 83 if len(adv.Events) == 0 { 84 // nothing to show 85 continue 86 } 87 88 if p.vuln != "" && !adv.DescribesVulnerability(p.vuln) { 89 // user specified a particular different vulnerability 90 continue 91 } 92 93 if p.unresolved && adv.Resolved() { 94 // user only wants to see unresolved advisories 95 continue 96 } 97 98 vulnID := adv.ID 99 if len(adv.Aliases) > 0 { 100 vulnID += fmt.Sprintf(" (%s)", strings.Join(adv.Aliases, ", ")) 101 } 102 103 if p.history { 104 // user wants to see the full history 105 sorted := adv.SortedEvents() 106 for _, event := range sorted { 107 statusDescription := renderListItem(event) 108 timestamp := event.Timestamp 109 110 output += fmt.Sprintf( 111 "%s: %s: %s @ %s\n", 112 cfg.Package.Name, 113 vulnID, 114 statusDescription, 115 timestamp, 116 ) 117 } 118 119 continue 120 } 121 122 statusDescription := renderListItem(adv.Latest()) 123 output += fmt.Sprintf("%s: %s: %s\n", cfg.Package.Name, vulnID, statusDescription) 124 } 125 } 126 127 fmt.Print(output) 128 return nil 129 }, 130 } 131 132 p.addFlagsTo(cmd) 133 return cmd 134 } 135 136 type listParams struct { 137 doNotDetectDistro bool 138 139 advisoriesRepoDir string 140 141 packageName string 142 vuln string 143 history bool 144 unresolved bool 145 } 146 147 func (p *listParams) addFlagsTo(cmd *cobra.Command) { 148 addNoDistroDetectionFlag(&p.doNotDetectDistro, cmd) 149 150 addAdvisoriesDirFlag(&p.advisoriesRepoDir, cmd) 151 152 addPackageFlag(&p.packageName, cmd) 153 addVulnFlag(&p.vuln, cmd) 154 155 cmd.Flags().BoolVar(&p.history, "history", false, "show full history for advisories") 156 cmd.Flags().BoolVar(&p.unresolved, "unresolved", false, "only show advisories considered to be unresolved") 157 } 158 159 func renderListItem(event v2.Event) string { 160 switch t := event.Type; t { 161 case v2.EventTypeAnalysisNotPlanned, 162 v2.EventTypeFixNotPlanned, 163 v2.EventTypePendingUpstreamFix: 164 return t 165 166 case v2.EventTypeDetection: 167 expanded := "" 168 if data, ok := event.Data.(v2.Detection); ok && data.Type != "" { 169 switch data.Type { 170 case v2.DetectionTypeManual: 171 expanded = "manual" 172 173 case v2.DetectionTypeNVDAPI: 174 if data, ok := data.Data.(v2.DetectionNVDAPI); ok { 175 expanded = fmt.Sprintf("nvdapi: %s", data.CPEFound) 176 } 177 } 178 } 179 return fmt.Sprintf("%s (%s)", t, expanded) 180 181 case v2.EventTypeTruePositiveDetermination: 182 expanded := "" 183 if data, ok := event.Data.(v2.TruePositiveDetermination); ok && data.Note != "" { 184 expanded = data.Note 185 } 186 return fmt.Sprintf("%s (%s)", t, expanded) 187 188 case v2.EventTypeFixed: 189 expanded := "" 190 if data, ok := event.Data.(v2.Fixed); ok && data.FixedVersion != "" { 191 expanded = data.FixedVersion 192 } 193 return fmt.Sprintf("%s (%s)", t, expanded) 194 195 case v2.EventTypeFalsePositiveDetermination: 196 expanded := "" 197 if data, ok := event.Data.(v2.FalsePositiveDetermination); ok && data.Type != "" { 198 expanded = data.Type 199 } 200 return fmt.Sprintf("%s (%s)", t, expanded) 201 } 202 203 return "INVALID EVENT TYPE" 204 }