github.com/quay/claircore@v1.5.28/libvuln/updates/manager_test.go (about) 1 package updates 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "strconv" 9 "testing" 10 "time" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 "github.com/jackc/pgx/v4/pgxpool" 15 "github.com/quay/zlog" 16 17 "github.com/quay/claircore" 18 19 "github.com/quay/claircore/datastore/postgres" 20 "github.com/quay/claircore/libvuln/driver" 21 "github.com/quay/claircore/pkg/ctxlock" 22 "github.com/quay/claircore/test/integration" 23 pgtest "github.com/quay/claircore/test/postgres" 24 ) 25 26 func TestDeltaUpdates(t *testing.T) { 27 integration.NeedDB(t) 28 ctx := zlog.Test(context.Background(), t) 29 facs := map[string]driver.UpdaterSetFactory{ 30 "delta": &Factory{ 31 vulnGetter: &vulnGetter{ 32 testVulns: testVulns, 33 }, 34 }, 35 } 36 37 pool := pgtest.TestMatcherDB(ctx, t) 38 store := postgres.NewMatcherStore(pool) 39 40 locks, err := ctxlock.New(ctx, pool) 41 if err != nil { 42 t.Fatalf("%v", err) 43 } 44 defer locks.Close(ctx) 45 46 // Using default client here as a non-nil client is an error, 47 // it's never used. 48 mgr, err := NewManager(ctx, store, locks, http.DefaultClient, WithFactories(facs)) 49 if err != nil { 50 t.Fatalf("%v", err) 51 } 52 53 for _, tc := range testVulns { 54 t.Run(tc.testName, func(t *testing.T) { 55 // force update 56 if err := mgr.Run(ctx); err != nil { 57 t.Fatalf("%v", err) 58 } 59 60 vulns, err := getLatestVulnerabilities(ctx, pool, "test/delta/updater") 61 if err != nil { 62 t.Fatalf("%v", err) 63 } 64 if len(vulns) != tc.expectedNumber { 65 t.Fatalf("expecting %d vuln but got %d", tc.expectedNumber, len(vulns)) 66 } 67 }) 68 } 69 70 vulns, err := getLatestVulnerabilities(ctx, pool, "test/delta/updater") 71 if err != nil { 72 t.Fatalf("%v", err) 73 } 74 75 if !cmp.Equal(vulns, finalVulns, 76 cmpopts.IgnoreFields(claircore.Vulnerability{}, "ID"), // Depends on the DB 77 cmpopts.SortSlices(func(a, b interface{}) bool { 78 return a.(*claircore.Vulnerability).Name < b.(*claircore.Vulnerability).Name 79 })) { 80 t.Error(cmp.Diff(vulns, finalVulns)) 81 } 82 } 83 84 var _ driver.DeltaUpdater = (*testUpdater)(nil) 85 var _ driver.Updater = (*testUpdater)(nil) 86 87 type testUpdater struct { 88 vulnGetter *vulnGetter 89 } 90 91 func (tu *testUpdater) Name() string { 92 return "test/delta/updater" 93 } 94 95 // DeltaFetch signals to the manager that we want to use DeltaFetch and store.DeltaUpdateVulnerabilities. 96 func (tu *testUpdater) Fetch(context.Context, driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { 97 // NOOP 98 return nil, "", nil 99 } 100 101 func (tu *testUpdater) Parse(ctx context.Context, vulnUpdates io.ReadCloser) ([]*claircore.Vulnerability, error) { 102 // NOOP 103 return nil, nil 104 } 105 106 func (tu *testUpdater) DeltaParse(ctx context.Context, vulnUpdates io.ReadCloser) ([]*claircore.Vulnerability, []string, error) { 107 newVulns := tu.vulnGetter.Get() 108 return newVulns.vulns, []string{}, nil 109 } 110 111 type Factory struct { 112 vulnGetter *vulnGetter 113 } 114 115 func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c *http.Client) error { 116 return nil 117 } 118 119 func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err error) { 120 s = driver.NewUpdaterSet() 121 s.Add(&testUpdater{ 122 vulnGetter: f.vulnGetter, 123 }) 124 return s, nil 125 } 126 127 type vulnGetter struct { 128 testVulns []*fetchedVulns 129 idx int 130 } 131 132 func (vg *vulnGetter) Get() *fetchedVulns { 133 if vg.idx+1 > len(vg.testVulns) { 134 return nil 135 } 136 defer func() { vg.idx++ }() 137 return vg.testVulns[vg.idx] 138 } 139 140 type fetchedVulns struct { 141 vulns []*claircore.Vulnerability 142 expectedNumber int 143 testName string 144 } 145 146 var testVulns = []*fetchedVulns{ 147 { 148 testName: "initial vuln", 149 vulns: []*claircore.Vulnerability{ 150 { 151 Updater: "test/delta/updater", 152 Name: "CVE-2023:123", 153 Description: "bad things", 154 Issued: time.Time{}, 155 Links: "https://ohno.com/CVE-2023:123 https://moreprobs.io/CVE-2023:123", 156 Severity: "Very Medium", 157 NormalizedSeverity: claircore.Medium, 158 Package: &claircore.Package{ 159 Name: "blah", 160 }, 161 }, 162 }, 163 expectedNumber: 1, 164 }, 165 { 166 testName: "same vuln desc updated", 167 vulns: []*claircore.Vulnerability{ 168 { 169 Updater: "test/delta/updater", 170 Name: "CVE-2023:123", 171 Description: "worse things", 172 Issued: time.Time{}, 173 Links: "https://ohno.com/CVE-2023:123 https://moreprobs.io/CVE-2023:123", 174 Severity: "Very Medium", 175 NormalizedSeverity: claircore.Medium, 176 Package: &claircore.Package{ 177 Name: "blah", 178 }, 179 }, 180 }, 181 expectedNumber: 1, 182 }, 183 { 184 testName: "two new vulns", 185 vulns: []*claircore.Vulnerability{ 186 { 187 Updater: "test/delta/updater", 188 Name: "CVE-2023:456", 189 Description: "problems", 190 Issued: time.Time{}, 191 Links: "https://ohno.com/CVE-2023:456 https://moreprobs.io/CVE-2023:456", 192 Severity: "Very Medium", 193 NormalizedSeverity: claircore.Medium, 194 Package: &claircore.Package{ 195 Name: "blah", 196 }, 197 }, 198 { 199 Updater: "test/delta/updater", 200 Name: "CVE-2023:789", 201 Description: "problems again", 202 Issued: time.Time{}, 203 Links: "https://ohno.com/CVE-2023:789 https://moreprobs.io/CVE-2023:789", 204 Severity: "Very Medium", 205 NormalizedSeverity: claircore.Medium, 206 Package: &claircore.Package{ 207 Name: "blah", 208 }, 209 }, 210 }, 211 expectedNumber: 3, 212 }, 213 { 214 testName: "two updated one new", 215 vulns: []*claircore.Vulnerability{ 216 { 217 Updater: "test/delta/updater", 218 Name: "CVE-2023:456", 219 Description: "problems 2", 220 Issued: time.Time{}, 221 Links: "https://ohno.com/CVE-2023:456 https://moreprobs.io/CVE-2023:456", 222 Severity: "Very Medium", 223 NormalizedSeverity: claircore.Medium, 224 Package: &claircore.Package{ 225 Name: "blah", 226 }, 227 }, 228 { 229 Updater: "test/delta/updater", 230 Name: "CVE-2023:789", 231 Description: "problems again", 232 Issued: time.Time{}, 233 Links: "https://ohno.com/CVE-2023:789 https://moreprobs.io/CVE-2023:789", 234 Severity: "Very Medium", 235 NormalizedSeverity: claircore.Medium, 236 Package: &claircore.Package{ 237 Name: "blah", 238 }, 239 }, 240 { 241 Updater: "test/delta/updater", 242 Name: "CVE-2023:101112", 243 Description: "problems again", 244 Issued: time.Time{}, 245 Links: "https://ohno.com/CVE-2023:101112 https://moreprobs.io/CVE-2023:101112", 246 Severity: "Very Medium", 247 NormalizedSeverity: claircore.Medium, 248 Package: &claircore.Package{ 249 Name: "blah", 250 }, 251 }, 252 }, 253 expectedNumber: 4, 254 }, 255 } 256 257 var finalVulns = []*claircore.Vulnerability{ 258 { 259 Updater: "test/delta/updater", 260 Name: "CVE-2023:123", 261 Description: "worse things", 262 Issued: time.Time{}, 263 Links: "https://ohno.com/CVE-2023:123 https://moreprobs.io/CVE-2023:123", 264 Severity: "Very Medium", 265 NormalizedSeverity: claircore.Medium, 266 Package: &claircore.Package{ 267 Name: "blah", 268 }, 269 Dist: &claircore.Distribution{}, 270 Repo: &claircore.Repository{}, 271 }, 272 { 273 Updater: "test/delta/updater", 274 Name: "CVE-2023:456", 275 Description: "problems 2", 276 Issued: time.Time{}, 277 Links: "https://ohno.com/CVE-2023:456 https://moreprobs.io/CVE-2023:456", 278 Severity: "Very Medium", 279 NormalizedSeverity: claircore.Medium, 280 Package: &claircore.Package{ 281 Name: "blah", 282 }, 283 Dist: &claircore.Distribution{}, 284 Repo: &claircore.Repository{}, 285 }, 286 { 287 Updater: "test/delta/updater", 288 Name: "CVE-2023:789", 289 Description: "problems again", 290 Issued: time.Time{}, 291 Links: "https://ohno.com/CVE-2023:789 https://moreprobs.io/CVE-2023:789", 292 Severity: "Very Medium", 293 NormalizedSeverity: claircore.Medium, 294 Package: &claircore.Package{ 295 Name: "blah", 296 }, 297 Dist: &claircore.Distribution{}, 298 Repo: &claircore.Repository{}, 299 }, 300 { 301 Updater: "test/delta/updater", 302 Name: "CVE-2023:101112", 303 Description: "problems again", 304 Issued: time.Time{}, 305 Links: "https://ohno.com/CVE-2023:101112 https://moreprobs.io/CVE-2023:101112", 306 Severity: "Very Medium", 307 NormalizedSeverity: claircore.Medium, 308 Package: &claircore.Package{ 309 Name: "blah", 310 }, 311 Dist: &claircore.Distribution{}, 312 Repo: &claircore.Repository{}, 313 }, 314 } 315 316 func getLatestVulnerabilities(ctx context.Context, pool *pgxpool.Pool, updater string) ([]*claircore.Vulnerability, error) { 317 query := ` 318 SELECT 319 "vuln"."id", 320 "name", 321 "description", 322 "issued", 323 "links", 324 "severity", 325 "normalized_severity", 326 "package_name", 327 "package_version", 328 "package_module", 329 "package_arch", 330 "package_kind", 331 "dist_id", 332 "dist_name", 333 "dist_version", 334 "dist_version_code_name", 335 "dist_version_id", 336 "dist_arch", 337 "dist_cpe", 338 "dist_pretty_name", 339 "arch_operation", 340 "repo_name", 341 "repo_key", 342 "repo_uri", 343 "fixed_in_version", 344 "vuln"."updater" 345 FROM 346 "vuln" 347 INNER JOIN "uo_vuln" ON ("vuln"."id" = "uo_vuln"."vuln") 348 INNER JOIN "latest_update_operations" ON ( 349 "latest_update_operations"."id" = "uo_vuln"."uo" 350 ) 351 WHERE 352 ( 353 "latest_update_operations"."kind" = 'vulnerability' 354 ) 355 AND 356 ( 357 "vuln"."updater" = $1 358 ) 359 ` 360 results := []*claircore.Vulnerability{} 361 rows, err := pool.Query(ctx, query, updater) 362 if err != nil { 363 return nil, err 364 } 365 defer rows.Close() 366 367 // unpack all returned rows into claircore.Vulnerability structs 368 for rows.Next() { 369 // fully allocate vuln struct 370 v := &claircore.Vulnerability{ 371 Package: &claircore.Package{}, 372 Dist: &claircore.Distribution{}, 373 Repo: &claircore.Repository{}, 374 } 375 376 var id int64 377 err := rows.Scan( 378 &id, 379 &v.Name, 380 &v.Description, 381 &v.Issued, 382 &v.Links, 383 &v.Severity, 384 &v.NormalizedSeverity, 385 &v.Package.Name, 386 &v.Package.Version, 387 &v.Package.Module, 388 &v.Package.Arch, 389 &v.Package.Kind, 390 &v.Dist.DID, 391 &v.Dist.Name, 392 &v.Dist.Version, 393 &v.Dist.VersionCodeName, 394 &v.Dist.VersionID, 395 &v.Dist.Arch, 396 &v.Dist.CPE, 397 &v.Dist.PrettyName, 398 &v.ArchOperation, 399 &v.Repo.Name, 400 &v.Repo.Key, 401 &v.Repo.URI, 402 &v.FixedInVersion, 403 &v.Updater, 404 ) 405 v.ID = strconv.FormatInt(id, 10) 406 if err != nil { 407 return nil, fmt.Errorf("failed to scan vulnerability: %v", err) 408 } 409 results = append(results, v) 410 } 411 412 return results, nil 413 }