mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-01-31 13:53:14 +08:00
feat: include registry and repository in artifact ID calculation (#9689)
Co-authored-by: knqyf263 <knqyf263@users.noreply.github.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
@@ -227,29 +226,17 @@ func TestDockerEngine(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
imageName := tt.input
|
||||
if !tt.invalidImage {
|
||||
testfile, err := os.Open(tt.input)
|
||||
require.NoError(t, err, tt.name)
|
||||
defer testfile.Close()
|
||||
|
||||
// Ensure image doesn't already exist
|
||||
cli.ImageRemove(t, ctx, tt.input)
|
||||
|
||||
// Load image into docker engine
|
||||
loadedImage := cli.ImageLoad(t, ctx, tt.input)
|
||||
|
||||
// Tag our image to something unique
|
||||
err = cli.ImageTag(ctx, loadedImage, tt.input)
|
||||
require.NoError(t, err, tt.name)
|
||||
|
||||
// Cleanup
|
||||
t.Cleanup(func() { cli.ImageRemove(t, ctx, tt.input) })
|
||||
// Removes any existing images with conflicting RepoTags and loading images
|
||||
imageName = cli.ImageCleanLoad(t, ctx, tt.input)
|
||||
}
|
||||
|
||||
osArgs := []string{
|
||||
"image",
|
||||
imageName,
|
||||
"--cache-dir",
|
||||
cacheDir,
|
||||
"image",
|
||||
"--quiet",
|
||||
"--skip-db-update",
|
||||
"--format=json",
|
||||
@@ -291,26 +278,22 @@ func TestDockerEngine(t *testing.T) {
|
||||
}...)
|
||||
}
|
||||
|
||||
osArgs = append(osArgs, tt.input)
|
||||
|
||||
// Run Trivy
|
||||
runTest(t, osArgs, tt.golden, types.FormatJSON, runOptions{
|
||||
wantErr: tt.wantErr,
|
||||
fakeUUID: "3ff14136-e09f-4df9-80ea-%012d",
|
||||
// Image config fields were removed
|
||||
override: overrideFuncs(overrideUID, overrideDockerRemovedFields, overrideDockerEngineRepoTags),
|
||||
override: overrideFuncs(overrideUID, overrideDockerRemovedFields, func(t *testing.T, want, got *types.Report) {
|
||||
// Override ArtifactName to match the archive file path
|
||||
got.ArtifactName = tt.input
|
||||
|
||||
// Override Result.Target for each result to match golden file expectations
|
||||
require.Len(t, got.Results, len(want.Results))
|
||||
for i := range got.Results {
|
||||
got.Results[i].Target = want.Results[i].Target
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// overrideDockerEngineRepoTags removes test-specific RepoTags that are added during the test.
|
||||
// In TestDockerEngine, we tag the loaded image with the archive file path (e.g., "testdata/fixtures/images/alpine-39.tar.gz")
|
||||
// for test purposes. This function filters out these test tags from the actual results to match the expected
|
||||
// RepoTags from the archive's manifest.json.
|
||||
func overrideDockerEngineRepoTags(_ *testing.T, _, got *types.Report) {
|
||||
// Keep only tags that don't start with "testdata/fixtures/images/"
|
||||
got.Metadata.RepoTags = lo.Filter(got.Metadata.RepoTags, func(tag string, _ int) bool {
|
||||
return !strings.HasPrefix(tag, "testdata/fixtures/images/")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -249,7 +249,13 @@ func TestRegistry(t *testing.T) {
|
||||
runTest(t, osArgs, tt.golden, types.FormatJSON, runOptions{
|
||||
wantErr: tt.wantErr,
|
||||
fakeUUID: "3ff14136-e09f-4df9-80ea-%012d",
|
||||
override: overrideFuncs(overrideUID, func(_ *testing.T, want, _ *types.Report) {
|
||||
override: overrideFuncs(overrideUID, func(_ *testing.T, want, got *types.Report) {
|
||||
// Exclude ArtifactID from comparison because registry tests use random ports
|
||||
// (e.g., localhost:54321/alpine:3.10), which causes RepoTags and the calculated
|
||||
// Artifact ID to vary on each test run.
|
||||
got.ArtifactID = ""
|
||||
want.ArtifactID = ""
|
||||
|
||||
want.ArtifactName = s
|
||||
want.Metadata.RepoTags = []string{s}
|
||||
for i := range want.Results {
|
||||
|
||||
2
integration/testdata/almalinux-8.json.golden
vendored
2
integration/testdata/almalinux-8.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:4ca63ce1d8a90da2ed4f2d5e93e8e9db2f32d0fabf0718a2edebbe0e70826622",
|
||||
"ArtifactID": "sha256:fb75459277a4cbcf98182b48c789cfbd4b34414e05898e1231ae8b2ca099f4e7",
|
||||
"ArtifactName": "testdata/fixtures/images/almalinux-8.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/alpine-310.json.golden
vendored
2
integration/testdata/alpine-310.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4",
|
||||
"ArtifactID": "sha256:39549bf49d696f172a6513103cdc8f53717024ad1fbce62d680a8e7ddde1a612",
|
||||
"ArtifactName": "testdata/fixtures/images/alpine-310.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1",
|
||||
"ArtifactID": "sha256:988a8e3eb049d90c20fafb183d0e792c99b8ba28433be1d1e4447a8b5a1adbdf",
|
||||
"ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1",
|
||||
"ArtifactID": "sha256:988a8e3eb049d90c20fafb183d0e792c99b8ba28433be1d1e4447a8b5a1adbdf",
|
||||
"ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1",
|
||||
"ArtifactID": "sha256:988a8e3eb049d90c20fafb183d0e792c99b8ba28433be1d1e4447a8b5a1adbdf",
|
||||
"ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/alpine-39.json.golden
vendored
2
integration/testdata/alpine-39.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1",
|
||||
"ArtifactID": "sha256:988a8e3eb049d90c20fafb183d0e792c99b8ba28433be1d1e4447a8b5a1adbdf",
|
||||
"ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:22848737c0d272ad5d7c7369d8ca830a62929e63e38edcb22085139a6ae0688d",
|
||||
"ArtifactID": "sha256:0edd1906378dca3abc435f47f2e4b91059e9950e55cd82c76089d60b9ca68f90",
|
||||
"ArtifactName": "testdata/fixtures/images/alpine-distroless.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/amazon-1.json.golden
vendored
2
integration/testdata/amazon-1.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:961c4ee06269351d858969ea0426878675ed708d3a140246eabbc0bfc352bffa",
|
||||
"ArtifactID": "sha256:5a0fd7bb415c9b52d1bb909e40b9f498a89a5572724bd107d26ead4a25f203e1",
|
||||
"ArtifactName": "testdata/fixtures/images/amazon-1.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/amazon-2.json.golden
vendored
2
integration/testdata/amazon-2.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:b94321659aca6a89cb7650a5b864bc8ec4bf62c620b8f1a01530c2e90a88c391",
|
||||
"ArtifactID": "sha256:87e7ebcf8b5c0a26985fd80875e09e11850fa4828e1156da190f85b17dcecb71",
|
||||
"ArtifactName": "testdata/fixtures/images/amazon-2.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:88702f6b6133bf06cc46af48437d0c0fc661239155548757c65916504a0e5eee",
|
||||
"ArtifactID": "sha256:4f807ebe9dfe3b25af3d4354d6cff8288e9a8c63477dabcde5e63998f0e68188",
|
||||
"ArtifactName": "testdata/fixtures/images/busybox-with-lockfile.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/centos-6.json.golden
vendored
2
integration/testdata/centos-6.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:5bf9684f472089d6d5cb636041d3d6dc748dbde39f1aefc374bbd367bd2aabbf",
|
||||
"ArtifactID": "sha256:6fd360ff01eb63800aaafdb5e58af85fab9d7849344b577f5f6077cceb0399bb",
|
||||
"ArtifactName": "testdata/fixtures/images/centos-6.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427",
|
||||
"ArtifactID": "sha256:8af996fb4a61e515887a173fdea3d5111c90c76e9f8247b3f668b17ab8215946",
|
||||
"ArtifactName": "testdata/fixtures/images/centos-7.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427",
|
||||
"ArtifactID": "sha256:8af996fb4a61e515887a173fdea3d5111c90c76e9f8247b3f668b17ab8215946",
|
||||
"ArtifactName": "testdata/fixtures/images/centos-7.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/centos-7.json.golden
vendored
2
integration/testdata/centos-7.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:f1cb7c7d58b73eac859c395882eec49d50651244e342cd6c68a5c7809785f427",
|
||||
"ArtifactID": "sha256:8af996fb4a61e515887a173fdea3d5111c90c76e9f8247b3f668b17ab8215946",
|
||||
"ArtifactName": "testdata/fixtures/images/centos-7.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:c2c03a296d2329a4f3ab72a7bf38b78a8a80108204d326b0139d6af700e152d1",
|
||||
"ArtifactID": "sha256:b75d2c78a42eae6eaa44e99638ba5fa36900538fa0b7a4feba19d18dd588552d",
|
||||
"ArtifactName": "testdata/fixtures/images/debian-buster.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:c2c03a296d2329a4f3ab72a7bf38b78a8a80108204d326b0139d6af700e152d1",
|
||||
"ArtifactID": "sha256:b75d2c78a42eae6eaa44e99638ba5fa36900538fa0b7a4feba19d18dd588552d",
|
||||
"ArtifactName": "testdata/fixtures/images/debian-buster.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:f26939cc87ef44a6fc554eedd0a976ab30b5bc2769d65d2e986b6c5f1fd4053d",
|
||||
"ArtifactID": "sha256:e7f0b65012f754f3e69bfa9e94999ba080f12cd7e6ac2742d5cb908252f9609f",
|
||||
"ArtifactName": "testdata/fixtures/images/debian-stretch.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:7f04a8d247173b1f2546d22913af637bbab4e7411e00ae6207da8d94c445750d",
|
||||
"ArtifactID": "sha256:6c3688715cb42ea1466b96eb45b39d8afc9f8cdcf723df8464fb26391711a7db",
|
||||
"ArtifactName": "testdata/fixtures/images/distroless-base.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:6fcac2cc8a710f21577b5bbd534e0bfc841c0cca569b57182ba19054696cddda",
|
||||
"ArtifactID": "sha256:b9ec0b7f93064fddace988e9a901386ccd55a6c16a34c00d5c45b06f62ec20ca",
|
||||
"ArtifactName": "testdata/fixtures/images/distroless-python27.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933",
|
||||
"ArtifactID": "sha256:7a550fb73ac2bf3e1fe50c96a8a5ba699be62cbb09ba9bd982557e574c904b3d",
|
||||
"ArtifactName": "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/mariner-1.0.json.golden
vendored
2
integration/testdata/mariner-1.0.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:8cdcbf18341ed8afa5322e7b0077f8ef3f46896882c921df5f97c51b369f6767",
|
||||
"ArtifactID": "sha256:357e30db7fb673e279ababa1128b33c8acc2fce50826727ec57ef24f5213fe0d",
|
||||
"ArtifactName": "testdata/fixtures/images/mariner-1.0.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:fef5ad254f6378f08071cfa2daaf05a1ce9857141c944b67a40742e63e65cecc",
|
||||
"ArtifactID": "sha256:b9f31768f6c3908af7de80cff1a9e53c62f46d1bc54aaf3b97b2c922ed9cf1fd",
|
||||
"ArtifactName": "testdata/fixtures/images/opensuse-leap-151.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:580e73f5c823232e6587136e9f5428a89afdf77a123bb8575d08208e0cc34b12",
|
||||
"ArtifactID": "sha256:1ab435ff0da0a8c95292bfd8a3b270b457cfca575a4a68731dd2fc142e2c13ef",
|
||||
"ArtifactName": "testdata/fixtures/images/opensuse-tumbleweed.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:8988c7081e1f7b6c2928cbc4832b8a05968bb589d45d444ca1e3027c68f97f56",
|
||||
"ArtifactID": "sha256:2982b2b5fd16f59d6b9ccdef6292e710791eb7fa12895a593959c5bceb7780e6",
|
||||
"ArtifactName": "testdata/fixtures/images/oraclelinux-8.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/photon-30.json.golden
vendored
2
integration/testdata/photon-30.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:5ccb5186b75cd13ff0d028f5b5b2bdf7ef7ca2b3d56eb2c6eb6c136077a6991a",
|
||||
"ArtifactID": "sha256:1d4bc53b38b27a97aca270bea3abf3e9ea14964d02c71d2c93cd7bc53b74d660",
|
||||
"ArtifactName": "testdata/fixtures/images/photon-30.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:210996f98b856d7cd00496ddbe9412e73f1c714c95de09661e07b4e43648f9ab",
|
||||
"ArtifactID": "sha256:ed11998b28b0bcd0488c4d2fda300f80d71ac058ce7eb12a43a4deb312ce429c",
|
||||
"ArtifactName": "testdata/fixtures/images/rockylinux-8.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:c45ec974938acac29c893b5d273d73e4ebdd7e6a97b6fa861dfbd8dd430b9016",
|
||||
"ArtifactID": "sha256:f3d716e4652bf4a60bf4289eace7bb2b46878fa9461c86b12b4c059126563aee",
|
||||
"ArtifactName": "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:ed8f0747d483b60657982f0ef1ba74482aed08795cf0eb774b00bc53022a8351",
|
||||
"ArtifactID": "sha256:d5fbfb11da0ffb72f8bdeae29420a8101a04534c281854fe5ace22ec13d133bb",
|
||||
"ArtifactName": "testdata/fixtures/images/spring4shell-jre11.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:b88bc3d2f0b5aacf1d36efa498f427d923b01c854dac090acf5368c55ac04fda",
|
||||
"ArtifactID": "sha256:8ac64b751688d0e2577befd47be60baea54f7294bee6deeb0288b580daf6ca52",
|
||||
"ArtifactName": "testdata/fixtures/images/spring4shell-jre8.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
@@ -308,15 +308,6 @@
|
||||
"Target": "",
|
||||
"Class": "custom",
|
||||
"CustomResources": [
|
||||
{
|
||||
"Type": "spring4shell/tomcat-version",
|
||||
"FilePath": "/usr/local/tomcat/RELEASE-NOTES",
|
||||
"Layer": {
|
||||
"Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06",
|
||||
"DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6"
|
||||
},
|
||||
"Data": "8.5.77"
|
||||
},
|
||||
{
|
||||
"Type": "spring4shell/java-major-version",
|
||||
"FilePath": "/usr/local/openjdk-8/release",
|
||||
@@ -325,6 +316,15 @@
|
||||
"DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1"
|
||||
},
|
||||
"Data": "1.8.0_322"
|
||||
},
|
||||
{
|
||||
"Type": "spring4shell/tomcat-version",
|
||||
"FilePath": "/usr/local/tomcat/RELEASE-NOTES",
|
||||
"Layer": {
|
||||
"Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06",
|
||||
"DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6"
|
||||
},
|
||||
"Data": "8.5.77"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:6fecccc91c83e11ae4fede6793e9410841221d4779520c2b9e9fb7f7b3830264",
|
||||
"ArtifactID": "sha256:b4b4762f4769903a61d4605927079c4e60d007d9b12f65c39714e5311fa3d0ca",
|
||||
"ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/ubi-7.json.golden
vendored
2
integration/testdata/ubi-7.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:6fecccc91c83e11ae4fede6793e9410841221d4779520c2b9e9fb7f7b3830264",
|
||||
"ArtifactID": "sha256:b4b4762f4769903a61d4605927079c4e60d007d9b12f65c39714e5311fa3d0ca",
|
||||
"ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:a2a15febcdf362f6115e801d37b5e60d6faaeedcb9896155e5fe9d754025be12",
|
||||
"ArtifactID": "sha256:61d0ff5073e754270b3b59487ac8049c50e12bca1cf792d84ec9a62dad36a8a1",
|
||||
"ArtifactName": "testdata/fixtures/images/ubuntu-1804.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
2
integration/testdata/ubuntu-1804.json.golden
vendored
2
integration/testdata/ubuntu-1804.json.golden
vendored
@@ -2,7 +2,7 @@
|
||||
"SchemaVersion": 2,
|
||||
"ReportID": "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
|
||||
"ArtifactID": "sha256:a2a15febcdf362f6115e801d37b5e60d6faaeedcb9896155e5fe9d754025be12",
|
||||
"ArtifactID": "sha256:61d0ff5073e754270b3b59487ac8049c50e12bca1cf792d84ec9a62dad36a8a1",
|
||||
"ArtifactName": "testdata/fixtures/images/ubuntu-1804.tar.gz",
|
||||
"ArtifactType": "container_image",
|
||||
"Metadata": {
|
||||
|
||||
@@ -3,13 +3,17 @@ package testutil
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
gzutil "github.com/aquasecurity/trivy/pkg/fanal/utils/gzip"
|
||||
)
|
||||
|
||||
type DockerClient struct {
|
||||
@@ -22,6 +26,8 @@ func NewDockerClient(t *testing.T) *DockerClient {
|
||||
return &DockerClient{Client: cli}
|
||||
}
|
||||
|
||||
// ImageLoad loads a Docker image from a tar archive file into the Docker engine.
|
||||
// It automatically registers cleanup via t.Cleanup() to remove the loaded image after the test.
|
||||
func (c *DockerClient) ImageLoad(t *testing.T, ctx context.Context, imageFile string) string {
|
||||
t.Helper()
|
||||
testfile, err := os.Open(imageFile)
|
||||
@@ -43,6 +49,7 @@ func (c *DockerClient) ImageLoad(t *testing.T, ctx context.Context, imageFile st
|
||||
loadedImage = strings.TrimSpace(loadedImage)
|
||||
require.NotEmpty(t, loadedImage, data.Stream)
|
||||
|
||||
// Register cleanup to remove the loaded image after the test
|
||||
t.Cleanup(func() { c.ImageRemove(t, ctx, loadedImage) })
|
||||
|
||||
return loadedImage
|
||||
@@ -55,3 +62,29 @@ func (c *DockerClient) ImageRemove(t *testing.T, ctx context.Context, imageID st
|
||||
PruneChildren: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ImageCleanLoad performs a clean load of a Docker image from a tar archive.
|
||||
// It removes any existing images with conflicting RepoTags before loading,
|
||||
// ensuring the loaded image has the correct RepoTags from the archive.
|
||||
// It automatically registers cleanup via t.Cleanup() to remove the loaded image after the test.
|
||||
func (c *DockerClient) ImageCleanLoad(t *testing.T, ctx context.Context, archivePath string) string {
|
||||
t.Helper()
|
||||
|
||||
// Extract RepoTags from archive
|
||||
opener := func() (io.ReadCloser, error) {
|
||||
return gzutil.OpenFile(archivePath)
|
||||
}
|
||||
|
||||
manifest, err := tarball.LoadManifest(opener)
|
||||
require.NoError(t, err, "failed to load manifest from archive")
|
||||
|
||||
// Remove existing images with the same RepoTags to avoid conflicts
|
||||
for _, m := range manifest {
|
||||
for _, tag := range m.RepoTags {
|
||||
c.ImageRemove(t, ctx, tag)
|
||||
}
|
||||
}
|
||||
|
||||
// Load image
|
||||
return c.ImageLoad(t, ctx, archivePath)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/image"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -65,3 +70,11 @@ func imageName(img, subpath, tag, digest string) string {
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// MustParseReference parses a string into a Reference and fails the test if there's an error
|
||||
func MustParseReference(t *testing.T, s string) image.Reference {
|
||||
t.Helper()
|
||||
ref, err := image.ParseReference(s)
|
||||
require.NoError(t, err)
|
||||
return ref
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/image"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/walker"
|
||||
"github.com/aquasecurity/trivy/pkg/misconf"
|
||||
@@ -103,6 +104,7 @@ type ImageMetadata struct {
|
||||
DiffIDs []string // uncompressed layer IDs
|
||||
RepoTags []string
|
||||
RepoDigests []string
|
||||
Reference image.Reference // image reference matching the artifact name
|
||||
ConfigFile v1.ConfigFile
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -146,6 +148,10 @@ func (a Artifact) Inspect(ctx context.Context) (ref artifact.Reference, err erro
|
||||
return artifact.Reference{}, xerrors.Errorf("analyze error: %w", err)
|
||||
}
|
||||
|
||||
repoTags := a.image.RepoTags()
|
||||
repoDigests := a.image.RepoDigests()
|
||||
imgRef := a.findMatchingReference(a.image.Name(), repoTags, repoDigests)
|
||||
|
||||
return artifact.Reference{
|
||||
Name: a.image.Name(),
|
||||
Type: types.TypeContainerImage,
|
||||
@@ -154,8 +160,9 @@ func (a Artifact) Inspect(ctx context.Context) (ref artifact.Reference, err erro
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: imageID,
|
||||
DiffIDs: diffIDs,
|
||||
RepoTags: a.image.RepoTags(),
|
||||
RepoDigests: a.image.RepoDigests(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Reference: imgRef,
|
||||
ConfigFile: *configFile,
|
||||
},
|
||||
}, nil
|
||||
@@ -165,6 +172,101 @@ func (a Artifact) Clean(_ artifact.Reference) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// findMatchingReference finds a RepoTag or RepoDigest that matches the artifact name
|
||||
func (a Artifact) findMatchingReference(artifactName string, repoTags, repoDigests []string) image.Reference {
|
||||
// Convert strings to typed references
|
||||
parsedTags := a.parseRepoTags(repoTags)
|
||||
parsedDigests := a.parseRepoDigests(repoDigests)
|
||||
|
||||
ref := a.findMatchingRepoReference(artifactName, parsedTags, parsedDigests)
|
||||
return image.NewReference(ref)
|
||||
}
|
||||
|
||||
// parseRepoTags parses repo tags into name.Tag
|
||||
func (a Artifact) parseRepoTags(repoTags []string) []name.Tag {
|
||||
return lo.FilterMap(repoTags, func(tagStr string, _ int) (name.Tag, bool) {
|
||||
tag, err := name.NewTag(tagStr)
|
||||
if err != nil {
|
||||
a.logger.Debug("Failed to parse repo tag", log.String("tag", tagStr), log.Err(err))
|
||||
return name.Tag{}, false
|
||||
}
|
||||
return tag, true
|
||||
})
|
||||
}
|
||||
|
||||
// parseRepoDigests parses repo digests into name.Digest
|
||||
func (a Artifact) parseRepoDigests(repoDigests []string) []name.Digest {
|
||||
return lo.FilterMap(repoDigests, func(digestStr string, _ int) (name.Digest, bool) {
|
||||
digest, err := name.NewDigest(digestStr)
|
||||
if err != nil {
|
||||
a.logger.Debug("Failed to parse repo digest", log.String("digest", digestStr), log.Err(err))
|
||||
return name.Digest{}, false
|
||||
}
|
||||
return digest, true
|
||||
})
|
||||
}
|
||||
|
||||
// findMatchingRepoReference finds a RepoTag or RepoDigest that matches the artifact name
|
||||
func (a Artifact) findMatchingRepoReference(artifactName string, repoTags []name.Tag, repoDigests []name.Digest) name.Reference {
|
||||
// If there are no RepoTags or RepoDigests, return nil
|
||||
if len(repoTags) == 0 && len(repoDigests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select the first available reference as fallback
|
||||
fallback := cmp.Or[name.Reference](lo.FirstOrEmpty(repoTags), lo.FirstOrEmpty(repoDigests))
|
||||
|
||||
// TODO(knqyf263): refactor to use a more robust method instead of suffix-based detection
|
||||
// Check if artifact name looks like a file path (tar archive)
|
||||
archiveExts := []string{
|
||||
".tar",
|
||||
".gz",
|
||||
".gzip",
|
||||
".tgz",
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(artifactName))
|
||||
if slices.Contains(archiveExts, ext) {
|
||||
// For file paths, use the first RepoTag or RepoDigest
|
||||
return fallback
|
||||
}
|
||||
|
||||
// Try to parse the artifact name as an image reference
|
||||
artifactRef, err := name.ParseReference(artifactName)
|
||||
if err != nil {
|
||||
// If parsing fails, use the first RepoTag or RepoDigest
|
||||
a.logger.Debug("Failed to parse artifact name as image reference, using first RepoTag",
|
||||
log.String("name", artifactName), log.Err(err))
|
||||
return fallback
|
||||
}
|
||||
|
||||
artifactRefName := artifactRef.Name()
|
||||
|
||||
switch artifactRef.(type) {
|
||||
case name.Digest:
|
||||
// Try to find a matching digest from RepoDigests
|
||||
if digest, ok := lo.Find(repoDigests, func(d name.Digest) bool {
|
||||
return artifactRefName == d.Name()
|
||||
}); ok {
|
||||
return digest
|
||||
}
|
||||
case name.Tag:
|
||||
// Try to find a matching tag from RepoTags
|
||||
if tag, ok := lo.Find(repoTags, func(t name.Tag) bool {
|
||||
return artifactRefName == t.Name()
|
||||
}); ok {
|
||||
return tag
|
||||
}
|
||||
}
|
||||
|
||||
// If no matching tag/digest found, use the first RepoTag or RepoDigest as fallback
|
||||
// This also handles the case when the artifact is specified by image ID (e.g., `trivy image sha256:abc123`)
|
||||
a.logger.Debug("No matching repo tag/digest found for artifact, using first one",
|
||||
log.String("name", artifactName),
|
||||
log.String("ref", artifactRefName),
|
||||
log.String("fallback", fallback.String()))
|
||||
return fallback
|
||||
}
|
||||
|
||||
func (a Artifact) calcCacheKeys(imageID string, diffIDs []string) (string, []string, error) {
|
||||
// Pass an empty config scanner option so that the cache key can be the same, even when policies are updated.
|
||||
imageKey, err := cache.CalcKey(imageID, artifactVersion, a.configAnalyzer.AnalyzerVersions(), nil, artifact.Option{})
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/cachetest"
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
|
||||
@@ -444,6 +445,7 @@ func TestArtifact_Inspect(t *testing.T) {
|
||||
},
|
||||
RepoTags: []string{"alpine:3.11"},
|
||||
RepoDigests: nil,
|
||||
Reference: testutil.MustParseReference(t, "alpine:3.11"),
|
||||
ConfigFile: v1.ConfigFile{
|
||||
Architecture: "amd64",
|
||||
Author: "",
|
||||
@@ -1776,6 +1778,7 @@ func TestArtifact_Inspect(t *testing.T) {
|
||||
},
|
||||
RepoTags: []string{"vuln-image:latest"},
|
||||
RepoDigests: nil,
|
||||
Reference: testutil.MustParseReference(t, "vuln-image:latest"),
|
||||
ConfigFile: v1.ConfigFile{
|
||||
Architecture: "amd64",
|
||||
Author: "",
|
||||
@@ -1933,6 +1936,7 @@ func TestArtifact_Inspect(t *testing.T) {
|
||||
},
|
||||
RepoTags: []string{"vuln-image:latest"},
|
||||
RepoDigests: nil,
|
||||
Reference: testutil.MustParseReference(t, "vuln-image:latest"),
|
||||
ConfigFile: v1.ConfigFile{
|
||||
Architecture: "amd64",
|
||||
Author: "",
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/utils"
|
||||
gzutil "github.com/aquasecurity/trivy/pkg/fanal/utils/gzip"
|
||||
)
|
||||
|
||||
type dockerArchive struct {
|
||||
@@ -42,22 +39,6 @@ func tryDockerArchive(fileName string) (v1.Image, error) {
|
||||
|
||||
func fileOpener(fileName string) func() (io.ReadCloser, error) {
|
||||
return func() (io.ReadCloser, error) {
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("unable to open the file: %w", err)
|
||||
}
|
||||
|
||||
var r io.Reader
|
||||
br := bufio.NewReader(f)
|
||||
r = br
|
||||
|
||||
if utils.IsGzip(br) {
|
||||
r, err = gzip.NewReader(br)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return nil, xerrors.Errorf("failed to open gzip: %w", err)
|
||||
}
|
||||
}
|
||||
return io.NopCloser(r), nil
|
||||
return gzutil.OpenFile(fileName)
|
||||
}
|
||||
}
|
||||
|
||||
62
pkg/fanal/image/reference.go
Normal file
62
pkg/fanal/image/reference.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Reference wraps name.Reference to support JSON marshaling/unmarshaling
|
||||
type Reference struct {
|
||||
name.Reference
|
||||
}
|
||||
|
||||
// NewReference creates a new Reference from name.Reference
|
||||
func NewReference(ref name.Reference) Reference {
|
||||
return Reference{Reference: ref}
|
||||
}
|
||||
|
||||
// ParseReference parses a string into a Reference
|
||||
func ParseReference(s string) (Reference, error) {
|
||||
if s == "" {
|
||||
return Reference{}, nil
|
||||
}
|
||||
ref, err := name.ParseReference(s)
|
||||
if err != nil {
|
||||
return Reference{}, xerrors.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
return Reference{Reference: ref}, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler
|
||||
func (r Reference) MarshalJSON() ([]byte, error) {
|
||||
if lo.IsNil(r.Reference) {
|
||||
return json.Marshal("")
|
||||
}
|
||||
return json.Marshal(r.Reference.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler
|
||||
func (r *Reference) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return xerrors.Errorf("failed to unmarshal reference: %w", err)
|
||||
}
|
||||
if s == "" {
|
||||
r.Reference = nil
|
||||
return nil
|
||||
}
|
||||
ref, err := name.ParseReference(s)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
r.Reference = ref
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the reference is empty
|
||||
func (r Reference) IsEmpty() bool {
|
||||
return lo.IsNil(r.Reference)
|
||||
}
|
||||
198
pkg/fanal/image/reference_test.go
Normal file
198
pkg/fanal/image/reference_test.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package image_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/image"
|
||||
)
|
||||
|
||||
func TestReference_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ref image.Reference
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "valid reference with tag",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:latest"),
|
||||
want: `"ghcr.io/aquasecurity/trivy:latest"`,
|
||||
},
|
||||
{
|
||||
name: "valid reference with digest",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
want: `"ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000"`,
|
||||
},
|
||||
{
|
||||
name: "empty reference",
|
||||
ref: image.Reference{},
|
||||
want: `""`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := json.Marshal(tt.ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReference_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
want string
|
||||
wantIsEmpty bool
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "valid reference with tag",
|
||||
json: `"ghcr.io/aquasecurity/trivy:latest"`,
|
||||
want: "ghcr.io/aquasecurity/trivy:latest",
|
||||
wantIsEmpty: false,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "valid reference with digest",
|
||||
json: `"ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000"`,
|
||||
want: "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||
wantIsEmpty: false,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "empty reference",
|
||||
json: `""`,
|
||||
want: "",
|
||||
wantIsEmpty: true,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "invalid reference",
|
||||
json: `"not a valid reference!"`,
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var r image.Reference
|
||||
err := json.Unmarshal([]byte(tt.json), &r)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.wantIsEmpty, r.IsEmpty())
|
||||
if !r.IsEmpty() {
|
||||
assert.Equal(t, tt.want, r.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReference_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ref image.Reference
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ghcr.io with tag",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:latest"),
|
||||
want: "ghcr.io/aquasecurity/trivy:latest",
|
||||
},
|
||||
{
|
||||
name: "ghcr.io with digest",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
want: "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
{
|
||||
name: "docker.io implicit",
|
||||
ref: testutil.MustParseReference(t, "aquasecurity/trivy:latest"),
|
||||
want: "aquasecurity/trivy:latest",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, tt.ref.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReference_Context(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ref image.Reference
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ghcr.io with tag",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:latest"),
|
||||
want: "ghcr.io/aquasecurity/trivy",
|
||||
},
|
||||
{
|
||||
name: "ghcr.io with digest",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
want: "ghcr.io/aquasecurity/trivy",
|
||||
},
|
||||
{
|
||||
name: "docker.io implicit",
|
||||
ref: testutil.MustParseReference(t, "aquasecurity/trivy:latest"),
|
||||
want: "index.docker.io/aquasecurity/trivy",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, tt.ref.Context().String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReference_IsEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ref image.Reference
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "non-empty reference",
|
||||
ref: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:latest"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty reference",
|
||||
ref: image.Reference{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, tt.ref.IsEmpty())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReference_JSONRoundTrip(t *testing.T) {
|
||||
ref := testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:v0.65.0")
|
||||
|
||||
// Marshal to JSON
|
||||
data, err := json.Marshal(ref)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Unmarshal from JSON
|
||||
var decoded image.Reference
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the decoded reference matches the original
|
||||
assert.Equal(t, ref.String(), decoded.String())
|
||||
assert.Equal(t, ref.Context().String(), decoded.Context().String())
|
||||
}
|
||||
@@ -310,6 +310,7 @@ func localImageTestWithNamespace(t *testing.T, namespace string) {
|
||||
},
|
||||
RepoTags: []string{testutil.ImageName("", "alpine-310", "")},
|
||||
RepoDigests: []string{testutil.ImageName("", "", "sha256:f12582b2f2190f350e3904462c1c23aaf366b4f76705e97b199f9bbded1d816a")},
|
||||
Reference: testutil.MustParseReference(t, testutil.ImageName("", "alpine-310", "")),
|
||||
ConfigFile: v1.ConfigFile{
|
||||
Architecture: "amd64",
|
||||
Created: v1.Time{
|
||||
@@ -354,6 +355,7 @@ func localImageTestWithNamespace(t *testing.T, namespace string) {
|
||||
tarArchive: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz",
|
||||
wantMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:c17083664da903e13e9092fa3a3a1aeee2431aa2728298e3dbcec72f26369c41",
|
||||
Reference: testutil.MustParseReference(t, testutil.ImageName("", "vulnimage", "")),
|
||||
DiffIDs: []string{
|
||||
"sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888",
|
||||
"sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33",
|
||||
@@ -765,6 +767,7 @@ func TestContainerd_PullImage(t *testing.T) {
|
||||
},
|
||||
RepoTags: []string{testutil.ImageName("", "alpine-310", "")},
|
||||
RepoDigests: []string{testutil.ImageName("", "", "sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb")},
|
||||
Reference: testutil.MustParseReference(t, testutil.ImageName("", "alpine-310", "")),
|
||||
ConfigFile: v1.ConfigFile{
|
||||
Architecture: "amd64",
|
||||
Created: v1.Time{
|
||||
|
||||
54
pkg/fanal/utils/gzip/gzip.go
Normal file
54
pkg/fanal/utils/gzip/gzip.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package gzip
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/utils"
|
||||
)
|
||||
|
||||
// multiCloser wraps a reader and manages multiple closers for proper cleanup
|
||||
type multiCloser struct {
|
||||
io.Reader
|
||||
closers []io.Closer
|
||||
}
|
||||
|
||||
func (mc *multiCloser) Close() error {
|
||||
for _, c := range mc.closers {
|
||||
if err := c.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenFile opens a file (optionally gzipped) by file path
|
||||
func OpenFile(fileName string) (io.ReadCloser, error) {
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("unable to open the file: %w", err)
|
||||
}
|
||||
|
||||
mc := &multiCloser{
|
||||
closers: []io.Closer{f},
|
||||
}
|
||||
|
||||
br := bufio.NewReader(f)
|
||||
mc.Reader = br
|
||||
|
||||
if utils.IsGzip(br) {
|
||||
gzr, err := gzip.NewReader(br)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return nil, xerrors.Errorf("failed to open gzip: %w", err)
|
||||
}
|
||||
mc.Reader = gzr
|
||||
mc.closers = append(mc.closers, gzr)
|
||||
}
|
||||
|
||||
return mc, nil
|
||||
}
|
||||
@@ -114,8 +114,32 @@ func (s Service) ScanArtifact(ctx context.Context, options types.ScanOptions) (t
|
||||
func (s Service) generateArtifactID(artifactInfo artifact.Reference) string {
|
||||
switch artifactInfo.Type {
|
||||
case ftypes.TypeContainerImage:
|
||||
// Use image ID directly
|
||||
return artifactInfo.ImageMetadata.ID
|
||||
// For container images, calculate hash(ImageID + Registry + Repository)
|
||||
// to ensure same images in different repos/registries have different IDs.
|
||||
// Note: The artifact ID does NOT include the tag or digest, only registry/repository,
|
||||
// so the same image with different tags will have the same artifact ID.
|
||||
imageID := artifactInfo.ImageMetadata.ID
|
||||
if imageID == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Use the Reference field if available
|
||||
ref := artifactInfo.ImageMetadata.Reference
|
||||
if ref.IsEmpty() {
|
||||
// Reference is empty when RepoTags and RepoDigests are both empty.
|
||||
// This happens in the following cases:
|
||||
// 1. Images built without tags (e.g., "docker build ." without -t flag)
|
||||
// 2. Images saved by ID (e.g., "docker save <image-id>" or "docker save sha256:xxx")
|
||||
// In these cases, fall back to using the image ID directly.
|
||||
log.Debug("No image reference available for artifact ID calculation, using image ID directly",
|
||||
log.String("image", artifactInfo.Name))
|
||||
return imageID
|
||||
}
|
||||
|
||||
// ref.Context() returns registry/repository (e.g., "index.docker.io/library/alpine")
|
||||
data := fmt.Sprintf("%s:%s", imageID, ref.Context())
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return fmt.Sprintf("sha256:%x", hash)
|
||||
|
||||
case ftypes.TypeRepository:
|
||||
// Generate ID from repository URL and commit hash combination
|
||||
|
||||
223
pkg/scan/service_private_test.go
Normal file
223
pkg/scan/service_private_test.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
)
|
||||
|
||||
func TestService_generateArtifactID(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
artifactInfo artifact.Reference
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "container image with valid reference",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "ghcr.io/aquasecurity/trivy:latest",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
Reference: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:latest"),
|
||||
},
|
||||
},
|
||||
want: "sha256:58a3381def2cec86309c94be4fbeaca4b6c0231743ed1df9b0bea883a33cdebb",
|
||||
},
|
||||
{
|
||||
name: "same image with different tag should have same artifact ID",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "ghcr.io/aquasecurity/trivy:v0.65.0",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
Reference: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy:v0.65.0"),
|
||||
},
|
||||
},
|
||||
want: "sha256:58a3381def2cec86309c94be4fbeaca4b6c0231743ed1df9b0bea883a33cdebb",
|
||||
},
|
||||
{
|
||||
name: "different repository should have different artifact ID",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "ghcr.io/aqua-sec/trivy:v0.65.0",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
Reference: testutil.MustParseReference(t, "ghcr.io/aqua-sec/trivy:v0.65.0"),
|
||||
},
|
||||
},
|
||||
want: "sha256:bf73a838ae6a9d9c3018fbc7b628741f3be920b75c011a49c0b192736eb789b1",
|
||||
},
|
||||
{
|
||||
name: "different registry should have different artifact ID",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "docker.io/aquasecurity/trivy:v0.65.0",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
Reference: testutil.MustParseReference(t, "docker.io/aquasecurity/trivy:v0.65.0"),
|
||||
},
|
||||
},
|
||||
want: "sha256:dcba426e1fbd6e7fda125be3b9a2507ce3da2c7954c2edbf0e06e34d7f0ca22f",
|
||||
},
|
||||
{
|
||||
name: "docker.io implicit (no registry)",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "aquasecurity/trivy:latest",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
Reference: testutil.MustParseReference(t, "aquasecurity/trivy:latest"),
|
||||
},
|
||||
},
|
||||
want: "sha256:dcba426e1fbd6e7fda125be3b9a2507ce3da2c7954c2edbf0e06e34d7f0ca22f",
|
||||
},
|
||||
{
|
||||
name: "docker.io official image",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "alpine:3.10",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:alpine123",
|
||||
Reference: testutil.MustParseReference(t, "alpine:3.10"),
|
||||
},
|
||||
},
|
||||
want: "sha256:56de33d7ec6a1f832c9a7b2a26b1870efe79198e1c13ac645d43798c90954bb5",
|
||||
},
|
||||
{
|
||||
name: "localhost with port",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "localhost:5000/myapp:latest",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:local123",
|
||||
Reference: testutil.MustParseReference(t, "localhost:5000/myapp:latest"),
|
||||
},
|
||||
},
|
||||
want: "sha256:7cbf1bbde2285bac7c810fb76da5b0476d284f320f50b913987d6fc9226dc3e3",
|
||||
},
|
||||
{
|
||||
name: "multi-level repository",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "gcr.io/my-org/my-team/my-app:v1.0.0",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:gcr123",
|
||||
Reference: testutil.MustParseReference(t, "gcr.io/my-org/my-team/my-app:v1.0.0"),
|
||||
},
|
||||
},
|
||||
want: "sha256:edb01f579a800df17687439f1115bf4ced7bb977aa6afd468675ec56145a530c",
|
||||
},
|
||||
{
|
||||
name: "same image with different digest should have same artifact ID",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
Reference: testutil.MustParseReference(t, "ghcr.io/aquasecurity/trivy@sha256:0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
},
|
||||
want: "sha256:58a3381def2cec86309c94be4fbeaca4b6c0231743ed1df9b0bea883a33cdebb",
|
||||
},
|
||||
{
|
||||
name: "image with digest (no reference)",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "ghcr.io/aquasecurity/trivy@sha256:abc123",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:abc123",
|
||||
// No reference for digest case (empty)
|
||||
},
|
||||
},
|
||||
want: "sha256:abc123",
|
||||
},
|
||||
{
|
||||
name: "container image with no image ID",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "ghcr.io/aquasecurity/trivy:latest",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "",
|
||||
// No reference
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "container image with tar archive (uses RepoTag)",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "../fanal/test/testdata/alpine-311.tar.gz",
|
||||
Type: ftypes.TypeContainerImage,
|
||||
ImageMetadata: artifact.ImageMetadata{
|
||||
ID: "sha256:fallback123",
|
||||
Reference: testutil.MustParseReference(t, "alpine:3.11"),
|
||||
},
|
||||
},
|
||||
want: "sha256:a840c3e6bbadd213fee8cf6e4c32082f06541b8792a929fd373a57e5af0e8fa5",
|
||||
},
|
||||
{
|
||||
name: "repository with URL and commit",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "myrepo",
|
||||
Type: ftypes.TypeRepository,
|
||||
RepoMetadata: artifact.RepoMetadata{
|
||||
RepoURL: "https://github.com/aquasecurity/trivy",
|
||||
Commit: "abc123def456",
|
||||
},
|
||||
},
|
||||
want: "sha256:e23a8c4bae6c00f26ebf52d59e70ddfbbf5b2916d089239c3224f7f06371af98",
|
||||
},
|
||||
{
|
||||
name: "repository with only commit",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "/path/to/local/repo",
|
||||
Type: ftypes.TypeRepository,
|
||||
RepoMetadata: artifact.RepoMetadata{
|
||||
Commit: "abc123def456",
|
||||
},
|
||||
},
|
||||
want: "sha256:9183de2823d60a525ed7aeabdb2cda775cba82dd5da0e94bb2fbba779ad399a7",
|
||||
},
|
||||
{
|
||||
name: "repository without commit",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "myrepo",
|
||||
Type: ftypes.TypeRepository,
|
||||
RepoMetadata: artifact.RepoMetadata{
|
||||
RepoURL: "https://github.com/aquasecurity/trivy",
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "filesystem scan",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "/some/path",
|
||||
Type: ftypes.TypeFilesystem,
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
artifactInfo: artifact.Reference{
|
||||
Name: "something",
|
||||
Type: "unknown",
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
s := Service{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := s.generateArtifactID(tt.artifactInfo)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
||||
want: tTypes.Report{
|
||||
SchemaVersion: 2,
|
||||
CreatedAt: time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC),
|
||||
ArtifactID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
|
||||
ArtifactID: "sha256:574abdaf07824449b1277ec1e7e67659cc869bbf97fd95447812b55644350a21", // hash(ImageID:index.docker.io/library/alpine) from RepoTag alpine:3.11
|
||||
ArtifactName: "../fanal/test/testdata/alpine-311.tar.gz",
|
||||
ArtifactType: ftypes.TypeContainerImage,
|
||||
ReportID: "3ff14136-e09f-4df9-80ea-000000000001",
|
||||
|
||||
@@ -11,14 +11,21 @@ import (
|
||||
|
||||
// Report represents a scan result
|
||||
type Report struct {
|
||||
SchemaVersion int `json:",omitempty"`
|
||||
ReportID string `json:",omitempty"` // Unique identifier for this scan report
|
||||
CreatedAt time.Time `json:",omitzero"`
|
||||
ArtifactID string `json:",omitempty"` // Unique identifier for the artifact (e.g., image config hash)
|
||||
ArtifactName string `json:",omitempty"`
|
||||
ArtifactType ftypes.ArtifactType `json:",omitempty"`
|
||||
Metadata Metadata `json:",omitzero"`
|
||||
Results Results `json:",omitempty"`
|
||||
SchemaVersion int `json:",omitempty"`
|
||||
ReportID string `json:",omitempty"` // Unique identifier for this scan report
|
||||
CreatedAt time.Time `json:",omitzero"`
|
||||
|
||||
// ArtifactID uniquely identifies the scanned artifact.
|
||||
// For container images: hash(ImageID + Registry + Repository) - ensures same image in different repos have different IDs
|
||||
// For repositories: hash(RepoURL + Commit) or hash(Path + Commit) for local repos
|
||||
// For filesystems: empty string
|
||||
// For other artifact types: empty string
|
||||
ArtifactID string `json:",omitempty"`
|
||||
|
||||
ArtifactName string `json:",omitempty"`
|
||||
ArtifactType ftypes.ArtifactType `json:",omitempty"`
|
||||
Metadata Metadata `json:",omitzero"`
|
||||
Results Results `json:",omitempty"`
|
||||
|
||||
// parsed SBOM
|
||||
BOM *core.BOM `json:"-"` // Just for internal usage, not exported in JSON
|
||||
|
||||
Reference in New Issue
Block a user