github.com/quay/claircore@v1.5.28/datastore/postgres/deletemanifests_test.go (about) 1 package postgres 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/quay/zlog" 9 10 "github.com/quay/claircore" 11 "github.com/quay/claircore/indexer" 12 "github.com/quay/claircore/test" 13 "github.com/quay/claircore/test/integration" 14 pgtest "github.com/quay/claircore/test/postgres" 15 ) 16 17 func TestDeleteManifests(t *testing.T) { 18 integration.NeedDB(t) 19 ctx := zlog.Test(context.Background(), t) 20 pool := pgtest.TestIndexerDB(ctx, t) 21 store := NewIndexerStore(pool) 22 defer store.Close(ctx) 23 24 t.Run("Nonexistent", func(t *testing.T) { 25 ctx := zlog.Test(ctx, t) 26 in := []claircore.Digest{ 27 test.RandomSHA256Digest(t), 28 } 29 got, err := store.DeleteManifests(ctx, in...) 30 if err != nil { 31 t.Error(err) 32 } 33 if len(got) != 0 { 34 t.Error(cmp.Diff(got, []claircore.Digest{}, cmpOpts)) 35 } 36 }) 37 t.Run("NonexistentMulti", func(t *testing.T) { 38 ctx := zlog.Test(ctx, t) 39 in := []claircore.Digest{ 40 test.RandomSHA256Digest(t), 41 test.RandomSHA256Digest(t), 42 test.RandomSHA256Digest(t), 43 test.RandomSHA256Digest(t), 44 test.RandomSHA256Digest(t), 45 } 46 got, err := store.DeleteManifests(ctx, in...) 47 if err != nil { 48 t.Error(err) 49 } 50 if len(got) != 0 { 51 t.Error(cmp.Diff(got, []claircore.Digest{}, cmpOpts)) 52 } 53 }) 54 const insertManifest = `INSERT INTO manifest (hash) SELECT unnest($1::TEXT[]);` 55 t.Run("One", func(t *testing.T) { 56 ctx := zlog.Test(ctx, t) 57 want := []claircore.Digest{ 58 test.RandomSHA256Digest(t), 59 } 60 if _, err := pool.Exec(ctx, insertManifest, digestSlice(want)); err != nil { 61 t.Error(err) 62 } 63 got, err := store.DeleteManifests(ctx, want...) 64 if err != nil { 65 t.Error(err) 66 } 67 if !cmp.Equal(got, want, cmpOpts) { 68 t.Error(cmp.Diff(got, want, cmpOpts)) 69 } 70 }) 71 t.Run("Subset", func(t *testing.T) { 72 ctx := zlog.Test(ctx, t) 73 in := make([]claircore.Digest, 8) 74 for i := range in { 75 in[i] = test.RandomSHA256Digest(t) 76 } 77 if _, err := pool.Exec(ctx, insertManifest, digestSlice(in)); err != nil { 78 t.Error(err) 79 } 80 for _, want := range [][]claircore.Digest{in[:4], in[4:]} { 81 arg := append(want[:len(want):len(want)], test.RandomSHA256Digest(t), test.RandomSHA256Digest(t)) 82 got, err := store.DeleteManifests(ctx, arg...) 83 if err != nil { 84 t.Error(err) 85 } 86 if !cmp.Equal(got, want, cmpOpts) { 87 t.Error(cmp.Diff(got, want, cmpOpts)) 88 } 89 } 90 }) 91 const ( 92 insertLayers = `INSERT INTO layer (hash) SELECT unnest($1::TEXT[]);` 93 assoc = `WITH 94 l AS (SELECT id FROM layer WHERE hash = ANY($1::TEXT[])), 95 m AS (SELECT id FROM manifest WHERE hash = $2::TEXT) 96 INSERT INTO manifest_layer (i, manifest_id, layer_id) 97 SELECT ROW_NUMBER() OVER (), m.id, l.id FROM m, l;` 98 ) 99 t.Run("Layers", func(t *testing.T) { 100 const ( 101 nManifests = 8 102 layersPer = 4 103 ) 104 ctx := zlog.Test(ctx, t) 105 ms := make([]claircore.Digest, nManifests) 106 for i := range ms { 107 ms[i] = test.RandomSHA256Digest(t) 108 } 109 ls := make([]claircore.Digest, nManifests+layersPer-1) 110 for i := range ls { 111 ls[i] = test.RandomSHA256Digest(t) 112 } 113 114 if _, err := pool.Exec(ctx, insertManifest, digestSlice(ms)); err != nil { 115 t.Error(err) 116 } 117 if _, err := pool.Exec(ctx, insertLayers, digestSlice(ls)); err != nil { 118 t.Error(err) 119 } 120 var nLayers int 121 if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM layer;`).Scan(&nLayers); err != nil { 122 t.Error(err) 123 } 124 for i, m := range ms { 125 tag, err := pool.Exec(ctx, assoc, digestSlice(ls[i:i+layersPer]), m) 126 t.Logf("affected: %d", tag.RowsAffected()) 127 if err != nil { 128 t.Error(err) 129 } 130 } 131 132 prev := len(ls) 133 for _, m := range ms { 134 want := []claircore.Digest{m} 135 got, err := store.DeleteManifests(ctx, want...) 136 if err != nil { 137 t.Error(err) 138 } 139 if !cmp.Equal(got, want, cmpOpts) { 140 t.Error(cmp.Diff(got, want, cmpOpts)) 141 } 142 var rem int 143 if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM layer;`).Scan(&rem); err != nil { 144 t.Error(err) 145 } 146 if got, want := rem, prev; got >= want { 147 t.Errorf("left overlayers: got: == %d, < want %d", got, want) 148 } 149 prev = rem 150 } 151 152 var rem int 153 if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM layer;`).Scan(&rem); err != nil { 154 t.Error(err) 155 } 156 if got, want := rem, 0; got != want { 157 t.Errorf("left overlayers: got: %d, want %d", got, want) 158 } 159 }) 160 161 const ( 162 checkManifestLayers = `SELECT l.hash FROM layer l 163 JOIN manifest_layer ml ON l.id = ml.layer_id 164 JOIN manifest m ON m.id = ml.manifest_id 165 WHERE m.hash = $1` 166 ) 167 t.Run("Shared base layers", func(t *testing.T) { 168 const ( 169 nManifests = 8 170 nonBaseLayersPer = 3 171 ) 172 ctx := zlog.Test(ctx, t) 173 ms := make([]claircore.Digest, nManifests) 174 for i := range ms { 175 ms[i] = test.RandomSHA256Digest(t) 176 } 177 ls := make([]claircore.Digest, nManifests*nonBaseLayersPer) 178 for i := range ls { 179 ls[i] = test.RandomSHA256Digest(t) 180 } 181 baseLayer := test.RandomSHA256Digest(t) 182 183 if _, err := pool.Exec(ctx, insertManifest, digestSlice(ms)); err != nil { 184 t.Error(err) 185 } 186 if _, err := pool.Exec(ctx, insertLayers, digestSlice(append(ls, baseLayer))); err != nil { 187 t.Error(err) 188 } 189 var nLayers int 190 if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM layer;`).Scan(&nLayers); err != nil { 191 t.Error(err) 192 } 193 li := 0 194 for _, m := range ms { 195 nextLayerIdx := li + nonBaseLayersPer 196 manifestLayers := make([]claircore.Digest, nonBaseLayersPer+1) 197 copy(manifestLayers, ls[li:nextLayerIdx]) 198 tag, err := pool.Exec(ctx, assoc, digestSlice(append(manifestLayers, baseLayer)), m) 199 if err != nil { 200 t.Error(err) 201 } 202 t.Logf("affected: %d", tag.RowsAffected()) 203 li = nextLayerIdx 204 } 205 206 // Delete all but the last manifest 207 toDelete := ms[:len(ms)-1] 208 deleted, err := store.DeleteManifests(ctx, toDelete...) 209 if err != nil { 210 t.Error(err) 211 } 212 if !cmp.Equal(toDelete, deleted, cmpOpts) { 213 t.Error(cmp.Diff(toDelete, deleted, cmpOpts)) 214 } 215 216 rows, err := pool.Query(ctx, checkManifestLayers, ms[len(ms)-1]) 217 if err != nil { 218 t.Error(err) 219 } 220 remainingLayers := []claircore.Digest{} 221 defer rows.Close() 222 for rows.Next() { 223 var ld claircore.Digest 224 err := rows.Scan(&ld) 225 if err != nil { 226 t.Error(err) 227 } 228 remainingLayers = append(remainingLayers, ld) 229 } 230 // To ensure none of the delete operations stepped on the toes of 231 // the final manifest's layers ensure that we've still got 4 layers: 232 // 3 distinct layers and 1 (now un-) shared base layer. 233 if got, want := len(remainingLayers), 4; got != want { 234 t.Errorf("left over layers: got: %d, want %d", got, want) 235 } 236 var foundBaseLayer bool 237 for _, l := range remainingLayers { 238 if l.String() == baseLayer.String() { 239 foundBaseLayer = true 240 } 241 } 242 if !foundBaseLayer { 243 t.Error("accidentally deleted shared base layer") 244 } 245 }) 246 247 t.Run("Manifest index", func(t *testing.T) { 248 const ( 249 layersN = 4 250 manifestsN = 100 251 packageN = 10 252 ) 253 s := NewIndexerStore(pool) 254 ctx := zlog.Test(ctx, t) 255 toDelete := make([]claircore.Digest, manifestsN) 256 for i := 0; i < manifestsN; i++ { 257 ir := &claircore.IndexReport{} 258 ir.Hash = test.RandomSHA256Digest(t) 259 toDelete[i] = ir.Hash 260 ls := make([]claircore.Digest, layersN) 261 for i := range ls { 262 ls[i] = test.RandomSHA256Digest(t) 263 } 264 265 if _, err := pool.Exec(ctx, insertManifest, []claircore.Digest{ir.Hash}); err != nil { 266 t.Error(err) 267 } 268 if _, err := pool.Exec(ctx, insertLayers, digestSlice(ls)); err != nil { 269 t.Error(err) 270 } 271 var nLayers int 272 if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM layer;`).Scan(&nLayers); err != nil { 273 t.Error(err) 274 } 275 tag, err := pool.Exec(ctx, assoc, digestSlice(ls), ir.Hash) 276 t.Logf("affected: %d", tag.RowsAffected()) 277 if err != nil { 278 t.Error(err) 279 } 280 281 scnr := indexer.NewPackageScannerMock("mock", "1", "vulnerability") 282 if err := s.RegisterScanners(ctx, indexer.VersionedScanners{scnr}); err != nil { 283 t.Error(err) 284 } 285 286 pkgs := test.GenUniquePackages(packageN) 287 layer := &claircore.Layer{Hash: ls[0]} 288 if err := s.IndexPackages(ctx, pkgs, layer, scnr); err != nil { 289 t.Error(err) 290 } 291 // Retrieve packages from DB so they are all correctly ID'd 292 if pkgs, err = s.PackagesByLayer(ctx, layer.Hash, []indexer.VersionedScanner{scnr}); err != nil { 293 t.Error(err) 294 } 295 296 pkgMap := make(map[string]*claircore.Package, packageN) 297 envs := make(map[string][]*claircore.Environment, packageN) 298 for _, p := range pkgs { 299 pkgMap[p.ID] = p 300 envs[p.ID] = []*claircore.Environment{ 301 { 302 PackageDB: "pdb", 303 IntroducedIn: ls[0], 304 DistributionID: "d", 305 RepositoryIDs: []string{}, 306 }, 307 } 308 } 309 ir.Packages = pkgMap 310 ir.Environments = envs 311 312 if err := s.IndexManifest(ctx, ir); err != nil { 313 t.Error(err) 314 } 315 } 316 got, err := store.DeleteManifests(ctx, toDelete...) 317 if err != nil { 318 t.Error(err) 319 } 320 if len(got) != manifestsN { 321 t.Error(cmp.Diff(got, toDelete, cmpOpts)) 322 } 323 }) 324 } 325 326 var cmpOpts cmp.Option = cmp.Transformer("DigestTransformer", func(d claircore.Digest) string { return d.String() })