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() })