github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/cache/redis_test.go (about) 1 package cache_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/alicebob/miniredis/v2" 10 "github.com/go-redis/redis/v8" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 "github.com/devseccon/trivy/pkg/fanal/cache" 15 "github.com/devseccon/trivy/pkg/fanal/types" 16 ) 17 18 const correctHash = "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7" 19 20 func TestRedisCache_PutArtifact(t *testing.T) { 21 type args struct { 22 artifactID string 23 artifactConfig types.ArtifactInfo 24 } 25 tests := []struct { 26 name string 27 setupRedis bool 28 args args 29 wantKey string 30 wantErr string 31 }{ 32 { 33 name: "happy path", 34 setupRedis: true, 35 args: args{ 36 artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", 37 artifactConfig: types.ArtifactInfo{ 38 SchemaVersion: 2, 39 Architecture: "amd64", 40 Created: time.Date(2020, 11, 14, 0, 20, 4, 0, time.UTC), 41 DockerVersion: "19.03.12", 42 OS: "linux", 43 }, 44 }, 45 wantKey: "fanal::artifact::sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", 46 }, 47 { 48 name: "no such host", 49 setupRedis: false, 50 args: args{ 51 artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", 52 artifactConfig: types.ArtifactInfo{}, 53 }, 54 wantErr: "unable to store artifact information in Redis cache", 55 }, 56 } 57 58 // Set up Redis test server 59 s, err := miniredis.Run() 60 require.NoError(t, err) 61 defer s.Close() 62 63 for _, tt := range tests { 64 t.Run(tt.name, func(t *testing.T) { 65 addr := s.Addr() 66 if !tt.setupRedis { 67 addr = "dummy:16379" 68 } 69 70 c := cache.NewRedisCache(&redis.Options{ 71 Addr: addr, 72 }, 0) 73 74 err = c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig) 75 if tt.wantErr != "" { 76 require.NotNil(t, err) 77 assert.Contains(t, err.Error(), tt.wantErr) 78 return 79 } else { 80 assert.NoError(t, err) 81 } 82 83 got, err := s.Get(tt.wantKey) 84 require.NoError(t, err) 85 86 want, err := json.Marshal(tt.args.artifactConfig) 87 require.NoError(t, err) 88 89 assert.JSONEq(t, string(want), got) 90 }) 91 } 92 } 93 94 func TestRedisCache_PutBlob(t *testing.T) { 95 type args struct { 96 blobID string 97 blobConfig types.BlobInfo 98 } 99 tests := []struct { 100 name string 101 setupRedis bool 102 args args 103 wantKey string 104 wantErr string 105 }{ 106 { 107 name: "happy path", 108 setupRedis: true, 109 args: args{ 110 blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 111 blobConfig: types.BlobInfo{ 112 SchemaVersion: 2, 113 Digest: "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", 114 DiffID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 115 OS: types.OS{ 116 Family: "alpine", 117 Name: "3.10.2", 118 }, 119 PackageInfos: []types.PackageInfo{ 120 { 121 FilePath: "lib/apk/db/installed", 122 Packages: []types.Package{ 123 { 124 Name: "musl", 125 Version: "1.1.22-r3", 126 SrcName: "musl", 127 SrcVersion: "1.1.22-r3", 128 }, 129 }, 130 }, 131 }, 132 }, 133 }, 134 wantKey: "fanal::blob::sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 135 }, 136 { 137 name: "no such host", 138 setupRedis: false, 139 args: args{ 140 blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 141 blobConfig: types.BlobInfo{}, 142 }, 143 wantErr: "unable to store blob information in Redis cache", 144 }, 145 } 146 147 // Set up Redis test server 148 s, err := miniredis.Run() 149 require.NoError(t, err) 150 defer s.Close() 151 152 for _, tt := range tests { 153 t.Run(tt.name, func(t *testing.T) { 154 addr := s.Addr() 155 if !tt.setupRedis { 156 addr = "dummy:16379" 157 } 158 159 c := cache.NewRedisCache(&redis.Options{ 160 Addr: addr, 161 }, 0) 162 163 err = c.PutBlob(tt.args.blobID, tt.args.blobConfig) 164 if tt.wantErr != "" { 165 require.NotNil(t, err) 166 assert.Contains(t, err.Error(), tt.wantErr) 167 return 168 } else { 169 assert.NoError(t, err) 170 } 171 172 got, err := s.Get(tt.wantKey) 173 require.NoError(t, err) 174 175 want, err := json.Marshal(tt.args.blobConfig) 176 require.NoError(t, err) 177 178 assert.JSONEq(t, string(want), got) 179 }) 180 } 181 } 182 183 func TestRedisCache_GetArtifact(t *testing.T) { 184 info := types.ArtifactInfo{ 185 SchemaVersion: 2, 186 Architecture: "amd64", 187 Created: time.Date(2020, 11, 14, 0, 20, 4, 0, time.UTC), 188 DockerVersion: "19.03.12", 189 OS: "linux", 190 } 191 192 tests := []struct { 193 name string 194 setupRedis bool 195 artifactID string 196 want types.ArtifactInfo 197 wantErr string 198 }{ 199 { 200 name: "happy path", 201 setupRedis: true, 202 artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", 203 want: info, 204 }, 205 { 206 name: "malformed JSON", 207 setupRedis: true, 208 artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", 209 wantErr: "failed to unmarshal artifact", 210 }, 211 { 212 name: "no such host", 213 setupRedis: false, 214 artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", 215 wantErr: "failed to get artifact from the Redis cache", 216 }, 217 { 218 name: "nonexistent key", 219 setupRedis: true, 220 artifactID: "sha256:foo", 221 wantErr: "artifact (sha256:foo) is missing in Redis cache", 222 }, 223 } 224 225 // Set up Redis test server 226 s, err := miniredis.Run() 227 require.NoError(t, err) 228 defer s.Close() 229 230 // Set key/value pairs 231 b, err := json.Marshal(info) 232 require.NoError(t, err) 233 234 s.Set("fanal::artifact::sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", string(b)) 235 s.Set("fanal::artifact::sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", "foobar") 236 237 for _, tt := range tests { 238 t.Run(tt.name, func(t *testing.T) { 239 addr := s.Addr() 240 if !tt.setupRedis { 241 addr = "dummy:16379" 242 } 243 244 c := cache.NewRedisCache(&redis.Options{ 245 Addr: addr, 246 }, 0) 247 248 got, err := c.GetArtifact(tt.artifactID) 249 if tt.wantErr != "" { 250 require.NotNil(t, err) 251 assert.Contains(t, err.Error(), tt.wantErr) 252 return 253 } else { 254 assert.NoError(t, err) 255 } 256 257 assert.Equal(t, tt.want, got) 258 }) 259 } 260 } 261 262 func TestRedisCache_GetBlob(t *testing.T) { 263 blobInfo := types.BlobInfo{ 264 SchemaVersion: 2, 265 Digest: "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", 266 DiffID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 267 OS: types.OS{ 268 Family: "alpine", 269 Name: "3.10.2", 270 }, 271 PackageInfos: []types.PackageInfo{ 272 { 273 FilePath: "lib/apk/db/installed", 274 Packages: []types.Package{ 275 { 276 Name: "musl", 277 Version: "1.1.22-r3", 278 SrcName: "musl", 279 SrcVersion: "1.1.22-r3", 280 }, 281 }, 282 }, 283 }, 284 } 285 286 tests := []struct { 287 name string 288 setupRedis bool 289 blobID string 290 want types.BlobInfo 291 wantErr string 292 }{ 293 { 294 name: "happy path", 295 setupRedis: true, 296 blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 297 want: blobInfo, 298 }, 299 { 300 name: "malformed JSON", 301 setupRedis: true, 302 blobID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", 303 wantErr: "failed to unmarshal blob", 304 }, 305 { 306 name: "no such host", 307 setupRedis: false, 308 blobID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", 309 wantErr: "failed to get blob from the Redis cache", 310 }, 311 { 312 name: "nonexistent key", 313 setupRedis: true, 314 blobID: "sha256:foo", 315 wantErr: "blob (sha256:foo) is missing in Redis cache", 316 }, 317 } 318 319 // Set up Redis test server 320 s, err := miniredis.Run() 321 require.NoError(t, err) 322 defer s.Close() 323 324 // Set key/value pairs 325 b, err := json.Marshal(blobInfo) 326 require.NoError(t, err) 327 s.Set("fanal::blob::sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", string(b)) 328 s.Set("fanal::blob::sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", "foobar") 329 330 for _, tt := range tests { 331 t.Run(tt.name, func(t *testing.T) { 332 addr := s.Addr() 333 if !tt.setupRedis { 334 addr = "dummy:16379" 335 } 336 337 c := cache.NewRedisCache(&redis.Options{ 338 Addr: addr, 339 }, 0) 340 341 got, err := c.GetBlob(tt.blobID) 342 if tt.wantErr != "" { 343 require.NotNil(t, err) 344 assert.Contains(t, err.Error(), tt.wantErr) 345 return 346 } 347 348 assert.NoError(t, err) 349 assert.Equal(t, tt.want, got) 350 }) 351 } 352 } 353 354 func TestRedisCache_MissingBlobs(t *testing.T) { 355 type args struct { 356 artifactID string 357 blobIDs []string 358 } 359 tests := []struct { 360 name string 361 setupRedis bool 362 args args 363 wantMissingArtifact bool 364 wantMissingBlobIDs []string 365 wantErr string 366 }{ 367 { 368 name: "missing both", 369 setupRedis: true, 370 args: args{ 371 artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4/1", 372 blobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, 373 }, 374 wantMissingArtifact: true, 375 wantMissingBlobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, 376 }, 377 { 378 name: "missing artifact", 379 setupRedis: true, 380 args: args{ 381 artifactID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4/1", 382 blobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11111"}, 383 }, 384 wantMissingArtifact: true, 385 }, 386 { 387 name: "missing blobs", 388 setupRedis: true, 389 args: args{ 390 artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/1", 391 blobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, 392 }, 393 wantMissingArtifact: false, 394 wantMissingBlobIDs: []string{"sha256:1b3ee35aacca9866b01dd96e870136266bde18006ac2f0d6eb706c798d1fa3c3/11111"}, 395 }, 396 { 397 name: "missing artifact with different schema version", 398 setupRedis: true, 399 args: args{ 400 artifactID: "sha256:be4e4bea2c2e15b403bb321562e78ea84b501fb41497472e91ecb41504e8a27c/1", 401 blobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11111"}, 402 }, 403 wantMissingArtifact: true, 404 }, 405 { 406 name: "missing blobs with different schema version", 407 setupRedis: true, 408 args: args{ 409 artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/1", 410 blobIDs: []string{"sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02/11111"}, 411 }, 412 wantMissingArtifact: false, 413 wantMissingBlobIDs: []string{"sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02/11111"}, 414 }, 415 { 416 name: "different analyzer versions", 417 setupRedis: true, 418 args: args{ 419 artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/0", 420 blobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11012"}, 421 }, 422 wantMissingArtifact: true, 423 wantMissingBlobIDs: []string{"sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11012"}, 424 }, 425 } 426 427 // Set up Redis test server 428 s, err := miniredis.Run() 429 require.NoError(t, err) 430 defer s.Close() 431 432 s.Set("fanal::artifact::sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf/1", 433 fmt.Sprintf("{\"SchemaVersion\": %d}", types.ArtifactJSONSchemaVersion)) 434 s.Set("fanal::artifact::sha256:be4e4bea2c2e15b403bb321562e78ea84b501fb41497472e91ecb41504e8a27c/1", 435 `{"SchemaVersion": 999999}`) // This version should not match the current version 436 s.Set("fanal::blob::sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0/11111", 437 fmt.Sprintf("{\"SchemaVersion\": %d}", types.BlobJSONSchemaVersion)) 438 s.Set("fanal::blob::sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02/11111", 439 `{"SchemaVersion": 999999}`) // This version should not match the current version 440 441 for _, tt := range tests { 442 t.Run(tt.name, func(t *testing.T) { 443 addr := s.Addr() 444 if !tt.setupRedis { 445 addr = "dummy:6379" 446 } 447 448 c := cache.NewRedisCache(&redis.Options{ 449 Addr: addr, 450 }, 0) 451 452 missingArtifact, missingBlobIDs, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) 453 if tt.wantErr != "" { 454 require.NotNil(t, err) 455 assert.Contains(t, err.Error(), tt.wantErr) 456 return 457 } 458 459 assert.NoError(t, err) 460 assert.Equal(t, tt.wantMissingArtifact, missingArtifact) 461 assert.Equal(t, tt.wantMissingBlobIDs, missingBlobIDs) 462 }) 463 } 464 } 465 466 func TestRedisCache_Close(t *testing.T) { 467 // Set up Redis test server 468 s, err := miniredis.Run() 469 require.NoError(t, err) 470 defer s.Close() 471 472 t.Run("close", func(t *testing.T) { 473 c := cache.NewRedisCache(&redis.Options{ 474 Addr: s.Addr(), 475 }, 0) 476 closeErr := c.Close() 477 require.NoError(t, closeErr) 478 time.Sleep(3 * time.Second) // give it some time 479 assert.Equal(t, 0, s.CurrentConnectionCount(), "The client is disconnected") 480 }) 481 } 482 483 func TestRedisCache_Clear(t *testing.T) { 484 // Set up Redis test server 485 s, err := miniredis.Run() 486 require.NoError(t, err) 487 defer s.Close() 488 489 for i := 0; i < 200; i++ { 490 s.Set(fmt.Sprintf("fanal::key%d", i), "value") 491 } 492 s.Set("foo", "bar") 493 494 t.Run("clear", func(t *testing.T) { 495 c := cache.NewRedisCache(&redis.Options{ 496 Addr: s.Addr(), 497 }, 0) 498 require.NoError(t, c.Clear()) 499 for i := 0; i < 200; i++ { 500 assert.False(t, s.Exists(fmt.Sprintf("fanal::key%d", i))) 501 } 502 assert.True(t, s.Exists("foo")) 503 }) 504 } 505 506 func TestRedisCache_DeleteBlobs(t *testing.T) { 507 type args struct { 508 blobIDs []string 509 } 510 tests := []struct { 511 name string 512 setupRedis bool 513 args args 514 wantKey string 515 wantErr string 516 }{ 517 { 518 name: "happy path", 519 setupRedis: true, 520 args: args{ 521 blobIDs: []string{correctHash}, 522 }, 523 wantKey: "fanal::blob::" + correctHash, 524 }, 525 { 526 name: "no such host", 527 setupRedis: false, 528 args: args{ 529 blobIDs: []string{"sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae800"}, 530 }, 531 wantErr: "unable to delete blob", 532 }, 533 } 534 535 // Set up Redis test server 536 s, err := miniredis.Run() 537 require.NoError(t, err) 538 defer s.Close() 539 540 s.Set(correctHash, "any string") 541 542 for _, tt := range tests { 543 t.Run(tt.name, func(t *testing.T) { 544 addr := s.Addr() 545 if !tt.setupRedis { 546 addr = "dummy:16379" 547 } 548 549 c := cache.NewRedisCache(&redis.Options{ 550 Addr: addr, 551 }, 0) 552 553 err = c.DeleteBlobs(tt.args.blobIDs) 554 if tt.wantErr != "" { 555 require.Error(t, err) 556 assert.Contains(t, err.Error(), tt.wantErr) 557 return 558 } 559 assert.NoError(t, err) 560 }) 561 } 562 }