Skip to content

Commit

Permalink
rhel: construct distributions dynamically
Browse files Browse the repository at this point in the history
This change swaps a hard-coded list for making distributions as needed.

Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Jun 14, 2022
1 parent c959ca5 commit 2eba2c4
Show file tree
Hide file tree
Showing 14 changed files with 149 additions and 215 deletions.
97 changes: 41 additions & 56 deletions rhel/distributionscanner.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package rhel

import (
"bytes"
"context"
"errors"
"fmt"
"io/fs"
"regexp"
"runtime/trace"
"strconv"

"github.com/quay/zlog"

"github.com/quay/claircore"
"github.com/quay/claircore/internal/indexer"
"github.com/quay/claircore/pkg/tarfs"
)

const (
Expand All @@ -19,50 +23,15 @@ const (

const (
scannerName = "rhel"
scannerVersion = "v0.0.1"
scannerVersion = "2"
scannerKind = "distribution"
)

type rhelRegex struct {
release Release
regexp *regexp.Regexp
}

// the follow set of regexps will match both the PrettyName in the os-releaes file
// ex: Red Hat Enterprise Linux Server 7.7 (Maipo)
// and the release string in the redhat-release
// ex: Red Hat Enterprise Linux Server release 7.7 (Maipo)
var rhelRegexes = []rhelRegex{
{
release: RHEL3,
// regex for /etc/issue
regexp: regexp.MustCompile(`Red Hat Enterprise Linux (Server)?\s*(release)?\s*3(\.\d)?`),
},
{
release: RHEL4,
regexp: regexp.MustCompile(`Red Hat Enterprise Linux (Server)?\s*(release)?\s*4(\.\d)?`),
},
{
release: RHEL5,
regexp: regexp.MustCompile(`Red Hat Enterprise Linux (Server)?\s*(release)?\s*5(\.\d)?`),
},
{
release: RHEL6,
regexp: regexp.MustCompile(`Red Hat Enterprise Linux (Server)?\s*(release)?\s*6(\.\d)?`),
},
{
release: RHEL7,
regexp: regexp.MustCompile(`Red Hat Enterprise Linux (Server)?\s*(release)?\s*7(\.\d)?`),
},
{
release: RHEL8,
regexp: regexp.MustCompile(`Red Hat Enterprise Linux (Server)?\s*(release)?\s*8(\.\d)?`),
},
}

var (
_ indexer.DistributionScanner = (*DistributionScanner)(nil)
_ indexer.VersionedScanner = (*DistributionScanner)(nil)

releaseRegexp = regexp.MustCompile(`Red Hat Enterprise Linux (?:Server)?\s*(?:release)?\s*(\d+)(?:\.\d)?`)
)

// DistributionScanner attempts to discover if a layer
Expand Down Expand Up @@ -91,29 +60,45 @@ func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]
"layer", l.Hash.String())
zlog.Debug(ctx).Msg("start")
defer zlog.Debug(ctx).Msg("done")
files, err := l.Files(osReleasePath, rhReleasePath)
rd, err := l.Reader()
if err != nil {
return nil, fmt.Errorf("rhel: unable to create layer reader: %w", err)
}
defer rd.Close()
sys, err := tarfs.New(rd)
if err != nil {
return nil, fmt.Errorf("rhel: unable to open tarfs: %w", err)
}
d, err := findDistribution(sys)
if err != nil {
return nil, fmt.Errorf("rhel: unexpected error reading files: %w", err)
}
if d == nil {
zlog.Debug(ctx).Msg("didn't find an os-release or redhat-release file")
return nil, nil
}
for _, buff := range files {
dist := ds.parse(buff)
if dist != nil {
return []*claircore.Distribution{dist}, nil
}
}
return []*claircore.Distribution{}, nil
return []*claircore.Distribution{d}, nil
}

// parse attempts to match all RHEL release regexp and returns the associated
// distribution if it exists.
//
// separated into its own method to aid testing.
func (ds *DistributionScanner) parse(buff *bytes.Buffer) *claircore.Distribution {
for _, ur := range rhelRegexes {
if ur.regexp.Match(buff.Bytes()) {
return releaseToDist(ur.release)
func findDistribution(sys fs.FS) (*claircore.Distribution, error) {
for _, n := range []string{rhReleasePath, osReleasePath} {
b, err := fs.ReadFile(sys, n)
switch {
case errors.Is(err, nil):
case errors.Is(err, fs.ErrNotExist):
continue
default:
return nil, fmt.Errorf("rhel: unexpected error reading files: %w", err)
}
ms := releaseRegexp.FindSubmatch(b)
if ms == nil {
continue
}
num, err := strconv.ParseInt(string(ms[1]), 10, 64)
if err != nil {
return nil, fmt.Errorf("rhel: unexpected error reading files: %w", err)
}
return mkRelease(num), nil
}
return nil
return nil, nil
}
117 changes: 23 additions & 94 deletions rhel/distributionscanner_test.go
Original file line number Diff line number Diff line change
@@ -1,105 +1,34 @@
package rhel

import (
"bytes"
"io/fs"
"os"
"path"
"testing"

"github.com/google/go-cmp/cmp"
)

var rhel3RHRelease []byte = []byte(`Red Hat Enterprise Linux Server release 3.1 (Taroon)`)
var rhel4RHRelease []byte = []byte(`Red Hat Enterprise Linux Server release 4.8 (Nahant)`)
var rhel5RHRelease []byte = []byte(`Red Hat Enterprise Linux Server release 5.6 (Tikanga)`)
var rhel6RHRelease []byte = []byte(`Red Hat Enterprise Linux Server release 6.10 (Santiago)`)
var rhel7RHRelease []byte = []byte(`Red Hat Enterprise Linux Server release 7.4 (Maipo)`)
var rhel7OSRelease []byte = []byte(`NAME="Red Hat Enterprise Linux Server"
VERSION="7.7 (Maipo)"
ID="rhel"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="7.7"
PRETTY_NAME="Red Hat Enterprise Linux Server 7.7 (Maipo)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:7.7:GA:server"
HOME_URL="https://access.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
REDHAT_BUGZILLA_PRODUCT_VERSION=7.7
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="7.7"`)
var rhel8RHRelease []byte = []byte(`Red Hat Enterprise Linux Server release 8.1 (Ootpa)`)
var rhel8OSRelease []byte = []byte(`NAME="Red Hat Enterprise Linux"
VERSION="8.1 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.1"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.1 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.1:GA"
HOME_URL="https://access.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_BUGZILLA_PRODUCT_VERSION=8.1
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="8.1"`)

func TestDistributionScanner(t *testing.T) {
table := []struct {
name string
release Release
file []byte
}{
{
name: "RHEL3",
release: RHEL3,
file: rhel3RHRelease,
},
{
name: "RHEL4",
release: RHEL4,
file: rhel4RHRelease,
},
{
name: "RHEL5",
release: RHEL5,
file: rhel5RHRelease,
},
{
name: "RHEL6",
release: RHEL6,
file: rhel6RHRelease,
},
{
name: "RHEL7",
release: RHEL7,
file: rhel7RHRelease,
},
{
name: "RHEL7 OSRelease",
release: RHEL7,
file: rhel7OSRelease,
},
{
name: "RHEL8",
release: RHEL8,
file: rhel8RHRelease,
},
{
name: "RHEL8 OSRelease",
release: RHEL8,
file: rhel8OSRelease,
},
sys := os.DirFS(`testdata/releasefiles`)
ents, err := fs.ReadDir(sys, ".")
if err != nil {
t.Fatal(err)
}
for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
scanner := DistributionScanner{}
dist := scanner.parse(bytes.NewBuffer(tt.file))
if !cmp.Equal(dist, releaseToDist(tt.release)) {
t.Fatalf("%v", cmp.Diff(dist, releaseToDist(tt.release)))
for _, e := range ents {
t.Run(e.Name(), func(t *testing.T) {
n := path.Base(t.Name())
sys, err := fs.Sub(sys, n)
if err != nil {
t.Fatal(err)
}
d, err := findDistribution(sys)
if err != nil {
t.Fatal(err)
}
if d == nil {
t.Fatal("missing distribution")
}
if got, want := d.Version, n; got != want {
t.Errorf("got: %q, want %q", got, want)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion rhel/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln
// each updater is configured to parse a rhel release
// specific xml database. we'll use the updater's release
// to map the parsed vulnerabilities
Dist: releaseToDist(u.release),
Dist: u.release.Distribution(),
}
vs = append(vs, v)
}
Expand Down
90 changes: 26 additions & 64 deletions rhel/releases.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package rhel

import (
"strconv"
"sync"

"github.com/quay/claircore"
"github.com/quay/claircore/pkg/cpe"
)
Expand All @@ -18,73 +21,32 @@ const (
RHEL6 Release = 6
RHEL7 Release = 7
RHEL8 Release = 8
RHEL9 Release = 9
)

var rhel3Dist = &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: "3",
VersionID: "3",
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server 3",
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:3"),
}
var rhel4Dist = &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: "4",
VersionID: "4",
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server 4",
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:4"),
}
var rhel5Dist = &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: "5",
VersionID: "5",
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server 5",
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:5"),
}
var rhel6Dist = &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: "6",
VersionID: "6",
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server 6",
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:6"),
}
var rhel7Dist = &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: "7",
VersionID: "7",
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server 7",
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:7"),
}
var rhel8Dist = &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: "8",
VersionID: "8",
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server 8",
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:8"),
func (r Release) Distribution() *claircore.Distribution {
return mkRelease(int64(r))
}

func releaseToDist(r Release) *claircore.Distribution {
switch r {
case RHEL3:
return rhel3Dist
case RHEL4:
return rhel4Dist
case RHEL5:
return rhel5Dist
case RHEL6:
return rhel6Dist
case RHEL7:
return rhel7Dist
case RHEL8:
return rhel8Dist
default:
// return empty dist
return &claircore.Distribution{}
// RelMap memoizes the Distributions handed out by this package.
//
// Doing this is a cop-out to the previous approach of having a hard-coded set of structs.
// In the case something is (mistakenly) doing pointer comparisons, this will make that work
// but still allows us to have the list of distributions grow ad-hoc.
var relMap sync.Map

func mkRelease(n int64) *claircore.Distribution {
v, ok := relMap.Load(n)
if !ok {
s := strconv.FormatInt(n, 10)
v, _ = relMap.LoadOrStore(n, &claircore.Distribution{
Name: "Red Hat Enterprise Linux Server",
Version: s,
VersionID: s,
DID: "rhel",
PrettyName: "Red Hat Enterprise Linux Server " + s,
CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:" + s),
})
}
return v.(*claircore.Distribution)
}
1 change: 1 addition & 0 deletions rhel/testdata/releasefiles/3/etc/redhat-release
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Red Hat Enterprise Linux Server release 3.1 (Taroon)
1 change: 1 addition & 0 deletions rhel/testdata/releasefiles/4/etc/redhat-release
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Red Hat Enterprise Linux Server release 4.8 (Nahant)
1 change: 1 addition & 0 deletions rhel/testdata/releasefiles/5/etc/redhat-release
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Red Hat Enterprise Linux Server release 5.6 (Tikanga)
1 change: 1 addition & 0 deletions rhel/testdata/releasefiles/6/etc/redhat-release
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Red Hat Enterprise Linux Server release 6.10 (Santiago)
Loading

0 comments on commit 2eba2c4

Please sign in to comment.