github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/db/db_test.go (about)

     1  package db_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	v1 "github.com/google/go-containerregistry/pkg/v1"
    10  	fakei "github.com/google/go-containerregistry/pkg/v1/fake"
    11  	"github.com/google/go-containerregistry/pkg/v1/tarball"
    12  	"github.com/google/go-containerregistry/pkg/v1/types"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"k8s.io/utils/clock"
    16  	clocktesting "k8s.io/utils/clock/testing"
    17  
    18  	tdb "github.com/aquasecurity/trivy-db/pkg/db"
    19  	"github.com/aquasecurity/trivy-db/pkg/metadata"
    20  	"github.com/devseccon/trivy/pkg/db"
    21  	ftypes "github.com/devseccon/trivy/pkg/fanal/types"
    22  	"github.com/devseccon/trivy/pkg/oci"
    23  )
    24  
    25  const mediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip"
    26  
    27  type fakeLayer struct {
    28  	v1.Layer
    29  }
    30  
    31  func (f fakeLayer) MediaType() (types.MediaType, error) {
    32  	return mediaType, nil
    33  }
    34  
    35  func newFakeLayer(t *testing.T, input string) v1.Layer {
    36  	layer, err := tarball.LayerFromFile(input)
    37  	require.NoError(t, err)
    38  
    39  	return fakeLayer{layer}
    40  }
    41  
    42  func TestClient_NeedsUpdate(t *testing.T) {
    43  	timeNextUpdateDay1 := time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC)
    44  	timeNextUpdateDay2 := time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC)
    45  
    46  	tests := []struct {
    47  		name     string
    48  		skip     bool
    49  		clock    clock.Clock
    50  		metadata metadata.Metadata
    51  		want     bool
    52  		wantErr  string
    53  	}{
    54  		{
    55  			name:  "happy path",
    56  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
    57  			metadata: metadata.Metadata{
    58  				Version:    tdb.SchemaVersion,
    59  				NextUpdate: timeNextUpdateDay1,
    60  			},
    61  			want: true,
    62  		},
    63  		{
    64  			name:     "happy path for first run",
    65  			clock:    clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
    66  			metadata: metadata.Metadata{},
    67  			want:     true,
    68  		},
    69  		{
    70  			name:  "happy path with old schema version",
    71  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
    72  			metadata: metadata.Metadata{
    73  				Version:    0,
    74  				NextUpdate: timeNextUpdateDay1,
    75  			},
    76  			want: true,
    77  		},
    78  		{
    79  			name:  "happy path with --skip-update",
    80  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
    81  			metadata: metadata.Metadata{
    82  				Version:    tdb.SchemaVersion,
    83  				NextUpdate: timeNextUpdateDay1,
    84  			},
    85  			skip: true,
    86  			want: false,
    87  		},
    88  		{
    89  			name:  "skip downloading DB",
    90  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
    91  			metadata: metadata.Metadata{
    92  				Version:    tdb.SchemaVersion,
    93  				NextUpdate: timeNextUpdateDay2,
    94  			},
    95  			want: false,
    96  		},
    97  		{
    98  			name:  "newer schema version",
    99  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
   100  			metadata: metadata.Metadata{
   101  				Version:    tdb.SchemaVersion + 1,
   102  				NextUpdate: timeNextUpdateDay2,
   103  			},
   104  			wantErr: fmt.Sprintf("the version of DB schema doesn't match. Local DB: %d, Expected: %d",
   105  				tdb.SchemaVersion+1, tdb.SchemaVersion),
   106  		},
   107  		{
   108  			name:     "--skip-update on the first run",
   109  			clock:    clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
   110  			metadata: metadata.Metadata{},
   111  			skip:     true,
   112  			wantErr:  "--skip-update cannot be specified on the first run",
   113  		},
   114  		{
   115  			name:  "--skip-update with different schema version",
   116  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
   117  			metadata: metadata.Metadata{
   118  				Version:    0,
   119  				NextUpdate: timeNextUpdateDay1,
   120  			},
   121  			skip: true,
   122  			wantErr: fmt.Sprintf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d",
   123  				0, tdb.SchemaVersion),
   124  		},
   125  		{
   126  			name:  "happy with old DownloadedAt",
   127  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
   128  			metadata: metadata.Metadata{
   129  				Version:      tdb.SchemaVersion,
   130  				NextUpdate:   timeNextUpdateDay1,
   131  				DownloadedAt: time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC),
   132  			},
   133  			want: true,
   134  		},
   135  		{
   136  			name:  "skip downloading DB with recent DownloadedAt",
   137  			clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
   138  			metadata: metadata.Metadata{
   139  				Version:      tdb.SchemaVersion,
   140  				NextUpdate:   timeNextUpdateDay1,
   141  				DownloadedAt: time.Date(2019, 9, 30, 23, 30, 0, 0, time.UTC),
   142  			},
   143  			want: false,
   144  		},
   145  	}
   146  
   147  	for _, tt := range tests {
   148  		t.Run(tt.name, func(t *testing.T) {
   149  			cacheDir := t.TempDir()
   150  			if tt.metadata != (metadata.Metadata{}) {
   151  				meta := metadata.NewClient(cacheDir)
   152  				err := meta.Update(tt.metadata)
   153  				require.NoError(t, err)
   154  			}
   155  
   156  			client := db.NewClient(cacheDir, true, db.WithClock(tt.clock))
   157  			needsUpdate, err := client.NeedsUpdate("test", tt.skip)
   158  
   159  			switch {
   160  			case tt.wantErr != "":
   161  				require.Error(t, err)
   162  				assert.Contains(t, err.Error(), tt.wantErr, tt.name)
   163  			default:
   164  				assert.NoError(t, err, tt.name)
   165  			}
   166  
   167  			assert.Equal(t, tt.want, needsUpdate)
   168  		})
   169  	}
   170  }
   171  
   172  func TestClient_Download(t *testing.T) {
   173  	timeDownloadedAt := clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC))
   174  
   175  	tests := []struct {
   176  		name    string
   177  		input   string
   178  		want    metadata.Metadata
   179  		wantErr string
   180  	}{
   181  		{
   182  			name:  "happy path",
   183  			input: "testdata/db.tar.gz",
   184  			want: metadata.Metadata{
   185  				Version:      1,
   186  				NextUpdate:   time.Date(3000, 1, 1, 18, 5, 43, 198355188, time.UTC),
   187  				UpdatedAt:    time.Date(3000, 1, 1, 12, 5, 43, 198355588, time.UTC),
   188  				DownloadedAt: time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC),
   189  			},
   190  		},
   191  		{
   192  			name:    "invalid gzip",
   193  			input:   "testdata/trivy.db",
   194  			wantErr: "unexpected EOF",
   195  		},
   196  	}
   197  
   198  	for _, tt := range tests {
   199  		t.Run(tt.name, func(t *testing.T) {
   200  			cacheDir := t.TempDir()
   201  
   202  			// Mock image
   203  			img := new(fakei.FakeImage)
   204  			img.LayersReturns([]v1.Layer{newFakeLayer(t, tt.input)}, nil)
   205  			img.ManifestReturns(&v1.Manifest{
   206  				Layers: []v1.Descriptor{
   207  					{
   208  						MediaType: "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip",
   209  						Size:      100,
   210  						Digest: v1.Hash{
   211  							Algorithm: "sha256",
   212  							Hex:       "aec482bc254b5dd025d3eaf5bb35997d3dba783e394e8f91d5a415963151bfb8",
   213  						},
   214  						Annotations: map[string]string{
   215  							"org.opencontainers.image.title": "db.tar.gz",
   216  						},
   217  					},
   218  				},
   219  			}, nil)
   220  
   221  			// Mock OCI artifact
   222  			opt := ftypes.RegistryOptions{
   223  				Insecure: false,
   224  			}
   225  			art, err := oci.NewArtifact("db", true, opt, oci.WithImage(img))
   226  			require.NoError(t, err)
   227  
   228  			client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt))
   229  			err = client.Download(context.Background(), cacheDir, opt)
   230  			if tt.wantErr != "" {
   231  				require.Error(t, err)
   232  				assert.Contains(t, err.Error(), tt.wantErr)
   233  				return
   234  			}
   235  			assert.NoError(t, err)
   236  
   237  			meta := metadata.NewClient(cacheDir)
   238  			got, err := meta.Get()
   239  			require.NoError(t, err)
   240  
   241  			assert.Equal(t, tt.want, got)
   242  		})
   243  	}
   244  }