github.com/quay/claircore@v1.5.28/datastore/postgres/getupdateoperationdiff.go (about) 1 package postgres 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/google/uuid" 10 "github.com/jackc/pgx/v4" 11 "github.com/jackc/pgx/v4/pgxpool" 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/prometheus/client_golang/prometheus/promauto" 14 15 "github.com/quay/claircore" 16 "github.com/quay/claircore/libvuln/driver" 17 ) 18 19 var ( 20 getUpdateDiffCounter = promauto.NewCounterVec( 21 prometheus.CounterOpts{ 22 Namespace: "claircore", 23 Subsystem: "vulnstore", 24 Name: "getupdatediff_total", 25 Help: "Total number of database queries issued in the getUpdateDiff method.", 26 }, 27 []string{"query"}, 28 ) 29 getUpdateDiffDuration = promauto.NewHistogramVec( 30 prometheus.HistogramOpts{ 31 Namespace: "claircore", 32 Subsystem: "vulnstore", 33 Name: "getupdatediff_duration_seconds", 34 Help: "The duration of all queries issued in the getUpdateDiff method", 35 }, 36 []string{"query"}, 37 ) 38 populateRefsCounter = promauto.NewCounterVec( 39 prometheus.CounterOpts{ 40 Namespace: "claircore", 41 Subsystem: "vulnstore", 42 Name: "populaterefs_total", 43 Help: "Total number of database queries issued in the populateRefs method.", 44 }, 45 []string{"query"}, 46 ) 47 populateRefsDuration = promauto.NewHistogramVec( 48 prometheus.HistogramOpts{ 49 Namespace: "claircore", 50 Subsystem: "vulnstore", 51 Name: "populaterefs_duration_seconds", 52 Help: "The duration of all queries issued in the populateRefs method", 53 }, 54 []string{"query"}, 55 ) 56 ) 57 58 func (s *MatcherStore) GetUpdateDiff(ctx context.Context, prev, cur uuid.UUID) (*driver.UpdateDiff, error) { 59 // confirmRefs will return a row only if both refs are kind = 'vulnerability' 60 // therefore, if a pgx.ErrNoRows is returned from this query, at least one 61 // of the incoming refs is not of kind = 'vulnerability'. 62 const confirmRefs = ` 63 SELECT 1 64 WHERE ROW ('vulnerability') = ALL (SELECT kind FROM update_operation WHERE ref = $1 OR ref = $2); 65 ` 66 // Query takes two update IDs and returns rows that only exist in first 67 // argument's set of vulnerabilities. 68 const query = `WITH 69 lhs AS (SELECT id, updater FROM update_operation WHERE ref = $1), 70 rhs AS (SELECT id, updater FROM update_operation WHERE ref = $2) 71 SELECT 72 id, 73 name, 74 updater, 75 description, 76 issued, 77 links, 78 severity, 79 normalized_severity, 80 package_name, 81 package_version, 82 package_module, 83 package_arch, 84 package_kind, 85 dist_id, 86 dist_name, 87 dist_version, 88 dist_version_code_name, 89 dist_version_id, 90 dist_arch, 91 dist_cpe, 92 dist_pretty_name, 93 arch_operation, 94 repo_name, 95 repo_key, 96 repo_uri, 97 fixed_in_version 98 FROM vuln 99 WHERE 100 vuln.id IN ( 101 SELECT vuln AS id FROM uo_vuln JOIN lhs ON (uo_vuln.uo = lhs.id) 102 EXCEPT ALL 103 SELECT vuln AS id FROM uo_vuln JOIN rhs ON (uo_vuln.uo = rhs.id) 104 ) 105 AND ( 106 vuln.updater = (SELECT updater FROM rhs) 107 OR vuln.updater = (SELECT updater FROM lhs) 108 ); 109 ` 110 111 if cur == uuid.Nil { 112 return nil, errors.New("nil uuid is invalid as \"current\" endpoint") 113 } 114 115 // confirm both refs are of type == 'vulnerability' 116 start := time.Now() 117 rows, err := s.pool.Query(ctx, confirmRefs, cur, prev) 118 switch err { 119 case nil: 120 rows.Close() 121 case pgx.ErrNoRows: 122 return nil, fmt.Errorf("provided ref was not of kind 'vulnerability'") 123 default: 124 return nil, fmt.Errorf("failed to confirm update op ref types: %w", err) 125 } 126 getUpdateDiffCounter.WithLabelValues("confirmrefs").Add(1) 127 getUpdateDiffDuration.WithLabelValues("confirmrefs").Observe(time.Since(start).Seconds()) 128 129 // Retrieve added first. 130 var diff driver.UpdateDiff 131 if err := populateRefs(ctx, &diff, s.pool, prev, cur); err != nil { 132 return nil, err 133 } 134 135 rows, err = s.pool.Query(ctx, query, cur, prev) 136 if err != nil { 137 return nil, fmt.Errorf("failed to retrieve added vulnerabilities: %w", err) 138 } 139 140 getUpdateDiffCounter.WithLabelValues("query").Add(1) 141 getUpdateDiffDuration.WithLabelValues("query").Observe(time.Since(start).Seconds()) 142 143 defer rows.Close() 144 145 for rows.Next() { 146 v := claircore.Vulnerability{ 147 Package: &claircore.Package{}, 148 Dist: &claircore.Distribution{}, 149 Repo: &claircore.Repository{}, 150 } 151 if err := scanVulnerability(&v, rows); err != nil { 152 return nil, fmt.Errorf("failed to scan added vulnerability: %v", err) 153 } 154 diff.Added = append(diff.Added, v) 155 } 156 if err := rows.Err(); err != nil { 157 return nil, err 158 } 159 rows.Close() // OK according to the docs. 160 161 // If we're starting at the beginning of time, nothing is going to 162 // be removed. 163 if prev == uuid.Nil { 164 return &diff, nil 165 } 166 rows, err = s.pool.Query(ctx, query, prev, cur) 167 if err != nil { 168 return nil, fmt.Errorf("failed to retrieve removed vulnerabilities: %w", err) 169 } 170 defer rows.Close() 171 for rows.Next() { 172 v := claircore.Vulnerability{ 173 Package: &claircore.Package{}, 174 Dist: &claircore.Distribution{}, 175 Repo: &claircore.Repository{}, 176 } 177 if err := scanVulnerability(&v, rows); err != nil { 178 return nil, fmt.Errorf("failed to scan removed vulnerability: %v", err) 179 } 180 diff.Removed = append(diff.Removed, v) 181 } 182 if err := rows.Err(); err != nil { 183 return nil, err 184 } 185 186 return &diff, nil 187 } 188 189 // PopulateRefs fills in the provided UpdateDiff with the details of the 190 // operations indicated by the two refs. 191 func populateRefs(ctx context.Context, diff *driver.UpdateDiff, pool *pgxpool.Pool, prev, cur uuid.UUID) error { 192 const query = `SELECT updater, fingerprint, date FROM update_operation WHERE ref = $1;` 193 var err error 194 195 diff.Cur.Ref = cur 196 start := time.Now() 197 err = pool.QueryRow(ctx, query, cur).Scan( 198 &diff.Cur.Updater, 199 &diff.Cur.Fingerprint, 200 &diff.Cur.Date, 201 ) 202 switch { 203 case err == nil: 204 case errors.Is(err, pgx.ErrNoRows): 205 return fmt.Errorf("operation %v does not exist", cur) 206 default: 207 return fmt.Errorf("failed to scan current UpdateOperation: %w", err) 208 } 209 populateRefsCounter.WithLabelValues("query").Add(1) 210 populateRefsDuration.WithLabelValues("query").Observe(time.Since(start).Seconds()) 211 212 if prev == uuid.Nil { 213 return nil 214 } 215 diff.Prev.Ref = prev 216 217 start = time.Now() 218 err = pool.QueryRow(ctx, query, prev).Scan( 219 &diff.Prev.Updater, 220 &diff.Prev.Fingerprint, 221 &diff.Prev.Date, 222 ) 223 switch { 224 case err == nil: 225 case errors.Is(err, pgx.ErrNoRows): 226 return fmt.Errorf("operation %v does not exist", prev) 227 default: 228 return fmt.Errorf("failed to scan previous UpdateOperation: %w", err) 229 } 230 populateRefsCounter.WithLabelValues("query").Add(1) 231 populateRefsDuration.WithLabelValues("query").Observe(time.Since(start).Seconds()) 232 233 return nil 234 }