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 }