Skip to content

Commit 8ceed94

Browse files
committed
all: account for language package overwrites
Signed-off-by: RTann <[email protected]>
1 parent 3d6e9b6 commit 8ceed94

18 files changed

+246
-313
lines changed

gobin/coalescer.go

-45
This file was deleted.

gobin/ecosystem.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ import (
44
"context"
55

66
"github.com/quay/claircore/indexer"
7+
"github.com/quay/claircore/language"
78
)
89

910
// NewEcosystem provides the ecosystem for handling go binaries.
10-
func NewEcosystem(ctx context.Context) *indexer.Ecosystem {
11+
func NewEcosystem(_ context.Context) *indexer.Ecosystem {
1112
return &indexer.Ecosystem{
1213
Name: "gobin",
1314
PackageScanners: func(context.Context) ([]indexer.PackageScanner, error) {
1415
return []indexer.PackageScanner{Detector{}}, nil
1516
},
1617
DistributionScanners: func(context.Context) ([]indexer.DistributionScanner, error) { return nil, nil },
1718
RepositoryScanners: func(context.Context) ([]indexer.RepositoryScanner, error) { return nil, nil },
18-
Coalescer: func(context.Context) (indexer.Coalescer, error) { return &coalescer{}, nil },
19+
Coalescer: language.NewCoalescer,
1920
}
2021
}

gobin/gobin.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Detector struct{}
3535

3636
const (
3737
detectorName = `gobin`
38-
detectorVersion = `6`
38+
detectorVersion = `7`
3939
detectorKind = `package`
4040
)
4141

java/coalescer.go

-42
This file was deleted.

java/ecosystem.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"context"
55

66
"github.com/quay/claircore/indexer"
7+
"github.com/quay/claircore/language"
78
)
89

910
// NewEcosystem provides the set of scanners for the java ecosystem.
10-
func NewEcosystem(ctx context.Context) *indexer.Ecosystem {
11+
func NewEcosystem(_ context.Context) *indexer.Ecosystem {
1112
return &indexer.Ecosystem{
1213
PackageScanners: func(_ context.Context) ([]indexer.PackageScanner, error) {
1314
return []indexer.PackageScanner{&Scanner{}}, nil
@@ -16,8 +17,6 @@ func NewEcosystem(ctx context.Context) *indexer.Ecosystem {
1617
RepositoryScanners: func(_ context.Context) ([]indexer.RepositoryScanner, error) {
1718
return nil, nil
1819
},
19-
Coalescer: func(_ context.Context) (indexer.Coalescer, error) {
20-
return (*coalescer)(nil), nil
21-
},
20+
Coalescer: language.NewCoalescer,
2221
}
2322
}

java/packagescanner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ type Scanner struct {
7171
func (*Scanner) Name() string { return "java" }
7272

7373
// Version implements scanner.VersionedScanner.
74-
func (*Scanner) Version() string { return "6" }
74+
func (*Scanner) Version() string { return "7" }
7575

7676
// Kind implements scanner.VersionedScanner.
7777
func (*Scanner) Kind() string { return "package" }

language/coalescer.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Package language implements structs and functions common between
2+
// programming language indexing implementations.
3+
package language
4+
5+
import (
6+
"context"
7+
8+
"github.com/quay/claircore"
9+
"github.com/quay/claircore/indexer"
10+
)
11+
12+
var _ indexer.Coalescer = (*coalescer)(nil)
13+
14+
type coalescer struct{}
15+
16+
// NewCoalescer returns a new common programming language coalescer.
17+
func NewCoalescer(_ context.Context) (indexer.Coalescer, error) {
18+
return &coalescer{}, nil
19+
}
20+
21+
// Coalesce implements [indexer.Coalescer].
22+
//
23+
// Image builders may opt to update language-packages instead of deleting and recreating them
24+
// (as in, there may or may not be a whiteout file to make it clear the package was updated).
25+
// This function ensures both scenarios are supported.
26+
func (c *coalescer) Coalesce(_ context.Context, ls []*indexer.LayerArtifacts) (*claircore.IndexReport, error) {
27+
ir := &claircore.IndexReport{
28+
Environments: map[string][]*claircore.Environment{},
29+
Packages: map[string]*claircore.Package{},
30+
Repositories: map[string]*claircore.Repository{},
31+
}
32+
// Similar to ir.Packages, except instead of mapping
33+
// id -> package, it maps packageDB -> package.
34+
// For language packages, it is possible the
35+
// packageDB is overwritten between subsequent layers.
36+
packages := make(map[string]*claircore.Package)
37+
for i := len(ls) - 1; i >= 0; i-- {
38+
l := ls[i]
39+
// If we didn't find at least one repo in this layer
40+
// no point searching for packages.
41+
if len(l.Repos) == 0 {
42+
continue
43+
}
44+
rs := make([]string, len(l.Repos))
45+
for i, r := range l.Repos {
46+
rs[i] = r.ID
47+
ir.Repositories[r.ID] = r
48+
}
49+
for _, pkg := range l.Pkgs {
50+
if seen, exists := packages[pkg.PackageDB]; exists {
51+
// If the package was renamed or has a different version in a higher (previously seen) layer,
52+
// then this is considered a different package.
53+
// In that case, ignore the original package in the lower (this) layer.
54+
if pkg.Name != seen.Name || pkg.Version != seen.Version {
55+
continue
56+
}
57+
// The name and version are the same, so delete the entry related to the higher (previously seen)
58+
// layer, as this package was likely introduced in the lower (this) layer.
59+
delete(ir.Packages, seen.ID)
60+
delete(ir.Environments, seen.ID)
61+
}
62+
packages[pkg.PackageDB] = pkg
63+
ir.Packages[pkg.ID] = pkg
64+
ir.Environments[pkg.ID] = []*claircore.Environment{
65+
{
66+
PackageDB: pkg.PackageDB,
67+
IntroducedIn: l.Hash,
68+
RepositoryIDs: rs,
69+
},
70+
}
71+
}
72+
}
73+
return ir, nil
74+
}

language/coalescer_test.go

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package language
2+
3+
import (
4+
"context"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/quay/zlog"
9+
10+
"github.com/quay/claircore"
11+
"github.com/quay/claircore/indexer"
12+
"github.com/quay/claircore/test"
13+
)
14+
15+
func TestCoalescer(t *testing.T) {
16+
t.Parallel()
17+
ctx := zlog.Test(context.Background(), t)
18+
coalescer := &coalescer{}
19+
pkgs := test.GenUniquePackages(6)
20+
repo := []*claircore.Repository{{
21+
Name: "npm",
22+
URI: "https://www.npmjs.com/",
23+
}}
24+
layerArtifacts := []*indexer.LayerArtifacts{
25+
{
26+
Hash: test.RandomSHA256Digest(t),
27+
Pkgs: pkgs[:1],
28+
},
29+
{
30+
Hash: test.RandomSHA256Digest(t),
31+
Pkgs: pkgs[:2],
32+
},
33+
{
34+
Hash: test.RandomSHA256Digest(t),
35+
Pkgs: pkgs[:3],
36+
Repos: repo,
37+
},
38+
{
39+
Hash: test.RandomSHA256Digest(t),
40+
Pkgs: pkgs[:4],
41+
},
42+
{
43+
Hash: test.RandomSHA256Digest(t),
44+
Pkgs: pkgs[:5],
45+
Repos: repo,
46+
},
47+
{
48+
Hash: test.RandomSHA256Digest(t),
49+
Pkgs: pkgs,
50+
},
51+
}
52+
ir, err := coalescer.Coalesce(ctx, layerArtifacts)
53+
if err != nil {
54+
t.Fatalf("received error from coalesce method: %v", err)
55+
}
56+
// Expect 0-5 to have gotten associated with the repository.
57+
for i := range pkgs {
58+
es, ok := ir.Environments[strconv.Itoa(i)]
59+
if !ok && i == 5 {
60+
// Left out the last package.
61+
continue
62+
}
63+
e := es[0]
64+
if len(e.RepositoryIDs) == 0 {
65+
t.Error("expected some repositories")
66+
}
67+
for _, id := range e.RepositoryIDs {
68+
r := ir.Repositories[id]
69+
if got, want := r.Name, "npm"; got != want {
70+
t.Errorf("got: %q, want: %q", got, want)
71+
}
72+
}
73+
}
74+
}
75+
76+
func TestCoalescerPackageOverwrite(t *testing.T) {
77+
t.Parallel()
78+
ctx := zlog.Test(context.Background(), t)
79+
coalescer := &coalescer{}
80+
repo := []*claircore.Repository{{
81+
Name: "npm",
82+
URI: "https://www.npmjs.com/",
83+
}}
84+
hashes := []claircore.Digest{
85+
test.RandomSHA256Digest(t),
86+
test.RandomSHA256Digest(t),
87+
test.RandomSHA256Digest(t),
88+
test.RandomSHA256Digest(t),
89+
}
90+
layerArtifacts := []*indexer.LayerArtifacts{
91+
{
92+
Hash: hashes[0],
93+
Pkgs: []*claircore.Package{
94+
{
95+
ID: "0",
96+
Name: "semver",
97+
Version: "7.3.8",
98+
PackageDB: "nodejs:usr/local/lib/node_modules/npm/node_modules/semver/package.json",
99+
},
100+
},
101+
Repos: repo,
102+
},
103+
{
104+
Hash: hashes[1],
105+
},
106+
{
107+
Hash: hashes[2],
108+
Pkgs: []*claircore.Package{
109+
{
110+
ID: "1",
111+
Name: "semver",
112+
Version: "7.5.2",
113+
PackageDB: "nodejs:usr/local/lib/node_modules/npm/node_modules/semver/package.json",
114+
},
115+
},
116+
Repos: repo,
117+
},
118+
{
119+
Hash: hashes[3],
120+
Pkgs: []*claircore.Package{
121+
{
122+
ID: "2",
123+
Name: "semver",
124+
Version: "7.5.2",
125+
PackageDB: "nodejs:usr/local/lib/node_modules/npm/node_modules/semver/package.json",
126+
},
127+
},
128+
Repos: repo,
129+
},
130+
}
131+
ir, err := coalescer.Coalesce(ctx, layerArtifacts)
132+
if err != nil {
133+
t.Fatalf("received error from coalesce method: %v", err)
134+
}
135+
if len(ir.Packages) != 1 {
136+
t.Fatalf("unexpected number of packages: %d != %d", len(ir.Packages), 1)
137+
}
138+
pkg, exists := ir.Packages["1"]
139+
if !exists {
140+
t.Fatal("expected package does not exist")
141+
}
142+
if pkg.Version != "7.5.2" {
143+
t.Fatalf("unexpected version: %s != %s", pkg.Version, "7.5.2")
144+
}
145+
envs, exists := ir.Environments["1"]
146+
if !exists {
147+
t.Fatal("expected environments do not exist")
148+
}
149+
if len(envs) != 1 {
150+
t.Fatalf("unexpected number of envionments: %d != %d", len(envs), 1)
151+
}
152+
if envs[0].IntroducedIn.String() != hashes[2].String() {
153+
t.Fatalf("unexpected introducedIn: %s != %s", envs[0].IntroducedIn.String(), hashes[2].String())
154+
}
155+
}

0 commit comments

Comments
 (0)