add registerable version formats
Since we only ever used dpkg, this change shims everything into using dpkg.
This commit is contained in:
parent
3897fb6706
commit
033709eaea
@ -21,16 +21,18 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/fernet/fernet-go"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
)
|
||||
|
||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1")
|
||||
|
||||
type Error struct {
|
||||
Message string `json:"Layer`
|
||||
Message string `json:"Layer"`
|
||||
}
|
||||
|
||||
type Layer struct {
|
||||
@ -63,7 +65,8 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
|
||||
feature := Feature{
|
||||
Name: dbFeatureVersion.Feature.Name,
|
||||
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
|
||||
Version: dbFeatureVersion.Version.String(),
|
||||
VersionFormat: dbFeatureVersion.Feature.Namespace.VersionFormat,
|
||||
Version: dbFeatureVersion.Version,
|
||||
AddedBy: dbFeatureVersion.AddedBy.Name,
|
||||
}
|
||||
|
||||
@ -77,8 +80,8 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
|
||||
Metadata: dbVuln.Metadata,
|
||||
}
|
||||
|
||||
if dbVuln.FixedBy != types.MaxVersion {
|
||||
vuln.FixedBy = dbVuln.FixedBy.String()
|
||||
if dbVuln.FixedBy != versionfmt.MaxVersion {
|
||||
vuln.FixedBy = dbVuln.FixedBy
|
||||
}
|
||||
feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
|
||||
}
|
||||
@ -90,7 +93,8 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
Name string `json:"Name,omitempty"`
|
||||
VersionFormat string `json:"VersionFormat,omitempty"`
|
||||
}
|
||||
|
||||
type Vulnerability struct {
|
||||
@ -153,44 +157,51 @@ func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn b
|
||||
type Feature struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
NamespaceName string `json:"NamespaceName,omitempty"`
|
||||
VersionFormat string `json:"VersionFormat,omitempty"`
|
||||
Version string `json:"Version,omitempty"`
|
||||
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
|
||||
AddedBy string `json:"AddedBy,omitempty"`
|
||||
}
|
||||
|
||||
func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature {
|
||||
versionStr := dbFeatureVersion.Version.String()
|
||||
if versionStr == types.MaxVersion.String() {
|
||||
versionStr = "None"
|
||||
version := dbFeatureVersion.Version
|
||||
if version == versionfmt.MaxVersion {
|
||||
version = "None"
|
||||
}
|
||||
|
||||
return Feature{
|
||||
Name: dbFeatureVersion.Feature.Name,
|
||||
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
|
||||
Version: versionStr,
|
||||
VersionFormat: dbFeatureVersion.Feature.Namespace.VersionFormat,
|
||||
Version: version,
|
||||
AddedBy: dbFeatureVersion.AddedBy.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (f Feature) DatabaseModel() (database.FeatureVersion, error) {
|
||||
var version types.Version
|
||||
func (f Feature) DatabaseModel() (fv database.FeatureVersion, err error) {
|
||||
var version string
|
||||
if f.Version == "None" {
|
||||
version = types.MaxVersion
|
||||
version = versionfmt.MaxVersion
|
||||
} else {
|
||||
var err error
|
||||
version, err = types.NewVersion(f.Version)
|
||||
err = versionfmt.Valid(f.VersionFormat, f.Version)
|
||||
if err != nil {
|
||||
return database.FeatureVersion{}, err
|
||||
return
|
||||
}
|
||||
version = f.Version
|
||||
}
|
||||
|
||||
return database.FeatureVersion{
|
||||
fv = database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: f.Name,
|
||||
Namespace: database.Namespace{Name: f.NamespaceName},
|
||||
Name: f.Name,
|
||||
Namespace: database.Namespace{
|
||||
Name: f.NamespaceName,
|
||||
VersionFormat: f.VersionFormat,
|
||||
},
|
||||
},
|
||||
Version: version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
|
@ -179,7 +179,10 @@ func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params,
|
||||
}
|
||||
var namespaces []Namespace
|
||||
for _, dbNamespace := range dbNamespaces {
|
||||
namespaces = append(namespaces, Namespace{Name: dbNamespace.Name})
|
||||
namespaces = append(namespaces, Namespace{
|
||||
Name: dbNamespace.Name,
|
||||
VersionFormat: dbNamespace.VersionFormat,
|
||||
})
|
||||
}
|
||||
|
||||
writeResponse(w, r, http.StatusOK, NamespaceEnvelope{Namespaces: &namespaces})
|
||||
|
@ -40,7 +40,8 @@ type Layer struct {
|
||||
type Namespace struct {
|
||||
Model
|
||||
|
||||
Name string
|
||||
Name string
|
||||
VersionFormat string
|
||||
}
|
||||
|
||||
type Feature struct {
|
||||
@ -54,7 +55,7 @@ type FeatureVersion struct {
|
||||
Model
|
||||
|
||||
Feature Feature
|
||||
Version types.Version
|
||||
Version string
|
||||
AffectedBy []Vulnerability
|
||||
|
||||
// For output purposes. Only make sense when the feature version is in the context of an image.
|
||||
@ -78,7 +79,7 @@ type Vulnerability struct {
|
||||
|
||||
// For output purposes. Only make sense when the vulnerability
|
||||
// is already about a specific Feature/FeatureVersion.
|
||||
FixedBy types.Version `json:",omitempty"`
|
||||
FixedBy string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type MetadataMap map[string]interface{}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -29,6 +29,9 @@ import (
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// dpkg versioning is used to parse test packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -46,8 +49,11 @@ func TestRaceAffects(t *testing.T) {
|
||||
|
||||
// Insert the Feature on which we'll work.
|
||||
feature := database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestRaceAffectsFeatureNamespace1"},
|
||||
Name: "TestRaceAffecturesFeature1",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestRaceAffectsFeatureNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestRaceAffecturesFeature1",
|
||||
}
|
||||
_, err = datastore.insertFeature(feature)
|
||||
if err != nil {
|
||||
@ -66,7 +72,7 @@ func TestRaceAffects(t *testing.T) {
|
||||
|
||||
featureVersions[i] = database.FeatureVersion{
|
||||
Feature: feature,
|
||||
Version: types.NewVersionUnsafe(strconv.Itoa(version)),
|
||||
Version: strconv.Itoa(version),
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +92,7 @@ func TestRaceAffects(t *testing.T) {
|
||||
FixedIn: []database.FeatureVersion{
|
||||
{
|
||||
Feature: feature,
|
||||
Version: types.NewVersionUnsafe(strconv.Itoa(version)),
|
||||
Version: strconv.Itoa(version),
|
||||
},
|
||||
},
|
||||
Severity: types.Unknown,
|
||||
@ -126,7 +132,7 @@ func TestRaceAffects(t *testing.T) {
|
||||
var expectedAffectedNames []string
|
||||
|
||||
for _, featureVersion := range featureVersions {
|
||||
featureVersionVersion, _ := strconv.Atoi(featureVersion.Version.String())
|
||||
featureVersionVersion, _ := strconv.Atoi(featureVersion.Version)
|
||||
|
||||
// Get actual affects.
|
||||
rows, err := datastore.Query(searchComplexTestFeatureVersionAffects,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -16,11 +16,13 @@ package pgsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
)
|
||||
|
||||
func (pgSQL *pgSQL) insertFeature(feature database.Feature) (int, error) {
|
||||
@ -61,13 +63,15 @@ func (pgSQL *pgSQL) insertFeature(feature database.Feature) (int, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion) (id int, err error) {
|
||||
if featureVersion.Version.String() == "" {
|
||||
func (pgSQL *pgSQL) insertFeatureVersion(fv database.FeatureVersion) (id int, err error) {
|
||||
err = versionfmt.Valid(fv.Feature.Namespace.VersionFormat, fv.Version)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 0, cerrors.NewBadRequestError("could not find/insert invalid FeatureVersion")
|
||||
}
|
||||
|
||||
// Do cache lookup.
|
||||
cacheIndex := "featureversion:" + featureVersion.Feature.Namespace.Name + ":" + featureVersion.Feature.Name + ":" + featureVersion.Version.String()
|
||||
cacheIndex := strings.Join([]string{"featureversion", fv.Feature.Namespace.Name, fv.Feature.Name, fv.Version}, ":")
|
||||
if pgSQL.cache != nil {
|
||||
promCacheQueriesTotal.WithLabelValues("featureversion").Inc()
|
||||
id, found := pgSQL.cache.Get(cacheIndex)
|
||||
@ -82,30 +86,29 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
||||
|
||||
// Find or create Feature first.
|
||||
t := time.Now()
|
||||
featureID, err := pgSQL.insertFeature(featureVersion.Feature)
|
||||
featureID, err := pgSQL.insertFeature(fv.Feature)
|
||||
observeQueryTime("insertFeatureVersion", "insertFeature", t)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
featureVersion.Feature.ID = featureID
|
||||
fv.Feature.ID = featureID
|
||||
|
||||
// Try to find the FeatureVersion.
|
||||
//
|
||||
// In a populated database, the likelihood of the FeatureVersion already being there is high.
|
||||
// If we can find it here, we then avoid using a transaction and locking the database.
|
||||
err = pgSQL.QueryRow(searchFeatureVersion, featureID, &featureVersion.Version).
|
||||
Scan(&featureVersion.ID)
|
||||
err = pgSQL.QueryRow(searchFeatureVersion, featureID, fv.Version).Scan(&fv.ID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, handleError("searchFeatureVersion", err)
|
||||
}
|
||||
if err == nil {
|
||||
if pgSQL.cache != nil {
|
||||
pgSQL.cache.Add(cacheIndex, featureVersion.ID)
|
||||
pgSQL.cache.Add(cacheIndex, fv.ID)
|
||||
}
|
||||
|
||||
return featureVersion.ID, nil
|
||||
return fv.ID, nil
|
||||
}
|
||||
|
||||
// Begin transaction.
|
||||
@ -132,8 +135,7 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
||||
var created bool
|
||||
|
||||
t = time.Now()
|
||||
err = tx.QueryRow(soiFeatureVersion, featureID, &featureVersion.Version).
|
||||
Scan(&created, &featureVersion.ID)
|
||||
err = tx.QueryRow(soiFeatureVersion, featureID, fv.Version).Scan(&created, &fv.ID)
|
||||
observeQueryTime("insertFeatureVersion", "soiFeatureVersion", t)
|
||||
|
||||
if err != nil {
|
||||
@ -147,16 +149,16 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
||||
tx.Commit()
|
||||
|
||||
if pgSQL.cache != nil {
|
||||
pgSQL.cache.Add(cacheIndex, featureVersion.ID)
|
||||
pgSQL.cache.Add(cacheIndex, fv.ID)
|
||||
}
|
||||
|
||||
return featureVersion.ID, nil
|
||||
return fv.ID, nil
|
||||
}
|
||||
|
||||
// Link the new FeatureVersion with every vulnerabilities that affect it, by inserting in
|
||||
// Vulnerability_Affects_FeatureVersion.
|
||||
t = time.Now()
|
||||
err = linkFeatureVersionToVulnerabilities(tx, featureVersion)
|
||||
err = linkFeatureVersionToVulnerabilities(tx, fv)
|
||||
observeQueryTime("insertFeatureVersion", "linkFeatureVersionToVulnerabilities", t)
|
||||
|
||||
if err != nil {
|
||||
@ -171,10 +173,10 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
||||
}
|
||||
|
||||
if pgSQL.cache != nil {
|
||||
pgSQL.cache.Add(cacheIndex, featureVersion.ID)
|
||||
pgSQL.cache.Add(cacheIndex, fv.ID)
|
||||
}
|
||||
|
||||
return featureVersion.ID, nil
|
||||
return fv.ID, nil
|
||||
}
|
||||
|
||||
// TODO(Quentin-M): Batch me
|
||||
@ -195,7 +197,7 @@ func (pgSQL *pgSQL) insertFeatureVersions(featureVersions []database.FeatureVers
|
||||
type vulnerabilityAffectsFeatureVersion struct {
|
||||
vulnerabilityID int
|
||||
fixedInID int
|
||||
fixedInVersion types.Version
|
||||
fixedInVersion string
|
||||
}
|
||||
|
||||
func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion database.FeatureVersion) error {
|
||||
@ -216,7 +218,11 @@ func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion database.Fea
|
||||
return handleError("searchVulnerabilityFixedInFeature.Scan()", err)
|
||||
}
|
||||
|
||||
if featureVersion.Version.Compare(affect.fixedInVersion) < 0 {
|
||||
cmp, err := versionfmt.Compare(featureVersion.Feature.Namespace.VersionFormat, featureVersion.Version, affect.fixedInVersion)
|
||||
if err != nil {
|
||||
return handleError("searchVulnerabilityVersionComparison", err)
|
||||
}
|
||||
if cmp < 0 {
|
||||
// The version of the FeatureVersion we are inserting is lower than the fixed version on this
|
||||
// Vulnerability, thus, this FeatureVersion is affected by it.
|
||||
affects = append(affects, affect)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -20,7 +20,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// dpkg versioning is used to parse test packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
func TestInsertFeature(t *testing.T) {
|
||||
@ -45,8 +47,11 @@ func TestInsertFeature(t *testing.T) {
|
||||
|
||||
// Insert Feature and ensure we can find it.
|
||||
feature := database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace1"},
|
||||
Name: "TestInsertFeature1",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertFeatureNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertFeature1",
|
||||
}
|
||||
id1, err := datastore.insertFeature(feature)
|
||||
assert.Nil(t, err)
|
||||
@ -58,28 +63,34 @@ func TestInsertFeature(t *testing.T) {
|
||||
for _, invalidFeatureVersion := range []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{},
|
||||
Version: types.NewVersionUnsafe("1.0"),
|
||||
Version: "1.0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{},
|
||||
Name: "TestInsertFeature2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.0"),
|
||||
Version: "1.0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace2"},
|
||||
Name: "TestInsertFeature2",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertFeatureNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertFeature2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe(""),
|
||||
Version: "",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace2"},
|
||||
Name: "TestInsertFeature2",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertFeatureNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertFeature2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("bad version"),
|
||||
Version: "bad version",
|
||||
},
|
||||
} {
|
||||
id3, err := datastore.insertFeatureVersion(invalidFeatureVersion)
|
||||
@ -90,10 +101,13 @@ func TestInsertFeature(t *testing.T) {
|
||||
// Insert FeatureVersion and ensure we can find it.
|
||||
featureVersion := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace1"},
|
||||
Name: "TestInsertFeature1",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertFeatureNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertFeature1",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("2:3.0-imba"),
|
||||
Version: "2:3.0-imba",
|
||||
}
|
||||
id4, err := datastore.insertFeatureVersion(featureVersion)
|
||||
assert.Nil(t, err)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -16,12 +16,14 @@ package pgsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/guregu/null/zero"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/guregu/null/zero"
|
||||
)
|
||||
|
||||
func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities bool) (database.Layer, error) {
|
||||
@ -34,14 +36,26 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
|
||||
defer observeQueryTime("FindLayer", subquery, time.Now())
|
||||
|
||||
// Find the layer
|
||||
var layer database.Layer
|
||||
var parentID zero.Int
|
||||
var parentName zero.String
|
||||
var namespaceID zero.Int
|
||||
var namespaceName sql.NullString
|
||||
var (
|
||||
layer database.Layer
|
||||
parentID zero.Int
|
||||
parentName zero.String
|
||||
nsID zero.Int
|
||||
nsName sql.NullString
|
||||
nsVersionFormat sql.NullString
|
||||
)
|
||||
|
||||
t := time.Now()
|
||||
err := pgSQL.QueryRow(searchLayer, name).Scan(&layer.ID, &layer.Name, &layer.EngineVersion, &parentID, &parentName, &namespaceID, &namespaceName)
|
||||
err := pgSQL.QueryRow(searchLayer, name).Scan(
|
||||
&layer.ID,
|
||||
&layer.Name,
|
||||
&layer.EngineVersion,
|
||||
&parentID,
|
||||
&parentName,
|
||||
&nsID,
|
||||
&nsName,
|
||||
&nsVersionFormat,
|
||||
)
|
||||
observeQueryTime("FindLayer", "searchLayer", t)
|
||||
|
||||
if err != nil {
|
||||
@ -54,10 +68,11 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
|
||||
Name: parentName.String,
|
||||
}
|
||||
}
|
||||
if !namespaceID.IsZero() {
|
||||
if !nsID.IsZero() {
|
||||
layer.Namespace = &database.Namespace{
|
||||
Model: database.Model{ID: int(namespaceID.Int64)},
|
||||
Name: namespaceName.String,
|
||||
Model: database.Model{ID: int(nsID.Int64)},
|
||||
Name: nsName.String,
|
||||
VersionFormat: nsVersionFormat.String,
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,12 +140,20 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
|
||||
var modification string
|
||||
mapFeatureVersions := make(map[int]database.FeatureVersion)
|
||||
for rows.Next() {
|
||||
var featureVersion database.FeatureVersion
|
||||
|
||||
err = rows.Scan(&featureVersion.ID, &modification, &featureVersion.Feature.Namespace.ID,
|
||||
&featureVersion.Feature.Namespace.Name, &featureVersion.Feature.ID,
|
||||
&featureVersion.Feature.Name, &featureVersion.ID, &featureVersion.Version,
|
||||
&featureVersion.AddedBy.ID, &featureVersion.AddedBy.Name)
|
||||
var fv database.FeatureVersion
|
||||
err = rows.Scan(
|
||||
&fv.ID,
|
||||
&modification,
|
||||
&fv.Feature.Namespace.ID,
|
||||
&fv.Feature.Namespace.Name,
|
||||
&fv.Feature.Namespace.VersionFormat,
|
||||
&fv.Feature.ID,
|
||||
&fv.Feature.Name,
|
||||
&fv.ID,
|
||||
&fv.Version,
|
||||
&fv.AddedBy.ID,
|
||||
&fv.AddedBy.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return featureVersions, handleError("searchLayerFeatureVersion.Scan()", err)
|
||||
}
|
||||
@ -138,9 +161,9 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
|
||||
// Do transitive closure.
|
||||
switch modification {
|
||||
case "add":
|
||||
mapFeatureVersions[featureVersion.ID] = featureVersion
|
||||
mapFeatureVersions[fv.ID] = fv
|
||||
case "del":
|
||||
delete(mapFeatureVersions, featureVersion.ID)
|
||||
delete(mapFeatureVersions, fv.ID)
|
||||
default:
|
||||
log.Warningf("unknown Layer_diff_FeatureVersion's modification: %s", modification)
|
||||
return featureVersions, database.ErrInconsistent
|
||||
@ -182,9 +205,18 @@ func loadAffectedBy(tx *sql.Tx, featureVersions []database.FeatureVersion) error
|
||||
var featureversionID int
|
||||
for rows.Next() {
|
||||
var vulnerability database.Vulnerability
|
||||
err := rows.Scan(&featureversionID, &vulnerability.ID, &vulnerability.Name,
|
||||
&vulnerability.Description, &vulnerability.Link, &vulnerability.Severity,
|
||||
&vulnerability.Metadata, &vulnerability.Namespace.Name, &vulnerability.FixedBy)
|
||||
err := rows.Scan(
|
||||
&featureversionID,
|
||||
&vulnerability.ID,
|
||||
&vulnerability.Name,
|
||||
&vulnerability.Description,
|
||||
&vulnerability.Link,
|
||||
&vulnerability.Severity,
|
||||
&vulnerability.Metadata,
|
||||
&vulnerability.Namespace.Name,
|
||||
&vulnerability.Namespace.VersionFormat,
|
||||
&vulnerability.FixedBy,
|
||||
)
|
||||
if err != nil {
|
||||
return handleError("searchFeatureVersionVulnerability.Scan()", err)
|
||||
}
|
||||
@ -374,9 +406,9 @@ func createNV(features []database.FeatureVersion) (map[string]*database.FeatureV
|
||||
sliceNV := make([]string, 0, len(features))
|
||||
|
||||
for i := 0; i < len(features); i++ {
|
||||
featureVersion := &features[i]
|
||||
nv := featureVersion.Feature.Namespace.Name + ":" + featureVersion.Feature.Name + ":" + featureVersion.Version.String()
|
||||
mapNV[nv] = featureVersion
|
||||
fv := &features[i]
|
||||
nv := strings.Join([]string{fv.Feature.Namespace.Name, fv.Feature.Name, fv.Version}, ":")
|
||||
mapNV[nv] = fv
|
||||
sliceNV = append(sliceNV, nv)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -23,6 +23,9 @@ import (
|
||||
"github.com/coreos/clair/database"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// dpkg versioning is used to parse test packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
func TestFindLayer(t *testing.T) {
|
||||
@ -67,9 +70,9 @@ func TestFindLayer(t *testing.T) {
|
||||
|
||||
switch featureVersion.Feature.Name {
|
||||
case "wechat":
|
||||
assert.Equal(t, types.NewVersionUnsafe("0.5"), featureVersion.Version)
|
||||
assert.Equal(t, "0.5", featureVersion.Version)
|
||||
case "openssl":
|
||||
assert.Equal(t, types.NewVersionUnsafe("1.0"), featureVersion.Version)
|
||||
assert.Equal(t, "1.0", featureVersion.Version)
|
||||
default:
|
||||
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
||||
}
|
||||
@ -83,9 +86,9 @@ func TestFindLayer(t *testing.T) {
|
||||
|
||||
switch featureVersion.Feature.Name {
|
||||
case "wechat":
|
||||
assert.Equal(t, types.NewVersionUnsafe("0.5"), featureVersion.Version)
|
||||
assert.Equal(t, "0.5", featureVersion.Version)
|
||||
case "openssl":
|
||||
assert.Equal(t, types.NewVersionUnsafe("1.0"), featureVersion.Version)
|
||||
assert.Equal(t, "1.0", featureVersion.Version)
|
||||
|
||||
if assert.Len(t, featureVersion.AffectedBy, 1) {
|
||||
assert.Equal(t, "debian:7", featureVersion.AffectedBy[0].Namespace.Name)
|
||||
@ -93,7 +96,7 @@ func TestFindLayer(t *testing.T) {
|
||||
assert.Equal(t, types.High, featureVersion.AffectedBy[0].Severity)
|
||||
assert.Equal(t, "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", featureVersion.AffectedBy[0].Description)
|
||||
assert.Equal(t, "http://google.com/#q=CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Link)
|
||||
assert.Equal(t, types.NewVersionUnsafe("2.0"), featureVersion.AffectedBy[0].FixedBy)
|
||||
assert.Equal(t, "2.0", featureVersion.AffectedBy[0].FixedBy)
|
||||
}
|
||||
default:
|
||||
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
||||
@ -139,45 +142,63 @@ func testInsertLayerInvalid(t *testing.T, datastore database.Datastore) {
|
||||
func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
||||
f1 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"},
|
||||
Name: "TestInsertLayerFeature1",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature1",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.0"),
|
||||
Version: "1.0",
|
||||
}
|
||||
f2 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"},
|
||||
Name: "TestInsertLayerFeature2",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.34"),
|
||||
Version: "0.34",
|
||||
}
|
||||
f3 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"},
|
||||
Name: "TestInsertLayerFeature3",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature3",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.56"),
|
||||
Version: "0.56",
|
||||
}
|
||||
f4 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
||||
Name: "TestInsertLayerFeature2",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace3",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.34"),
|
||||
Version: "0.34",
|
||||
}
|
||||
f5 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
||||
Name: "TestInsertLayerFeature3",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace3",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature3",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.56"),
|
||||
Version: "0.56",
|
||||
}
|
||||
f6 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
||||
Name: "TestInsertLayerFeature4",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace3",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature4",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.666"),
|
||||
Version: "0.666",
|
||||
}
|
||||
|
||||
layers := []database.Layer{
|
||||
@ -185,16 +206,22 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
||||
Name: "TestInsertLayer1",
|
||||
},
|
||||
{
|
||||
Name: "TestInsertLayer2",
|
||||
Parent: &database.Layer{Name: "TestInsertLayer1"},
|
||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace1"},
|
||||
Name: "TestInsertLayer2",
|
||||
Parent: &database.Layer{Name: "TestInsertLayer1"},
|
||||
Namespace: &database.Namespace{
|
||||
Name: "TestInsertLayerNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
},
|
||||
// This layer changes the namespace and adds Features.
|
||||
{
|
||||
Name: "TestInsertLayer3",
|
||||
Parent: &database.Layer{Name: "TestInsertLayer2"},
|
||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace2"},
|
||||
Features: []database.FeatureVersion{f1, f2, f3},
|
||||
Name: "TestInsertLayer3",
|
||||
Parent: &database.Layer{Name: "TestInsertLayer2"},
|
||||
Namespace: &database.Namespace{
|
||||
Name: "TestInsertLayerNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Features: []database.FeatureVersion{f1, f2, f3},
|
||||
},
|
||||
// This layer covers the case where the last layer doesn't provide any new Feature.
|
||||
{
|
||||
@ -206,9 +233,12 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
||||
// It also modifies the Namespace ("upgrade") but keeps some Features not upgraded, their
|
||||
// Namespaces should then remain unchanged.
|
||||
{
|
||||
Name: "TestInsertLayer4b",
|
||||
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace3"},
|
||||
Name: "TestInsertLayer4b",
|
||||
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
||||
Namespace: &database.Namespace{
|
||||
Name: "TestInsertLayerNamespace3",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Features: []database.FeatureVersion{
|
||||
// Deletes TestInsertLayerFeature1.
|
||||
// Keep TestInsertLayerFeature2 (old Namespace should be kept):
|
||||
@ -263,18 +293,24 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
||||
func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
|
||||
f7 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
||||
Name: "TestInsertLayerFeature7",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestInsertLayerNamespace3",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Name: "TestInsertLayerFeature7",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.01"),
|
||||
Version: "0.01",
|
||||
}
|
||||
|
||||
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false)
|
||||
l3u := database.Layer{
|
||||
Name: l3.Name,
|
||||
Parent: l3.Parent,
|
||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespaceUpdated1"},
|
||||
Features: []database.FeatureVersion{f7},
|
||||
Name: l3.Name,
|
||||
Parent: l3.Parent,
|
||||
Namespace: &database.Namespace{
|
||||
Name: "TestInsertLayerNamespaceUpdated1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Features: []database.FeatureVersion{f7},
|
||||
}
|
||||
|
||||
l4u := database.Layer{
|
||||
@ -347,5 +383,5 @@ func testInsertLayerDelete(t *testing.T, datastore database.Datastore) {
|
||||
func cmpFV(a, b database.FeatureVersion) bool {
|
||||
return a.Feature.Name == b.Feature.Name &&
|
||||
a.Feature.Namespace.Name == b.Feature.Namespace.Name &&
|
||||
a.Version.String() == b.Version.String()
|
||||
a.Version == b.Version
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
29
database/pgsql/migrations/00006_add_version_format.go
Normal file
29
database/pgsql/migrations/00006_add_version_format.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package migrations
|
||||
|
||||
import "github.com/remind101/migrate"
|
||||
|
||||
func init() {
|
||||
RegisterMigration(migrate.Migration{
|
||||
ID: 6,
|
||||
Up: migrate.Queries([]string{
|
||||
`ALTER TABLE Namespace ADD COLUMN version_format varchar(128);`,
|
||||
}),
|
||||
Down: migrate.Queries([]string{
|
||||
`ALTER TABLE Namespace DROP COLUMN version_format;`,
|
||||
}),
|
||||
})
|
||||
}
|
@ -38,7 +38,7 @@ func (pgSQL *pgSQL) insertNamespace(namespace database.Namespace) (int, error) {
|
||||
defer observeQueryTime("insertNamespace", "all", time.Now())
|
||||
|
||||
var id int
|
||||
err := pgSQL.QueryRow(soiNamespace, namespace.Name).Scan(&id)
|
||||
err := pgSQL.QueryRow(soiNamespace, namespace.Name, namespace.VersionFormat).Scan(&id)
|
||||
if err != nil {
|
||||
return 0, handleError("soiNamespace", err)
|
||||
}
|
||||
@ -58,14 +58,14 @@ func (pgSQL *pgSQL) ListNamespaces() (namespaces []database.Namespace, err error
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var namespace database.Namespace
|
||||
var ns database.Namespace
|
||||
|
||||
err = rows.Scan(&namespace.ID, &namespace.Name)
|
||||
err = rows.Scan(&ns.ID, &ns.Name, &ns.VersionFormat)
|
||||
if err != nil {
|
||||
return namespaces, handleError("listNamespace.Scan()", err)
|
||||
}
|
||||
|
||||
namespaces = append(namespaces, namespace)
|
||||
namespaces = append(namespaces, ns)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return namespaces, handleError("listNamespace.Rows()", err)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -21,6 +21,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
|
||||
// dpkg versioning is used to parse test packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
func TestInsertNamespace(t *testing.T) {
|
||||
@ -37,9 +40,15 @@ func TestInsertNamespace(t *testing.T) {
|
||||
assert.Zero(t, id0)
|
||||
|
||||
// Insert Namespace and ensure we can find it.
|
||||
id1, err := datastore.insertNamespace(database.Namespace{Name: "TestInsertNamespace1"})
|
||||
id1, err := datastore.insertNamespace(database.Namespace{
|
||||
Name: "TestInsertNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
id2, err := datastore.insertNamespace(database.Namespace{Name: "TestInsertNamespace1"})
|
||||
id2, err := datastore.insertNamespace(database.Namespace{
|
||||
Name: "TestInsertNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, id1, id2)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -21,8 +21,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// dpkg versioning is used to parse test packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
func TestNotification(t *testing.T) {
|
||||
@ -39,13 +43,19 @@ func TestNotification(t *testing.T) {
|
||||
|
||||
// Create some data.
|
||||
f1 := database.Feature{
|
||||
Name: "TestNotificationFeature1",
|
||||
Namespace: database.Namespace{Name: "TestNotificationNamespace1"},
|
||||
Name: "TestNotificationFeature1",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestNotificationNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
}
|
||||
|
||||
f2 := database.Feature{
|
||||
Name: "TestNotificationFeature2",
|
||||
Namespace: database.Namespace{Name: "TestNotificationNamespace1"},
|
||||
Name: "TestNotificationFeature2",
|
||||
Namespace: database.Namespace{
|
||||
Name: "TestNotificationNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
}
|
||||
|
||||
l1 := database.Layer{
|
||||
@ -53,7 +63,7 @@ func TestNotification(t *testing.T) {
|
||||
Features: []database.FeatureVersion{
|
||||
{
|
||||
Feature: f1,
|
||||
Version: types.NewVersionUnsafe("0.1"),
|
||||
Version: "0.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -63,7 +73,7 @@ func TestNotification(t *testing.T) {
|
||||
Features: []database.FeatureVersion{
|
||||
{
|
||||
Feature: f1,
|
||||
Version: types.NewVersionUnsafe("0.2"),
|
||||
Version: "0.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -73,7 +83,7 @@ func TestNotification(t *testing.T) {
|
||||
Features: []database.FeatureVersion{
|
||||
{
|
||||
Feature: f1,
|
||||
Version: types.NewVersionUnsafe("0.3"),
|
||||
Version: "0.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -83,7 +93,7 @@ func TestNotification(t *testing.T) {
|
||||
Features: []database.FeatureVersion{
|
||||
{
|
||||
Feature: f2,
|
||||
Version: types.NewVersionUnsafe("0.1"),
|
||||
Version: "0.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -105,7 +115,7 @@ func TestNotification(t *testing.T) {
|
||||
FixedIn: []database.FeatureVersion{
|
||||
{
|
||||
Feature: f1,
|
||||
Version: types.NewVersionUnsafe("1.0"),
|
||||
Version: "1.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -165,11 +175,11 @@ func TestNotification(t *testing.T) {
|
||||
v1b.FixedIn = []database.FeatureVersion{
|
||||
{
|
||||
Feature: f1,
|
||||
Version: types.MinVersion,
|
||||
Version: versionfmt.MinVersion,
|
||||
},
|
||||
{
|
||||
Feature: f2,
|
||||
Version: types.MaxVersion,
|
||||
Version: versionfmt.MaxVersion,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -29,8 +29,8 @@ const (
|
||||
// namespace.go
|
||||
soiNamespace = `
|
||||
WITH new_namespace AS (
|
||||
INSERT INTO Namespace(name)
|
||||
SELECT CAST($1 AS VARCHAR)
|
||||
INSERT INTO Namespace(name, version_format)
|
||||
SELECT CAST($1 AS VARCHAR), CAST($2 AS VARCHAR)
|
||||
WHERE NOT EXISTS (SELECT name FROM Namespace WHERE name = $1)
|
||||
RETURNING id
|
||||
)
|
||||
@ -39,7 +39,7 @@ const (
|
||||
SELECT id FROM new_namespace`
|
||||
|
||||
searchNamespace = `SELECT id FROM Namespace WHERE name = $1`
|
||||
listNamespace = `SELECT id, name FROM Namespace`
|
||||
listNamespace = `SELECT id, name, version_format FROM Namespace`
|
||||
|
||||
// feature.go
|
||||
soiFeature = `
|
||||
@ -72,12 +72,12 @@ const (
|
||||
WHERE feature_id = $1`
|
||||
|
||||
insertVulnerabilityAffectsFeatureVersion = `
|
||||
INSERT INTO Vulnerability_Affects_FeatureVersion(vulnerability_id,
|
||||
featureversion_id, fixedin_id) VALUES($1, $2, $3)`
|
||||
INSERT INTO Vulnerability_Affects_FeatureVersion(vulnerability_id, featureversion_id, fixedin_id)
|
||||
VALUES($1, $2, $3)`
|
||||
|
||||
// layer.go
|
||||
searchLayer = `
|
||||
SELECT l.id, l.name, l.engineversion, p.id, p.name, n.id, n.name
|
||||
SELECT l.id, l.name, l.engineversion, p.id, p.name, n.id, n.name, n.version_format
|
||||
FROM Layer l
|
||||
LEFT JOIN Layer p ON l.parent_id = p.id
|
||||
LEFT JOIN Namespace n ON l.namespace_id = n.id
|
||||
@ -93,7 +93,7 @@ const (
|
||||
FROM Layer l, layer_tree lt
|
||||
WHERE l.id = lt.parent_id
|
||||
)
|
||||
SELECT ldf.featureversion_id, ldf.modification, fn.id, fn.name, f.id, f.name, fv.id, fv.version, ltree.id, ltree.name
|
||||
SELECT ldf.featureversion_id, ldf.modification, fn.id, fn.name, fn.version_format, f.id, f.name, fv.id, fv.version, ltree.id, ltree.name
|
||||
FROM Layer_diff_FeatureVersion ldf
|
||||
JOIN (
|
||||
SELECT row_number() over (ORDER BY depth DESC), id, name FROM layer_tree
|
||||
@ -103,7 +103,7 @@ const (
|
||||
|
||||
searchFeatureVersionVulnerability = `
|
||||
SELECT vafv.featureversion_id, v.id, v.name, v.description, v.link, v.severity, v.metadata,
|
||||
vn.name, vfif.version
|
||||
vn.name, vn.version_format, vfif.version
|
||||
FROM Vulnerability_Affects_FeatureVersion vafv, Vulnerability v,
|
||||
Namespace vn, Vulnerability_FixedIn_Feature vfif
|
||||
WHERE vafv.featureversion_id = ANY($1::integer[])
|
||||
@ -140,7 +140,7 @@ const (
|
||||
|
||||
// vulnerability.go
|
||||
searchVulnerabilityBase = `
|
||||
SELECT v.id, v.name, n.id, n.name, v.description, v.link, v.severity, v.metadata
|
||||
SELECT v.id, v.name, n.id, n.name, n.version_format, v.description, v.link, v.severity, v.metadata
|
||||
FROM Vulnerability v JOIN Namespace n ON v.namespace_id = n.id`
|
||||
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
|
||||
searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND v.name = $2 AND v.deleted_at IS NULL`
|
||||
|
6
database/pgsql/testdata/data.sql
vendored
6
database/pgsql/testdata/data.sql
vendored
@ -12,9 +12,9 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
INSERT INTO namespace (id, name) VALUES
|
||||
(1, 'debian:7'),
|
||||
(2, 'debian:8');
|
||||
INSERT INTO namespace (id, name, version_format) VALUES
|
||||
(1, 'debian:7', 'dpkg'),
|
||||
(2, 'debian:8', 'dpkg');
|
||||
|
||||
INSERT INTO feature (id, namespace_id, name) VALUES
|
||||
(1, 1, 'wechat'),
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -22,9 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/utils"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/guregu/null/zero"
|
||||
)
|
||||
|
||||
@ -60,6 +60,7 @@ func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID
|
||||
&vulnerability.Name,
|
||||
&vulnerability.Namespace.ID,
|
||||
&vulnerability.Namespace.Name,
|
||||
&vulnerability.Namespace.VersionFormat,
|
||||
&vulnerability.Description,
|
||||
&vulnerability.Link,
|
||||
&vulnerability.Severity,
|
||||
@ -117,6 +118,7 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.
|
||||
&vulnerability.Name,
|
||||
&vulnerability.Namespace.ID,
|
||||
&vulnerability.Namespace.Name,
|
||||
&vulnerability.Namespace.VersionFormat,
|
||||
&vulnerability.Description,
|
||||
&vulnerability.Link,
|
||||
&vulnerability.Severity,
|
||||
@ -163,7 +165,7 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.
|
||||
Namespace: vulnerability.Namespace,
|
||||
Name: featureVersionFeatureName.String,
|
||||
},
|
||||
Version: types.NewVersionUnsafe(featureVersionVersion.String),
|
||||
Version: featureVersionVersion.String,
|
||||
}
|
||||
vulnerability.FixedIn = append(vulnerability.FixedIn, featureVersion)
|
||||
}
|
||||
@ -182,7 +184,6 @@ func (pgSQL *pgSQL) InsertVulnerabilities(vulnerabilities []database.Vulnerabili
|
||||
for _, vulnerability := range vulnerabilities {
|
||||
err := pgSQL.insertVulnerability(vulnerability, false, generateNotifications)
|
||||
if err != nil {
|
||||
fmt.Printf("%#v\n", vulnerability)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -274,7 +275,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
|
||||
// for diffing existing vulnerabilities.
|
||||
var fixedIn []database.FeatureVersion
|
||||
for _, fv := range vulnerability.FixedIn {
|
||||
if fv.Version != types.MinVersion {
|
||||
if fv.Version != versionfmt.MinVersion {
|
||||
fixedIn = append(fixedIn, fv)
|
||||
}
|
||||
}
|
||||
@ -350,7 +351,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F
|
||||
different := false
|
||||
|
||||
for _, name := range addedNames {
|
||||
if diffMap[name].Version == types.MinVersion {
|
||||
if diffMap[name].Version == versionfmt.MinVersion {
|
||||
// MinVersion only makes sense when a Feature is already fixed in some version,
|
||||
// in which case we would be in the "inBothNames".
|
||||
continue
|
||||
@ -363,7 +364,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F
|
||||
for _, name := range inBothNames {
|
||||
fv := diffMap[name]
|
||||
|
||||
if fv.Version == types.MinVersion {
|
||||
if fv.Version == versionfmt.MinVersion {
|
||||
// MinVersion means that the Feature doesn't affect the Vulnerability anymore.
|
||||
delete(currentMap, name)
|
||||
different = true
|
||||
@ -453,7 +454,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner
|
||||
}
|
||||
|
||||
// Insert Vulnerability_Affects_FeatureVersion.
|
||||
err = linkVulnerabilityToFeatureVersions(tx, fixedInID, vulnerabilityID, fv.Feature.ID, fv.Version)
|
||||
err = linkVulnerabilityToFeatureVersions(tx, fixedInID, vulnerabilityID, fv.Feature.ID, fv.Feature.Namespace.VersionFormat, fv.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -462,7 +463,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID, featureID int, fixedInVersion types.Version) error {
|
||||
func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID, featureID int, versionFormat, fixedInVersion string) error {
|
||||
// Find every FeatureVersions of the Feature that the vulnerability affects.
|
||||
// TODO(Quentin-M): LIMIT
|
||||
rows, err := tx.Query(searchFeatureVersionByFeature, featureID)
|
||||
@ -480,7 +481,11 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
|
||||
return handleError("searchFeatureVersionByFeature.Scan()", err)
|
||||
}
|
||||
|
||||
if affected.Version.Compare(fixedInVersion) < 0 {
|
||||
cmp, err := versionfmt.Compare(versionFormat, affected.Version, fixedInVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cmp < 0 {
|
||||
// The version of the FeatureVersion is lower than the fixed version of this vulnerability,
|
||||
// thus, this FeatureVersion is affected by it.
|
||||
affecteds = append(affecteds, affected)
|
||||
@ -494,8 +499,7 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
|
||||
// Insert into Vulnerability_Affects_FeatureVersion.
|
||||
for _, affected := range affecteds {
|
||||
// TODO(Quentin-M): Batch me.
|
||||
_, err := tx.Exec(insertVulnerabilityAffectsFeatureVersion, vulnerabilityID,
|
||||
affected.ID, fixedInID)
|
||||
_, err := tx.Exec(insertVulnerabilityAffectsFeatureVersion, vulnerabilityID, affected.ID, fixedInID)
|
||||
if err != nil {
|
||||
return handleError("insertVulnerabilityAffectsFeatureVersion", err)
|
||||
}
|
||||
@ -534,7 +538,7 @@ func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerability
|
||||
Name: vulnerabilityNamespace,
|
||||
},
|
||||
},
|
||||
Version: types.MinVersion,
|
||||
Version: versionfmt.MinVersion,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -21,8 +21,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// dpkg versioning is used to parse test packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
func TestFindVulnerability(t *testing.T) {
|
||||
@ -43,15 +47,18 @@ func TestFindVulnerability(t *testing.T) {
|
||||
Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0",
|
||||
Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7",
|
||||
Severity: types.High,
|
||||
Namespace: database.Namespace{Name: "debian:7"},
|
||||
Namespace: database.Namespace{
|
||||
Name: "debian:7",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
FixedIn: []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{Name: "openssl"},
|
||||
Version: types.NewVersionUnsafe("2.0"),
|
||||
Version: "2.0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "libssl"},
|
||||
Version: types.NewVersionUnsafe("1.9-abc"),
|
||||
Version: "1.9-abc",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -65,8 +72,11 @@ func TestFindVulnerability(t *testing.T) {
|
||||
v2 := database.Vulnerability{
|
||||
Name: "CVE-NOPE",
|
||||
Description: "A vulnerability affecting nothing",
|
||||
Namespace: database.Namespace{Name: "debian:7"},
|
||||
Severity: types.Unknown,
|
||||
Namespace: database.Namespace{
|
||||
Name: "debian:7",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
Severity: types.Unknown,
|
||||
}
|
||||
|
||||
v2f, err := datastore.FindVulnerability("debian:7", "CVE-NOPE")
|
||||
@ -106,58 +116,64 @@ func TestInsertVulnerability(t *testing.T) {
|
||||
defer datastore.Close()
|
||||
|
||||
// Create some data.
|
||||
n1 := database.Namespace{Name: "TestInsertVulnerabilityNamespace1"}
|
||||
n2 := database.Namespace{Name: "TestInsertVulnerabilityNamespace2"}
|
||||
n1 := database.Namespace{
|
||||
Name: "TestInsertVulnerabilityNamespace1",
|
||||
VersionFormat: "dpkg",
|
||||
}
|
||||
n2 := database.Namespace{
|
||||
Name: "TestInsertVulnerabilityNamespace2",
|
||||
VersionFormat: "dpkg",
|
||||
}
|
||||
|
||||
f1 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion1",
|
||||
Namespace: n1,
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.0"),
|
||||
Version: "1.0",
|
||||
}
|
||||
f2 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion1",
|
||||
Namespace: n2,
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.0"),
|
||||
Version: "1.0",
|
||||
}
|
||||
f3 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion2",
|
||||
},
|
||||
Version: types.MaxVersion,
|
||||
Version: versionfmt.MaxVersion,
|
||||
}
|
||||
f4 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.4"),
|
||||
Version: "1.4",
|
||||
}
|
||||
f5 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion3",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.5"),
|
||||
Version: "1.5",
|
||||
}
|
||||
f6 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion4",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.1"),
|
||||
Version: "0.1",
|
||||
}
|
||||
f7 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion5",
|
||||
},
|
||||
Version: types.MaxVersion,
|
||||
Version: versionfmt.MaxVersion,
|
||||
}
|
||||
f8 := database.FeatureVersion{
|
||||
Feature: database.Feature{
|
||||
Name: "TestInsertVulnerabilityFeatureVersion5",
|
||||
},
|
||||
Version: types.MinVersion,
|
||||
Version: versionfmt.MinVersion,
|
||||
}
|
||||
|
||||
// Insert invalid vulnerabilities.
|
||||
|
282
ext/versionfmt/dpkg/parser.go
Normal file
282
ext/versionfmt/dpkg/parser.go
Normal file
@ -0,0 +1,282 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dpkg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
)
|
||||
|
||||
type version struct {
|
||||
epoch int
|
||||
version string
|
||||
revision string
|
||||
}
|
||||
|
||||
var (
|
||||
minVersion = version{version: versionfmt.MinVersion}
|
||||
maxVersion = version{version: versionfmt.MaxVersion}
|
||||
|
||||
versionAllowedSymbols = []rune{'.', '-', '+', '~', ':', '_'}
|
||||
revisionAllowedSymbols = []rune{'.', '+', '~', '_'}
|
||||
)
|
||||
|
||||
// newVersion function parses a string into a Version struct which can be compared
|
||||
//
|
||||
// The implementation is based on http://man.he.net/man5/deb-version
|
||||
// on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
||||
//
|
||||
// It uses the dpkg-1.17.25's algorithm (lib/parsehelp.c)
|
||||
func newVersion(str string) (version, error) {
|
||||
var v version
|
||||
|
||||
// Trim leading and trailing space
|
||||
str = strings.TrimSpace(str)
|
||||
|
||||
if len(str) == 0 {
|
||||
return version{}, errors.New("Version string is empty")
|
||||
}
|
||||
|
||||
// Max/Min versions
|
||||
if str == maxVersion.String() {
|
||||
return maxVersion, nil
|
||||
}
|
||||
if str == minVersion.String() {
|
||||
return minVersion, nil
|
||||
}
|
||||
|
||||
// Find epoch
|
||||
sepepoch := strings.Index(str, ":")
|
||||
if sepepoch > -1 {
|
||||
intepoch, err := strconv.Atoi(str[:sepepoch])
|
||||
if err == nil {
|
||||
v.epoch = intepoch
|
||||
} else {
|
||||
return version{}, errors.New("epoch in version is not a number")
|
||||
}
|
||||
if intepoch < 0 {
|
||||
return version{}, errors.New("epoch in version is negative")
|
||||
}
|
||||
} else {
|
||||
v.epoch = 0
|
||||
}
|
||||
|
||||
// Find version / revision
|
||||
seprevision := strings.LastIndex(str, "-")
|
||||
if seprevision > -1 {
|
||||
v.version = str[sepepoch+1 : seprevision]
|
||||
v.revision = str[seprevision+1:]
|
||||
} else {
|
||||
v.version = str[sepepoch+1:]
|
||||
v.revision = ""
|
||||
}
|
||||
// Verify format
|
||||
if len(v.version) == 0 {
|
||||
return version{}, errors.New("No version")
|
||||
}
|
||||
|
||||
if !unicode.IsDigit(rune(v.version[0])) {
|
||||
return version{}, errors.New("version does not start with digit")
|
||||
}
|
||||
|
||||
for i := 0; i < len(v.version); i = i + 1 {
|
||||
r := rune(v.version[i])
|
||||
if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !containsRune(versionAllowedSymbols, r) {
|
||||
return version{}, errors.New("invalid character in version")
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(v.revision); i = i + 1 {
|
||||
r := rune(v.revision[i])
|
||||
if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !containsRune(revisionAllowedSymbols, r) {
|
||||
return version{}, errors.New("invalid character in revision")
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type parser struct{}
|
||||
|
||||
func (p parser) Valid(str string) bool {
|
||||
_, err := newVersion(str)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Compare function compares two Debian-like package version
|
||||
//
|
||||
// The implementation is based on http://man.he.net/man5/deb-version
|
||||
// on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
||||
//
|
||||
// It uses the dpkg-1.17.25's algorithm (lib/version.c)
|
||||
func (p parser) Compare(a, b string) (int, error) {
|
||||
v1, err := newVersion(a)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
v2, err := newVersion(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Quick check
|
||||
if v1 == v2 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Max/Min comparison
|
||||
if v1 == minVersion || v2 == maxVersion {
|
||||
return -1, nil
|
||||
}
|
||||
if v2 == minVersion || v1 == maxVersion {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// Compare epochs
|
||||
if v1.epoch > v2.epoch {
|
||||
return 1, nil
|
||||
}
|
||||
if v1.epoch < v2.epoch {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// Compare version
|
||||
rc := verrevcmp(v1.version, v2.version)
|
||||
if rc != 0 {
|
||||
return signum(rc), nil
|
||||
}
|
||||
|
||||
// Compare revision
|
||||
return signum(verrevcmp(v1.revision, v2.revision)), nil
|
||||
}
|
||||
|
||||
// String returns the string representation of a Version.
|
||||
func (v version) String() (s string) {
|
||||
if v.epoch != 0 {
|
||||
s = strconv.Itoa(v.epoch) + ":"
|
||||
}
|
||||
s += v.version
|
||||
if v.revision != "" {
|
||||
s += "-" + v.revision
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func verrevcmp(t1, t2 string) int {
|
||||
t1, rt1 := nextRune(t1)
|
||||
t2, rt2 := nextRune(t2)
|
||||
|
||||
for rt1 != nil || rt2 != nil {
|
||||
firstDiff := 0
|
||||
|
||||
for (rt1 != nil && !unicode.IsDigit(*rt1)) || (rt2 != nil && !unicode.IsDigit(*rt2)) {
|
||||
ac := 0
|
||||
bc := 0
|
||||
if rt1 != nil {
|
||||
ac = order(*rt1)
|
||||
}
|
||||
if rt2 != nil {
|
||||
bc = order(*rt2)
|
||||
}
|
||||
|
||||
if ac != bc {
|
||||
return ac - bc
|
||||
}
|
||||
|
||||
t1, rt1 = nextRune(t1)
|
||||
t2, rt2 = nextRune(t2)
|
||||
}
|
||||
for rt1 != nil && *rt1 == '0' {
|
||||
t1, rt1 = nextRune(t1)
|
||||
}
|
||||
for rt2 != nil && *rt2 == '0' {
|
||||
t2, rt2 = nextRune(t2)
|
||||
}
|
||||
for rt1 != nil && unicode.IsDigit(*rt1) && rt2 != nil && unicode.IsDigit(*rt2) {
|
||||
if firstDiff == 0 {
|
||||
firstDiff = int(*rt1) - int(*rt2)
|
||||
}
|
||||
t1, rt1 = nextRune(t1)
|
||||
t2, rt2 = nextRune(t2)
|
||||
}
|
||||
if rt1 != nil && unicode.IsDigit(*rt1) {
|
||||
return 1
|
||||
}
|
||||
if rt2 != nil && unicode.IsDigit(*rt2) {
|
||||
return -1
|
||||
}
|
||||
if firstDiff != 0 {
|
||||
return firstDiff
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// order compares runes using a modified ASCII table
|
||||
// so that letters are sorted earlier than non-letters
|
||||
// and so that tildes sorts before anything
|
||||
func order(r rune) int {
|
||||
if unicode.IsDigit(r) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if unicode.IsLetter(r) {
|
||||
return int(r)
|
||||
}
|
||||
|
||||
if r == '~' {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(r) + 256
|
||||
}
|
||||
|
||||
func nextRune(str string) (string, *rune) {
|
||||
if len(str) >= 1 {
|
||||
r := rune(str[0])
|
||||
return str[1:], &r
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func containsRune(s []rune, e rune) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func signum(a int) int {
|
||||
switch {
|
||||
case a < 0:
|
||||
return -1
|
||||
case a > 0:
|
||||
return +1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionfmt.RegisterParser("dpkg", parser{})
|
||||
}
|
197
ext/versionfmt/dpkg/parser_test.go
Normal file
197
ext/versionfmt/dpkg/parser_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dpkg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
LESS = -1
|
||||
EQUAL = 0
|
||||
GREATER = 1
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
str string
|
||||
ver version
|
||||
err bool
|
||||
}{
|
||||
// Test 0
|
||||
{"0", version{epoch: 0, version: "0", revision: ""}, false},
|
||||
{"0:0", version{epoch: 0, version: "0", revision: ""}, false},
|
||||
{"0:0-", version{epoch: 0, version: "0", revision: ""}, false},
|
||||
{"0:0-0", version{epoch: 0, version: "0", revision: "0"}, false},
|
||||
{"0:0.0-0.0", version{epoch: 0, version: "0.0", revision: "0.0"}, false},
|
||||
// Test epoched
|
||||
{"1:0", version{epoch: 1, version: "0", revision: ""}, false},
|
||||
{"5:1", version{epoch: 5, version: "1", revision: ""}, false},
|
||||
// Test multiple hypens
|
||||
{"0:0-0-0", version{epoch: 0, version: "0-0", revision: "0"}, false},
|
||||
{"0:0-0-0-0", version{epoch: 0, version: "0-0-0", revision: "0"}, false},
|
||||
// Test multiple colons
|
||||
{"0:0:0-0", version{epoch: 0, version: "0:0", revision: "0"}, false},
|
||||
{"0:0:0:0-0", version{epoch: 0, version: "0:0:0", revision: "0"}, false},
|
||||
// Test multiple hyphens and colons
|
||||
{"0:0:0-0-0", version{epoch: 0, version: "0:0-0", revision: "0"}, false},
|
||||
{"0:0-0:0-0", version{epoch: 0, version: "0-0:0", revision: "0"}, false},
|
||||
// Test valid characters in version
|
||||
{"0:09azAZ.-+~:_-0", version{epoch: 0, version: "09azAZ.-+~:_", revision: "0"}, false},
|
||||
// Test valid characters in debian revision
|
||||
{"0:0-azAZ09.+~_", version{epoch: 0, version: "0", revision: "azAZ09.+~_"}, false},
|
||||
// Test version with leading and trailing spaces
|
||||
{" 0:0-1", version{epoch: 0, version: "0", revision: "1"}, false},
|
||||
{"0:0-1 ", version{epoch: 0, version: "0", revision: "1"}, false},
|
||||
{" 0:0-1 ", version{epoch: 0, version: "0", revision: "1"}, false},
|
||||
// Test empty version
|
||||
{"", version{}, true},
|
||||
{" ", version{}, true},
|
||||
{"0:", version{}, true},
|
||||
// Test version with embedded spaces
|
||||
{"0:0 0-1", version{}, true},
|
||||
// Test version with negative epoch
|
||||
{"-1:0-1", version{}, true},
|
||||
// Test invalid characters in epoch
|
||||
{"a:0-0", version{}, true},
|
||||
{"A:0-0", version{}, true},
|
||||
// Test version not starting with a digit
|
||||
{"0:abc3-0", version{}, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
v, err := newVersion(c.str)
|
||||
|
||||
if c.err {
|
||||
assert.Error(t, err, "When parsing '%s'", c.str)
|
||||
} else {
|
||||
assert.Nil(t, err, "When parsing '%s'", c.str)
|
||||
}
|
||||
assert.Equal(t, c.ver, v, "When parsing '%s'", c.str)
|
||||
}
|
||||
|
||||
// Test invalid characters in version
|
||||
versym := []rune{'!', '#', '@', '$', '%', '&', '/', '|', '\\', '<', '>', '(', ')', '[', ']', '{', '}', ';', ',', '=', '*', '^', '\''}
|
||||
for _, r := range versym {
|
||||
_, err := newVersion(strings.Join([]string{"0:0", string(r), "-0"}, ""))
|
||||
assert.Error(t, err, "Parsing with invalid character '%s' in version should have failed", string(r))
|
||||
}
|
||||
|
||||
// Test invalid characters in revision
|
||||
versym = []rune{'!', '#', '@', '$', '%', '&', '/', '|', '\\', '<', '>', '(', ')', '[', ']', '{', '}', ':', ';', ',', '=', '*', '^', '\''}
|
||||
for _, r := range versym {
|
||||
_, err := newVersion(strings.Join([]string{"0:0-", string(r)}, ""))
|
||||
assert.Error(t, err, "Parsing with invalid character '%s' in revision should have failed", string(r))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAndCompare(t *testing.T) {
|
||||
cases := []struct {
|
||||
v1 string
|
||||
expected int
|
||||
v2 string
|
||||
}{
|
||||
{"7.6p2-4", GREATER, "7.6-0"},
|
||||
{"1.0.3-3", GREATER, "1.0-1"},
|
||||
{"1.3", GREATER, "1.2.2-2"},
|
||||
{"1.3", GREATER, "1.2.2"},
|
||||
// Some properties of text strings
|
||||
{"0-pre", EQUAL, "0-pre"},
|
||||
{"0-pre", LESS, "0-pree"},
|
||||
{"1.1.6r2-2", GREATER, "1.1.6r-1"},
|
||||
{"2.6b2-1", GREATER, "2.6b-2"},
|
||||
{"98.1p5-1", LESS, "98.1-pre2-b6-2"},
|
||||
{"0.4a6-2", GREATER, "0.4-1"},
|
||||
{"1:3.0.5-2", LESS, "1:3.0.5.1"},
|
||||
// epochs
|
||||
{"1:0.4", GREATER, "10.3"},
|
||||
{"1:1.25-4", LESS, "1:1.25-8"},
|
||||
{"0:1.18.36", EQUAL, "1.18.36"},
|
||||
{"1.18.36", GREATER, "1.18.35"},
|
||||
{"0:1.18.36", GREATER, "1.18.35"},
|
||||
// Funky, but allowed, characters in upstream version
|
||||
{"9:1.18.36:5.4-20", LESS, "10:0.5.1-22"},
|
||||
{"9:1.18.36:5.4-20", LESS, "9:1.18.36:5.5-1"},
|
||||
{"9:1.18.36:5.4-20", LESS, " 9:1.18.37:4.3-22"},
|
||||
{"1.18.36-0.17.35-18", GREATER, "1.18.36-19"},
|
||||
// Junk
|
||||
{"1:1.2.13-3", LESS, "1:1.2.13-3.1"},
|
||||
{"2.0.7pre1-4", LESS, "2.0.7r-1"},
|
||||
// if a version includes a dash, it should be the debrev dash - policy says so
|
||||
{"0:0-0-0", GREATER, "0-0"},
|
||||
// do we like strange versions? Yes we like strange versions…
|
||||
{"0", EQUAL, "0"},
|
||||
{"0", EQUAL, "00"},
|
||||
// #205960
|
||||
{"3.0~rc1-1", LESS, "3.0-1"},
|
||||
// #573592 - debian policy 5.6.12
|
||||
{"1.0", EQUAL, "1.0-0"},
|
||||
{"0.2", LESS, "1.0-0"},
|
||||
{"1.0", LESS, "1.0-0+b1"},
|
||||
{"1.0", GREATER, "1.0-0~"},
|
||||
// "steal" the testcases from (old perl) cupt
|
||||
{"1.2.3", EQUAL, "1.2.3"}, // identical
|
||||
{"4.4.3-2", EQUAL, "4.4.3-2"}, // identical
|
||||
{"1:2ab:5", EQUAL, "1:2ab:5"}, // this is correct...
|
||||
{"7:1-a:b-5", EQUAL, "7:1-a:b-5"}, // and this
|
||||
{"57:1.2.3abYZ+~-4-5", EQUAL, "57:1.2.3abYZ+~-4-5"}, // and those too
|
||||
{"1.2.3", EQUAL, "0:1.2.3"}, // zero epoch
|
||||
{"1.2.3", EQUAL, "1.2.3-0"}, // zero revision
|
||||
{"009", EQUAL, "9"}, // zeroes…
|
||||
{"009ab5", EQUAL, "9ab5"}, // there as well
|
||||
{"1.2.3", LESS, "1.2.3-1"}, // added non-zero revision
|
||||
{"1.2.3", LESS, "1.2.4"}, // just bigger
|
||||
{"1.2.4", GREATER, "1.2.3"}, // order doesn't matter
|
||||
{"1.2.24", GREATER, "1.2.3"}, // bigger, eh?
|
||||
{"0.10.0", GREATER, "0.8.7"}, // bigger, eh?
|
||||
{"3.2", GREATER, "2.3"}, // major number rocks
|
||||
{"1.3.2a", GREATER, "1.3.2"}, // letters rock
|
||||
{"0.5.0~git", LESS, "0.5.0~git2"}, // numbers rock
|
||||
{"2a", LESS, "21"}, // but not in all places
|
||||
{"1.3.2a", LESS, "1.3.2b"}, // but there is another letter
|
||||
{"1:1.2.3", GREATER, "1.2.4"}, // epoch rocks
|
||||
{"1:1.2.3", LESS, "1:1.2.4"}, // bigger anyway
|
||||
{"1.2a+~bCd3", LESS, "1.2a++"}, // tilde doesn't rock
|
||||
{"1.2a+~bCd3", GREATER, "1.2a+~"}, // but first is longer!
|
||||
{"5:2", GREATER, "304-2"}, // epoch rocks
|
||||
{"5:2", LESS, "304:2"}, // so big epoch?
|
||||
{"25:2", GREATER, "3:2"}, // 25 > 3, obviously
|
||||
{"1:2:123", LESS, "1:12:3"}, // 12 > 2
|
||||
{"1.2-5", LESS, "1.2-3-5"}, // 1.2 < 1.2-3
|
||||
{"5.10.0", GREATER, "5.005"}, // preceding zeroes don't matters
|
||||
{"3a9.8", LESS, "3.10.2"}, // letters are before all letter symbols
|
||||
{"3a9.8", GREATER, "3~10"}, // but after the tilde
|
||||
{"1.4+OOo3.0.0~", LESS, "1.4+OOo3.0.0-4"}, // another tilde check
|
||||
{"2.4.7-1", LESS, "2.4.7-z"}, // revision comparing
|
||||
{"1.002-1+b2", GREATER, "1.00"}, // whatever...
|
||||
}
|
||||
|
||||
var (
|
||||
p parser
|
||||
cmp int
|
||||
err error
|
||||
)
|
||||
for _, c := range cases {
|
||||
cmp, err = p.Compare(c.v1, c.v2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, c.expected, cmp, "%s vs. %s, = %d, expected %d", c.v1, c.v2, cmp, c.expected)
|
||||
|
||||
cmp, err = p.Compare(c.v2, c.v1)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, -c.expected, cmp, "%s vs. %s, = %d, expected %d", c.v2, c.v1, cmp, -c.expected)
|
||||
}
|
||||
}
|
114
ext/versionfmt/driver.go
Normal file
114
ext/versionfmt/driver.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package versionfmt exposes functions to dynamically register formats used to
|
||||
// parse Feature Versions.
|
||||
package versionfmt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
const (
|
||||
// MinVersion is a special package version which is always sorted first.
|
||||
MinVersion = "#MINV#"
|
||||
|
||||
// MaxVersion is a special package version which is always sorted last
|
||||
MaxVersion = "#MAXV#"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnknownVersionFormat is returned when a function does not have enough
|
||||
// context to determine the format of a version.
|
||||
ErrUnknownVersionFormat = errors.New("unknown version format")
|
||||
|
||||
// ErrInvalidVersion is returned when a function needs to validate a version,
|
||||
// but should return an error in the case where the version is invalid.
|
||||
ErrInvalidVersion = errors.New("invalid version")
|
||||
|
||||
nlog = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/versionfmt")
|
||||
|
||||
parsersM sync.Mutex
|
||||
parsers = make(map[string]Parser)
|
||||
)
|
||||
|
||||
// Parser represents any format that can compare two version strings.
|
||||
type Parser interface {
|
||||
// Valid attempts to parse a version string and returns its success.
|
||||
Valid(string) bool
|
||||
|
||||
// Compare parses two different version strings.
|
||||
// Returns 0 when equal, -1 when a < b, 1 when b < a.
|
||||
Compare(a, b string) (int, error)
|
||||
}
|
||||
|
||||
// RegisterParser provides a way to dynamically register an implementation of a
|
||||
// Parser.
|
||||
//
|
||||
// If RegisterParser is called twice with the same name, the name is blank, or
|
||||
// if the provided Parser is nil, this function panics.
|
||||
func RegisterParser(name string, p Parser) {
|
||||
if name == "" {
|
||||
panic("Could not register a Parser with an empty name")
|
||||
}
|
||||
if p == nil {
|
||||
panic("Could not register a nil Parser")
|
||||
}
|
||||
|
||||
parsersM.Lock()
|
||||
defer parsersM.Unlock()
|
||||
|
||||
if _, alreadyExists := parsers[name]; alreadyExists {
|
||||
panic("Parser '" + name + "' is already registered")
|
||||
}
|
||||
parsers[name] = p
|
||||
}
|
||||
|
||||
// GetParser returns the registered Parser with a provided name.
|
||||
func GetParser(name string) (p Parser, exists bool) {
|
||||
parsersM.Lock()
|
||||
defer parsersM.Unlock()
|
||||
|
||||
p, exists = parsers[name]
|
||||
return
|
||||
}
|
||||
|
||||
// Valid is a helper function that will return an error if the version fails to
|
||||
// validate with a given format.
|
||||
func Valid(format, version string) error {
|
||||
versionParser, exists := GetParser(format)
|
||||
if !exists {
|
||||
return ErrUnknownVersionFormat
|
||||
}
|
||||
|
||||
if !versionParser.Valid(version) {
|
||||
return ErrInvalidVersion
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compare is a helper function that will compare two versions with a given
|
||||
// format and return an error if there are any failures.
|
||||
func Compare(format, versionA, versionB string) (int, error) {
|
||||
versionParser, exists := GetParser(format)
|
||||
if !exists {
|
||||
return 0, ErrUnknownVersionFormat
|
||||
}
|
||||
|
||||
return versionParser.Compare(versionA, versionB)
|
||||
}
|
289
ext/versionfmt/rpm/parser.go
Normal file
289
ext/versionfmt/rpm/parser.go
Normal file
@ -0,0 +1,289 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
)
|
||||
|
||||
type version struct {
|
||||
epoch int
|
||||
version string
|
||||
revision string
|
||||
}
|
||||
|
||||
var (
|
||||
minVersion = version{version: versionfmt.MinVersion}
|
||||
maxVersion = version{version: versionfmt.MaxVersion}
|
||||
|
||||
versionAllowedSymbols = []rune{'.', '-', '+', '~', ':', '_'}
|
||||
revisionAllowedSymbols = []rune{'.', '+', '~', '_'}
|
||||
)
|
||||
|
||||
// newVersion function parses a string into a Version struct which can be compared
|
||||
//
|
||||
// The implementation is based on http://man.he.net/man5/deb-version
|
||||
// on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
||||
//
|
||||
// It uses the dpkg-1.17.25's algorithm (lib/parsehelp.c)
|
||||
func newVersion(str string) (version, error) {
|
||||
var v version
|
||||
|
||||
// Trim leading and trailing space
|
||||
str = strings.TrimSpace(str)
|
||||
|
||||
if len(str) == 0 {
|
||||
return version{}, errors.New("Version string is empty")
|
||||
}
|
||||
|
||||
// Max/Min versions
|
||||
if str == maxVersion.String() {
|
||||
return maxVersion, nil
|
||||
}
|
||||
if str == minVersion.String() {
|
||||
return minVersion, nil
|
||||
}
|
||||
|
||||
// Find epoch
|
||||
sepepoch := strings.Index(str, ":")
|
||||
if sepepoch > -1 {
|
||||
intepoch, err := strconv.Atoi(str[:sepepoch])
|
||||
if err == nil {
|
||||
v.epoch = intepoch
|
||||
} else {
|
||||
return version{}, errors.New("epoch in version is not a number")
|
||||
}
|
||||
if intepoch < 0 {
|
||||
return version{}, errors.New("epoch in version is negative")
|
||||
}
|
||||
} else {
|
||||
v.epoch = 0
|
||||
}
|
||||
|
||||
// Find version / revision
|
||||
seprevision := strings.LastIndex(str, "-")
|
||||
if seprevision > -1 {
|
||||
v.version = str[sepepoch+1 : seprevision]
|
||||
v.revision = str[seprevision+1:]
|
||||
} else {
|
||||
v.version = str[sepepoch+1:]
|
||||
v.revision = ""
|
||||
}
|
||||
// Verify format
|
||||
if len(v.version) == 0 {
|
||||
return version{}, errors.New("No version")
|
||||
}
|
||||
|
||||
if !unicode.IsDigit(rune(v.version[0])) {
|
||||
return version{}, errors.New("version does not start with digit")
|
||||
}
|
||||
|
||||
for i := 0; i < len(v.version); i = i + 1 {
|
||||
r := rune(v.version[i])
|
||||
if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !containsRune(versionAllowedSymbols, r) {
|
||||
return version{}, errors.New("invalid character in version")
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(v.revision); i = i + 1 {
|
||||
r := rune(v.revision[i])
|
||||
if !unicode.IsDigit(r) && !unicode.IsLetter(r) && !containsRune(revisionAllowedSymbols, r) {
|
||||
return version{}, errors.New("invalid character in revision")
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// newVersionUnsafe is just a wrapper around NewVersion that ignore potentiel
|
||||
// parsing error. Useful for test purposes
|
||||
func newVersionUnsafe(str string) version {
|
||||
v, _ := newVersion(str)
|
||||
return v
|
||||
}
|
||||
|
||||
type parser struct{}
|
||||
|
||||
func (p parser) Valid(str string) bool {
|
||||
_, err := newVersion(str)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Compare function compares two Debian-like package version
|
||||
//
|
||||
// The implementation is based on http://man.he.net/man5/deb-version
|
||||
// on https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
||||
//
|
||||
// It uses the dpkg-1.17.25's algorithm (lib/version.c)
|
||||
func (p parser) Compare(a, b string) (int, error) {
|
||||
v1, err := newVersion(a)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
v2, err := newVersion(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Quick check
|
||||
if v1 == v2 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Max/Min comparison
|
||||
if v1 == minVersion || v2 == maxVersion {
|
||||
return -1, nil
|
||||
}
|
||||
if v2 == minVersion || v1 == maxVersion {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// Compare epochs
|
||||
if v1.epoch > v2.epoch {
|
||||
return 1, nil
|
||||
}
|
||||
if v1.epoch < v2.epoch {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// Compare version
|
||||
rc := verrevcmp(v1.version, v2.version)
|
||||
if rc != 0 {
|
||||
return signum(rc), nil
|
||||
}
|
||||
|
||||
// Compare revision
|
||||
return signum(verrevcmp(v1.revision, v2.revision)), nil
|
||||
}
|
||||
|
||||
// String returns the string representation of a Version.
|
||||
func (v version) String() (s string) {
|
||||
if v.epoch != 0 {
|
||||
s = strconv.Itoa(v.epoch) + ":"
|
||||
}
|
||||
s += v.version
|
||||
if v.revision != "" {
|
||||
s += "-" + v.revision
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func verrevcmp(t1, t2 string) int {
|
||||
t1, rt1 := nextRune(t1)
|
||||
t2, rt2 := nextRune(t2)
|
||||
|
||||
for rt1 != nil || rt2 != nil {
|
||||
firstDiff := 0
|
||||
|
||||
for (rt1 != nil && !unicode.IsDigit(*rt1)) || (rt2 != nil && !unicode.IsDigit(*rt2)) {
|
||||
ac := 0
|
||||
bc := 0
|
||||
if rt1 != nil {
|
||||
ac = order(*rt1)
|
||||
}
|
||||
if rt2 != nil {
|
||||
bc = order(*rt2)
|
||||
}
|
||||
|
||||
if ac != bc {
|
||||
return ac - bc
|
||||
}
|
||||
|
||||
t1, rt1 = nextRune(t1)
|
||||
t2, rt2 = nextRune(t2)
|
||||
}
|
||||
for rt1 != nil && *rt1 == '0' {
|
||||
t1, rt1 = nextRune(t1)
|
||||
}
|
||||
for rt2 != nil && *rt2 == '0' {
|
||||
t2, rt2 = nextRune(t2)
|
||||
}
|
||||
for rt1 != nil && unicode.IsDigit(*rt1) && rt2 != nil && unicode.IsDigit(*rt2) {
|
||||
if firstDiff == 0 {
|
||||
firstDiff = int(*rt1) - int(*rt2)
|
||||
}
|
||||
t1, rt1 = nextRune(t1)
|
||||
t2, rt2 = nextRune(t2)
|
||||
}
|
||||
if rt1 != nil && unicode.IsDigit(*rt1) {
|
||||
return 1
|
||||
}
|
||||
if rt2 != nil && unicode.IsDigit(*rt2) {
|
||||
return -1
|
||||
}
|
||||
if firstDiff != 0 {
|
||||
return firstDiff
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// order compares runes using a modified ASCII table
|
||||
// so that letters are sorted earlier than non-letters
|
||||
// and so that tildes sorts before anything
|
||||
func order(r rune) int {
|
||||
if unicode.IsDigit(r) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if unicode.IsLetter(r) {
|
||||
return int(r)
|
||||
}
|
||||
|
||||
if r == '~' {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(r) + 256
|
||||
}
|
||||
|
||||
func nextRune(str string) (string, *rune) {
|
||||
if len(str) >= 1 {
|
||||
r := rune(str[0])
|
||||
return str[1:], &r
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func containsRune(s []rune, e rune) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func signum(a int) int {
|
||||
switch {
|
||||
case a < 0:
|
||||
return -1
|
||||
case a > 0:
|
||||
return +1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionfmt.RegisterParser("rpm", parser{})
|
||||
}
|
197
ext/versionfmt/rpm/parser_test.go
Normal file
197
ext/versionfmt/rpm/parser_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
LESS = -1
|
||||
EQUAL = 0
|
||||
GREATER = 1
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
str string
|
||||
ver version
|
||||
err bool
|
||||
}{
|
||||
// Test 0
|
||||
{"0", version{epoch: 0, version: "0", revision: ""}, false},
|
||||
{"0:0", version{epoch: 0, version: "0", revision: ""}, false},
|
||||
{"0:0-", version{epoch: 0, version: "0", revision: ""}, false},
|
||||
{"0:0-0", version{epoch: 0, version: "0", revision: "0"}, false},
|
||||
{"0:0.0-0.0", version{epoch: 0, version: "0.0", revision: "0.0"}, false},
|
||||
// Test epoched
|
||||
{"1:0", version{epoch: 1, version: "0", revision: ""}, false},
|
||||
{"5:1", version{epoch: 5, version: "1", revision: ""}, false},
|
||||
// Test multiple hypens
|
||||
{"0:0-0-0", version{epoch: 0, version: "0-0", revision: "0"}, false},
|
||||
{"0:0-0-0-0", version{epoch: 0, version: "0-0-0", revision: "0"}, false},
|
||||
// Test multiple colons
|
||||
{"0:0:0-0", version{epoch: 0, version: "0:0", revision: "0"}, false},
|
||||
{"0:0:0:0-0", version{epoch: 0, version: "0:0:0", revision: "0"}, false},
|
||||
// Test multiple hyphens and colons
|
||||
{"0:0:0-0-0", version{epoch: 0, version: "0:0-0", revision: "0"}, false},
|
||||
{"0:0-0:0-0", version{epoch: 0, version: "0-0:0", revision: "0"}, false},
|
||||
// Test valid characters in version
|
||||
{"0:09azAZ.-+~:_-0", version{epoch: 0, version: "09azAZ.-+~:_", revision: "0"}, false},
|
||||
// Test valid characters in debian revision
|
||||
{"0:0-azAZ09.+~_", version{epoch: 0, version: "0", revision: "azAZ09.+~_"}, false},
|
||||
// Test version with leading and trailing spaces
|
||||
{" 0:0-1", version{epoch: 0, version: "0", revision: "1"}, false},
|
||||
{"0:0-1 ", version{epoch: 0, version: "0", revision: "1"}, false},
|
||||
{" 0:0-1 ", version{epoch: 0, version: "0", revision: "1"}, false},
|
||||
// Test empty version
|
||||
{"", version{}, true},
|
||||
{" ", version{}, true},
|
||||
{"0:", version{}, true},
|
||||
// Test version with embedded spaces
|
||||
{"0:0 0-1", version{}, true},
|
||||
// Test version with negative epoch
|
||||
{"-1:0-1", version{}, true},
|
||||
// Test invalid characters in epoch
|
||||
{"a:0-0", version{}, true},
|
||||
{"A:0-0", version{}, true},
|
||||
// Test version not starting with a digit
|
||||
{"0:abc3-0", version{}, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
v, err := newVersion(c.str)
|
||||
|
||||
if c.err {
|
||||
assert.Error(t, err, "When parsing '%s'", c.str)
|
||||
} else {
|
||||
assert.Nil(t, err, "When parsing '%s'", c.str)
|
||||
}
|
||||
assert.Equal(t, c.ver, v, "When parsing '%s'", c.str)
|
||||
}
|
||||
|
||||
// Test invalid characters in version
|
||||
versym := []rune{'!', '#', '@', '$', '%', '&', '/', '|', '\\', '<', '>', '(', ')', '[', ']', '{', '}', ';', ',', '=', '*', '^', '\''}
|
||||
for _, r := range versym {
|
||||
_, err := newVersion(strings.Join([]string{"0:0", string(r), "-0"}, ""))
|
||||
assert.Error(t, err, "Parsing with invalid character '%s' in version should have failed", string(r))
|
||||
}
|
||||
|
||||
// Test invalid characters in revision
|
||||
versym = []rune{'!', '#', '@', '$', '%', '&', '/', '|', '\\', '<', '>', '(', ')', '[', ']', '{', '}', ':', ';', ',', '=', '*', '^', '\''}
|
||||
for _, r := range versym {
|
||||
_, err := newVersion(strings.Join([]string{"0:0-", string(r)}, ""))
|
||||
assert.Error(t, err, "Parsing with invalid character '%s' in revision should have failed", string(r))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAndCompare(t *testing.T) {
|
||||
cases := []struct {
|
||||
v1 string
|
||||
expected int
|
||||
v2 string
|
||||
}{
|
||||
{"7.6p2-4", GREATER, "7.6-0"},
|
||||
{"1.0.3-3", GREATER, "1.0-1"},
|
||||
{"1.3", GREATER, "1.2.2-2"},
|
||||
{"1.3", GREATER, "1.2.2"},
|
||||
// Some properties of text strings
|
||||
{"0-pre", EQUAL, "0-pre"},
|
||||
{"0-pre", LESS, "0-pree"},
|
||||
{"1.1.6r2-2", GREATER, "1.1.6r-1"},
|
||||
{"2.6b2-1", GREATER, "2.6b-2"},
|
||||
{"98.1p5-1", LESS, "98.1-pre2-b6-2"},
|
||||
{"0.4a6-2", GREATER, "0.4-1"},
|
||||
{"1:3.0.5-2", LESS, "1:3.0.5.1"},
|
||||
// epochs
|
||||
{"1:0.4", GREATER, "10.3"},
|
||||
{"1:1.25-4", LESS, "1:1.25-8"},
|
||||
{"0:1.18.36", EQUAL, "1.18.36"},
|
||||
{"1.18.36", GREATER, "1.18.35"},
|
||||
{"0:1.18.36", GREATER, "1.18.35"},
|
||||
// Funky, but allowed, characters in upstream version
|
||||
{"9:1.18.36:5.4-20", LESS, "10:0.5.1-22"},
|
||||
{"9:1.18.36:5.4-20", LESS, "9:1.18.36:5.5-1"},
|
||||
{"9:1.18.36:5.4-20", LESS, " 9:1.18.37:4.3-22"},
|
||||
{"1.18.36-0.17.35-18", GREATER, "1.18.36-19"},
|
||||
// Junk
|
||||
{"1:1.2.13-3", LESS, "1:1.2.13-3.1"},
|
||||
{"2.0.7pre1-4", LESS, "2.0.7r-1"},
|
||||
// if a version includes a dash, it should be the debrev dash - policy says so
|
||||
{"0:0-0-0", GREATER, "0-0"},
|
||||
// do we like strange versions? Yes we like strange versions…
|
||||
{"0", EQUAL, "0"},
|
||||
{"0", EQUAL, "00"},
|
||||
// #205960
|
||||
{"3.0~rc1-1", LESS, "3.0-1"},
|
||||
// #573592 - debian policy 5.6.12
|
||||
{"1.0", EQUAL, "1.0-0"},
|
||||
{"0.2", LESS, "1.0-0"},
|
||||
{"1.0", LESS, "1.0-0+b1"},
|
||||
{"1.0", GREATER, "1.0-0~"},
|
||||
// "steal" the testcases from (old perl) cupt
|
||||
{"1.2.3", EQUAL, "1.2.3"}, // identical
|
||||
{"4.4.3-2", EQUAL, "4.4.3-2"}, // identical
|
||||
{"1:2ab:5", EQUAL, "1:2ab:5"}, // this is correct...
|
||||
{"7:1-a:b-5", EQUAL, "7:1-a:b-5"}, // and this
|
||||
{"57:1.2.3abYZ+~-4-5", EQUAL, "57:1.2.3abYZ+~-4-5"}, // and those too
|
||||
{"1.2.3", EQUAL, "0:1.2.3"}, // zero epoch
|
||||
{"1.2.3", EQUAL, "1.2.3-0"}, // zero revision
|
||||
{"009", EQUAL, "9"}, // zeroes…
|
||||
{"009ab5", EQUAL, "9ab5"}, // there as well
|
||||
{"1.2.3", LESS, "1.2.3-1"}, // added non-zero revision
|
||||
{"1.2.3", LESS, "1.2.4"}, // just bigger
|
||||
{"1.2.4", GREATER, "1.2.3"}, // order doesn't matter
|
||||
{"1.2.24", GREATER, "1.2.3"}, // bigger, eh?
|
||||
{"0.10.0", GREATER, "0.8.7"}, // bigger, eh?
|
||||
{"3.2", GREATER, "2.3"}, // major number rocks
|
||||
{"1.3.2a", GREATER, "1.3.2"}, // letters rock
|
||||
{"0.5.0~git", LESS, "0.5.0~git2"}, // numbers rock
|
||||
{"2a", LESS, "21"}, // but not in all places
|
||||
{"1.3.2a", LESS, "1.3.2b"}, // but there is another letter
|
||||
{"1:1.2.3", GREATER, "1.2.4"}, // epoch rocks
|
||||
{"1:1.2.3", LESS, "1:1.2.4"}, // bigger anyway
|
||||
{"1.2a+~bCd3", LESS, "1.2a++"}, // tilde doesn't rock
|
||||
{"1.2a+~bCd3", GREATER, "1.2a+~"}, // but first is longer!
|
||||
{"5:2", GREATER, "304-2"}, // epoch rocks
|
||||
{"5:2", LESS, "304:2"}, // so big epoch?
|
||||
{"25:2", GREATER, "3:2"}, // 25 > 3, obviously
|
||||
{"1:2:123", LESS, "1:12:3"}, // 12 > 2
|
||||
{"1.2-5", LESS, "1.2-3-5"}, // 1.2 < 1.2-3
|
||||
{"5.10.0", GREATER, "5.005"}, // preceding zeroes don't matters
|
||||
{"3a9.8", LESS, "3.10.2"}, // letters are before all letter symbols
|
||||
{"3a9.8", GREATER, "3~10"}, // but after the tilde
|
||||
{"1.4+OOo3.0.0~", LESS, "1.4+OOo3.0.0-4"}, // another tilde check
|
||||
{"2.4.7-1", LESS, "2.4.7-z"}, // revision comparing
|
||||
{"1.002-1+b2", GREATER, "1.00"}, // whatever...
|
||||
}
|
||||
|
||||
var (
|
||||
p parser
|
||||
cmp int
|
||||
err error
|
||||
)
|
||||
for _, c := range cases {
|
||||
cmp, err = p.Compare(c.v1, c.v2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, c.expected, cmp, "%s vs. %s, = %d, expected %d", c.v1, c.v2, cmp, c.expected)
|
||||
|
||||
cmp, err = p.Compare(c.v2, c.v1)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, -c.expected, cmp, "%s vs. %s, = %d, expected %d", c.v2, c.v1, cmp, -c.expected)
|
||||
}
|
||||
}
|
@ -29,10 +29,14 @@ import (
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/updater"
|
||||
"github.com/coreos/clair/utils"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// dpkg versioning is used to parse Alpine Linux packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -219,7 +223,7 @@ func parse33YAML(r io.Reader) (vulns []database.Vulnerability, err error) {
|
||||
for _, pack := range file.Packages {
|
||||
pkg := pack.Pkg
|
||||
for _, fix := range pkg.Fixes {
|
||||
version, err := types.NewVersion(pkg.Version)
|
||||
err = versionfmt.Valid("dpkg", pkg.Version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", pkg.Version, err.Error())
|
||||
continue
|
||||
@ -235,7 +239,7 @@ func parse33YAML(r io.Reader) (vulns []database.Vulnerability, err error) {
|
||||
Namespace: database.Namespace{Name: "alpine:" + file.Distro},
|
||||
Name: pkg.Name,
|
||||
},
|
||||
Version: version,
|
||||
Version: pkg.Version,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -269,10 +273,10 @@ func parse34YAML(r io.Reader) (vulns []database.Vulnerability, err error) {
|
||||
|
||||
for _, pack := range file.Packages {
|
||||
pkg := pack.Pkg
|
||||
for versionStr, vulnStrs := range pkg.Fixes {
|
||||
version, err := types.NewVersion(versionStr)
|
||||
for version, vulnStrs := range pkg.Fixes {
|
||||
err := versionfmt.Valid("dpkg", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", versionStr, err.Error())
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", version, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -23,11 +23,16 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/updater"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
// dpkg versioning is used to parse Debian packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -168,23 +173,24 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
|
||||
}
|
||||
|
||||
// Determine the version of the package the vulnerability affects.
|
||||
var version types.Version
|
||||
var version string
|
||||
var err error
|
||||
if releaseNode.FixedVersion == "0" {
|
||||
// This means that the package is not affected by this vulnerability.
|
||||
version = types.MinVersion
|
||||
version = versionfmt.MinVersion
|
||||
} else if releaseNode.Status == "open" {
|
||||
// Open means that the package is currently vulnerable in the latest
|
||||
// version of this Debian release.
|
||||
version = types.MaxVersion
|
||||
version = versionfmt.MaxVersion
|
||||
} else if releaseNode.Status == "resolved" {
|
||||
// Resolved means that the vulnerability has been fixed in
|
||||
// "fixed_version" (if affected).
|
||||
version, err = types.NewVersion(releaseNode.FixedVersion)
|
||||
err = versionfmt.Valid("dpkg", releaseNode.FixedVersion)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", releaseNode.FixedVersion, err.Error())
|
||||
continue
|
||||
}
|
||||
version = releaseNode.FixedVersion
|
||||
}
|
||||
|
||||
// Create and add the feature version.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -44,7 +45,7 @@ func TestDebianParser(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "debian:8"},
|
||||
Name: "aptdaemon",
|
||||
},
|
||||
Version: types.MaxVersion,
|
||||
Version: versionfmt.MaxVersion,
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
@ -52,7 +53,7 @@ func TestDebianParser(t *testing.T) {
|
||||
|
||||
Name: "aptdaemon",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("1.1.1+bzr982-1"),
|
||||
Version: "1.1.1+bzr982-1",
|
||||
},
|
||||
}
|
||||
|
||||
@ -70,21 +71,21 @@ func TestDebianParser(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "debian:8"},
|
||||
Name: "aptdaemon",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.7.0"),
|
||||
Version: "0.7.0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "debian:unstable"},
|
||||
Name: "aptdaemon",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.7.0"),
|
||||
Version: "0.7.0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "debian:8"},
|
||||
Name: "asterisk",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.5.56"),
|
||||
Version: "0.5.56",
|
||||
},
|
||||
}
|
||||
|
||||
@ -102,7 +103,7 @@ func TestDebianParser(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "debian:8"},
|
||||
Name: "asterisk",
|
||||
},
|
||||
Version: types.MinVersion,
|
||||
Version: versionfmt.MinVersion,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -24,10 +24,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/updater"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
// rpm versioning is used to parse Oracle Linux packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -98,7 +102,6 @@ func (f *OracleFetcher) FetchUpdate(datastore database.Datastore) (resp updater.
|
||||
firstELSA = firstOracle5ELSA
|
||||
}
|
||||
|
||||
|
||||
// Fetch the update list.
|
||||
r, err := http.Get(ovalURI)
|
||||
if err != nil {
|
||||
@ -282,16 +285,20 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
|
||||
} else if strings.Contains(c.Comment, " is earlier than ") {
|
||||
const prefixLen = len(" is earlier than ")
|
||||
featureVersion.Feature.Name = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
|
||||
featureVersion.Version, err = types.NewVersion(c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:])
|
||||
version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]
|
||||
err := versionfmt.Valid("rpm", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:], err.Error())
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", version, err.Error())
|
||||
} else {
|
||||
featureVersion.Version = version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
featureVersion.Feature.Namespace.Name = "oracle" + ":" + strconv.Itoa(osVersion)
|
||||
featureVersion.Feature.Namespace.VersionFormat = "rpm"
|
||||
|
||||
if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.Version.String() != "" {
|
||||
if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.Version != "" {
|
||||
featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion
|
||||
} else {
|
||||
log.Warningf("could not determine a valid package from criterions: %v", criterions)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -23,6 +23,9 @@ import (
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
// rpm versioning is used to parse Oracle Linux packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
)
|
||||
|
||||
func TestOracleParser(t *testing.T) {
|
||||
@ -43,24 +46,33 @@ func TestOracleParser(t *testing.T) {
|
||||
expectedFeatureVersions := []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "oracle:7"},
|
||||
Name: "xerces-c",
|
||||
Namespace: database.Namespace{
|
||||
Name: "oracle:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "xerces-c",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
||||
Version: "0:3.1.1-7.el7_1",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "oracle:7"},
|
||||
Name: "xerces-c-devel",
|
||||
Namespace: database.Namespace{
|
||||
Name: "oracle:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "xerces-c-devel",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
||||
Version: "0:3.1.1-7.el7_1",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "oracle:7"},
|
||||
Name: "xerces-c-doc",
|
||||
Namespace: database.Namespace{
|
||||
Name: "oracle:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "xerces-c-doc",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
||||
Version: "0:3.1.1-7.el7_1",
|
||||
},
|
||||
}
|
||||
|
||||
@ -81,17 +93,23 @@ func TestOracleParser(t *testing.T) {
|
||||
expectedFeatureVersions := []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "oracle:6"},
|
||||
Name: "firefox",
|
||||
Namespace: database.Namespace{
|
||||
Name: "oracle:6",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "firefox",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("38.1.0-1.0.1.el6_6"),
|
||||
Version: "0:38.1.0-1.0.1.el6_6",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "oracle:7"},
|
||||
Name: "firefox",
|
||||
Namespace: database.Namespace{
|
||||
Name: "oracle:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "firefox",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("38.1.0-1.0.1.el7_1"),
|
||||
Version: "0:38.1.0-1.0.1.el7_1",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -23,11 +23,16 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/updater"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
// rpm versioning is used to parse Oracle Linux packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -283,9 +288,13 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
|
||||
} else if strings.Contains(c.Comment, " is earlier than ") {
|
||||
const prefixLen = len(" is earlier than ")
|
||||
featureVersion.Feature.Name = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
|
||||
featureVersion.Version, err = types.NewVersion(c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:])
|
||||
version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]
|
||||
err := versionfmt.Valid("rpm", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:], err.Error())
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", version, err.Error())
|
||||
} else {
|
||||
featureVersion.Version = version
|
||||
featureVersion.Feature.Namespace.VersionFormat = "rpm"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,7 +306,7 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.Version.String() != "" {
|
||||
if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.Version != "" {
|
||||
featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion
|
||||
} else {
|
||||
log.Warningf("could not determine a valid package from criterions: %v", criterions)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -23,6 +23,9 @@ import (
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
// rpm versioning is used to parse RHEL packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
)
|
||||
|
||||
func TestRHELParser(t *testing.T) {
|
||||
@ -41,24 +44,33 @@ func TestRHELParser(t *testing.T) {
|
||||
expectedFeatureVersions := []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "centos:7"},
|
||||
Name: "xerces-c",
|
||||
Namespace: database.Namespace{
|
||||
Name: "centos:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "xerces-c",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
||||
Version: "0:3.1.1-7.el7_1",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "centos:7"},
|
||||
Name: "xerces-c-devel",
|
||||
Namespace: database.Namespace{
|
||||
Name: "centos:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "xerces-c-devel",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
||||
Version: "0:3.1.1-7.el7_1",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "centos:7"},
|
||||
Name: "xerces-c-doc",
|
||||
Namespace: database.Namespace{
|
||||
Name: "centos:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "xerces-c-doc",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
||||
Version: "0:3.1.1-7.el7_1",
|
||||
},
|
||||
}
|
||||
|
||||
@ -79,17 +91,23 @@ func TestRHELParser(t *testing.T) {
|
||||
expectedFeatureVersions := []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "centos:6"},
|
||||
Name: "firefox",
|
||||
Namespace: database.Namespace{
|
||||
Name: "centos:6",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "firefox",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("38.1.0-1.el6_6"),
|
||||
Version: "0:38.1.0-1.el6_6",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "centos:7"},
|
||||
Name: "firefox",
|
||||
Namespace: database.Namespace{
|
||||
Name: "centos:7",
|
||||
VersionFormat: "rpm",
|
||||
},
|
||||
Name: "firefox",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("38.1.0-1.el7_1"),
|
||||
Version: "0:38.1.0-1.el7_1",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/updater"
|
||||
"github.com/coreos/clair/utils"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -344,21 +346,22 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
|
||||
continue
|
||||
}
|
||||
|
||||
var version types.Version
|
||||
var version string
|
||||
if md["status"] == "released" {
|
||||
if md["note"] != "" {
|
||||
var err error
|
||||
version, err = types.NewVersion(md["note"])
|
||||
err = versionfmt.Valid("dpkg", md["note"])
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", md["note"], err)
|
||||
}
|
||||
version = md["note"]
|
||||
}
|
||||
} else if md["status"] == "not-affected" {
|
||||
version = types.MinVersion
|
||||
version = versionfmt.MinVersion
|
||||
} else {
|
||||
version = types.MaxVersion
|
||||
version = versionfmt.MaxVersion
|
||||
}
|
||||
if version.String() == "" {
|
||||
if version == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -20,9 +20,11 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
)
|
||||
|
||||
func TestUbuntuParser(t *testing.T) {
|
||||
@ -48,21 +50,21 @@ func TestUbuntuParser(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "ubuntu:14.04"},
|
||||
Name: "libmspack",
|
||||
},
|
||||
Version: types.MaxVersion,
|
||||
Version: versionfmt.MaxVersion,
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "ubuntu:15.04"},
|
||||
Name: "libmspack",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.4-3"),
|
||||
Version: "0.4-3",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "ubuntu:15.10"},
|
||||
Name: "libmspack-anotherpkg",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.1"),
|
||||
Version: "0.1",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,26 @@
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
)
|
||||
|
||||
func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
||||
@ -15,7 +29,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "Namespace1"},
|
||||
Name: "Feature1",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.1"),
|
||||
Version: "0.1",
|
||||
}
|
||||
|
||||
fv2 := database.FeatureVersion{
|
||||
@ -23,7 +37,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "Namespace2"},
|
||||
Name: "Feature1",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.2"),
|
||||
Version: "0.2",
|
||||
}
|
||||
|
||||
fv3 := database.FeatureVersion{
|
||||
@ -31,7 +45,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
||||
Namespace: database.Namespace{Name: "Namespace2"},
|
||||
Name: "Feature2",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("0.3"),
|
||||
Version: "0.3",
|
||||
}
|
||||
|
||||
vulnerability := database.Vulnerability{
|
||||
|
@ -18,10 +18,14 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
|
||||
// dpkg versioning is used to parse apk packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "worker/detectors/packages")
|
||||
@ -55,17 +59,19 @@ func (d *detector) Detect(data map[string][]byte) ([]database.FeatureVersion, er
|
||||
case line[:2] == "P:":
|
||||
ipkg.Feature.Name = line[2:]
|
||||
case line[:2] == "V:":
|
||||
var err error
|
||||
ipkg.Version, err = types.NewVersion(line[2:])
|
||||
version := string(line[2:])
|
||||
err := versionfmt.Valid("dpkg", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", line[2:], err.Error())
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", version, err.Error())
|
||||
} else {
|
||||
ipkg.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a whole feature, store it in the set and try to parse a new
|
||||
// one.
|
||||
if ipkg.Feature.Name != "" && ipkg.Version.String() != "" {
|
||||
pkgSet[ipkg.Feature.Name+"#"+ipkg.Version.String()] = ipkg
|
||||
if ipkg.Feature.Name != "" && ipkg.Version != "" {
|
||||
pkgSet[ipkg.Feature.Name+"#"+ipkg.Version] = ipkg
|
||||
ipkg = database.FeatureVersion{}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/clair/worker/detectors/feature"
|
||||
)
|
||||
|
||||
@ -28,47 +27,47 @@ func TestAPKFeatureDetection(t *testing.T) {
|
||||
FeatureVersions: []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{Name: "musl"},
|
||||
Version: types.NewVersionUnsafe("1.1.14-r10"),
|
||||
Version: "1.1.14-r10",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "busybox"},
|
||||
Version: types.NewVersionUnsafe("1.24.2-r9"),
|
||||
Version: "1.24.2-r9",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "alpine-baselayout"},
|
||||
Version: types.NewVersionUnsafe("3.0.3-r0"),
|
||||
Version: "3.0.3-r0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "alpine-keys"},
|
||||
Version: types.NewVersionUnsafe("1.1-r0"),
|
||||
Version: "1.1-r0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "zlib"},
|
||||
Version: types.NewVersionUnsafe("1.2.8-r2"),
|
||||
Version: "1.2.8-r2",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "libcrypto1.0"},
|
||||
Version: types.NewVersionUnsafe("1.0.2h-r1"),
|
||||
Version: "1.0.2h-r1",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "libssl1.0"},
|
||||
Version: types.NewVersionUnsafe("1.0.2h-r1"),
|
||||
Version: "1.0.2h-r1",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "apk-tools"},
|
||||
Version: types.NewVersionUnsafe("2.6.7-r0"),
|
||||
Version: "2.6.7-r0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "scanelf"},
|
||||
Version: types.NewVersionUnsafe("1.1.6-r0"),
|
||||
Version: "1.1.6-r0",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "musl-utils"},
|
||||
Version: types.NewVersionUnsafe("1.1.14-r10"),
|
||||
Version: "1.1.14-r10",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "libc-utils"},
|
||||
Version: types.NewVersionUnsafe("0.7-r0"),
|
||||
Version: "0.7-r0",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
|
@ -19,10 +19,14 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
|
||||
// dpkg versioning is used to parse dpkg packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -60,7 +64,7 @@ func (detector *DpkgFeaturesDetector) Detect(data map[string][]byte) ([]database
|
||||
// Defines the name of the package
|
||||
|
||||
pkg.Feature.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
|
||||
pkg.Version = types.Version{}
|
||||
pkg.Version = ""
|
||||
} else if strings.HasPrefix(line, "Source: ") {
|
||||
// Source line (Optionnal)
|
||||
// Gives the name of the source package
|
||||
@ -74,28 +78,34 @@ func (detector *DpkgFeaturesDetector) Detect(data map[string][]byte) ([]database
|
||||
|
||||
pkg.Feature.Name = md["name"]
|
||||
if md["version"] != "" {
|
||||
pkg.Version, err = types.NewVersion(md["version"])
|
||||
version := md["version"]
|
||||
err = versionfmt.Valid("dpkg", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", line[1], err.Error())
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", string(line[1]), err.Error())
|
||||
} else {
|
||||
pkg.Version = version
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(line, "Version: ") && pkg.Version.String() == "" {
|
||||
} else if strings.HasPrefix(line, "Version: ") && pkg.Version == "" {
|
||||
// Version line
|
||||
// Defines the version of the package
|
||||
// This version is less important than a version retrieved from a Source line
|
||||
// because the Debian vulnerabilities often skips the epoch from the Version field
|
||||
// which is not present in the Source version, and because +bX revisions don't matter
|
||||
pkg.Version, err = types.NewVersion(strings.TrimPrefix(line, "Version: "))
|
||||
version := strings.TrimPrefix(line, "Version: ")
|
||||
err = versionfmt.Valid("dpkg", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", line[1], err.Error())
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", string(line[1]), err.Error())
|
||||
} else {
|
||||
pkg.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
// Add the package to the result array if we have all the informations
|
||||
if pkg.Feature.Name != "" && pkg.Version.String() != "" {
|
||||
packagesMap[pkg.Feature.Name+"#"+pkg.Version.String()] = pkg
|
||||
if pkg.Feature.Name != "" && pkg.Version != "" {
|
||||
packagesMap[pkg.Feature.Name+"#"+pkg.Version] = pkg
|
||||
pkg.Feature.Name = ""
|
||||
pkg.Version = types.Version{}
|
||||
pkg.Version = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/clair/worker/detectors/feature"
|
||||
)
|
||||
|
||||
@ -30,15 +29,15 @@ func TestDpkgFeatureDetection(t *testing.T) {
|
||||
// Two packages from this source are installed, it should only appear one time
|
||||
{
|
||||
Feature: database.Feature{Name: "pam"},
|
||||
Version: types.NewVersionUnsafe("1.1.8-3.1ubuntu3"),
|
||||
Version: "1.1.8-3.1ubuntu3",
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "makedev"}, // The source name and the package name are equals
|
||||
Version: types.NewVersionUnsafe("2.3.1-93ubuntu1"), // The version comes from the "Version:" line
|
||||
Feature: database.Feature{Name: "makedev"}, // The source name and the package name are equals
|
||||
Version: "2.3.1-93ubuntu1", // The version comes from the "Version:" line
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{Name: "gcc-5"},
|
||||
Version: types.NewVersionUnsafe("5.1.1-12ubuntu1"), // The version comes from the "Source:" line
|
||||
Version: "5.1.1-12ubuntu1", // The version comes from the "Source:" line
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
|
@ -20,12 +20,16 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/ext/versionfmt"
|
||||
"github.com/coreos/clair/utils"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/clair/worker/detectors"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
// rpm versioning is used to parse rpm packages.
|
||||
_ "github.com/coreos/clair/ext/versionfmt/rpm"
|
||||
)
|
||||
|
||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "rpm")
|
||||
@ -88,7 +92,8 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
|
||||
}
|
||||
|
||||
// Parse version
|
||||
version, err := types.NewVersion(strings.Replace(line[1], "(none):", "", -1))
|
||||
version := strings.Replace(line[1], "(none):", "", -1)
|
||||
err := versionfmt.Valid("rpm", version)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", line[1], err.Error())
|
||||
continue
|
||||
@ -101,7 +106,7 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
|
||||
},
|
||||
Version: version,
|
||||
}
|
||||
packagesMap[pkg.Feature.Name+"#"+pkg.Version.String()] = pkg
|
||||
packagesMap[pkg.Feature.Name+"#"+pkg.Version] = pkg
|
||||
}
|
||||
|
||||
// Convert the map to a slice
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 clair authors
|
||||
// Copyright 2016 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -18,7 +18,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/clair/worker/detectors/feature"
|
||||
)
|
||||
|
||||
@ -31,12 +30,12 @@ func TestRpmFeatureDetection(t *testing.T) {
|
||||
// Two packages from this source are installed, it should only appear once
|
||||
{
|
||||
Feature: database.Feature{Name: "centos-release"},
|
||||
Version: types.NewVersionUnsafe("7-1.1503.el7.centos.2.8"),
|
||||
Version: "7-1.1503.el7.centos.2.8",
|
||||
},
|
||||
// Two packages from this source are installed, it should only appear once
|
||||
{
|
||||
Feature: database.Feature{Name: "filesystem"},
|
||||
Version: types.NewVersionUnsafe("3.2-18.el7"),
|
||||
Version: "3.2-18.el7",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
|
@ -180,7 +180,7 @@ func detectFeatureVersions(name string, data map[string][]byte, namespace *datab
|
||||
parentFeatureNamespaces := make(map[string]database.Namespace)
|
||||
if parent != nil {
|
||||
for _, parentFeature := range parent.Features {
|
||||
parentFeatureNamespaces[parentFeature.Feature.Name+":"+parentFeature.Version.String()] = parentFeature.Feature.Namespace
|
||||
parentFeatureNamespaces[parentFeature.Feature.Name+":"+parentFeature.Version] = parentFeature.Feature.Namespace
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ func detectFeatureVersions(name string, data map[string][]byte, namespace *datab
|
||||
continue
|
||||
}
|
||||
|
||||
if parentFeatureNamespace, ok := parentFeatureNamespaces[feature.Feature.Name+":"+feature.Version.String()]; ok {
|
||||
if parentFeatureNamespace, ok := parentFeatureNamespaces[feature.Feature.Name+":"+feature.Version]; ok {
|
||||
// The FeatureVersion is present in the parent layer; associate with their Namespace.
|
||||
features[i].Feature.Namespace = parentFeatureNamespace
|
||||
continue
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
|
||||
// Register the required detectors.
|
||||
_ "github.com/coreos/clair/worker/detectors/data/docker"
|
||||
@ -62,14 +61,14 @@ func TestProcessWithDistUpgrade(t *testing.T) {
|
||||
|
||||
// Create the list of FeatureVersions that should not been upgraded from one layer to another.
|
||||
nonUpgradedFeatureVersions := []database.FeatureVersion{
|
||||
{Feature: database.Feature{Name: "libtext-wrapi18n-perl"}, Version: types.NewVersionUnsafe("0.06-7")},
|
||||
{Feature: database.Feature{Name: "libtext-charwidth-perl"}, Version: types.NewVersionUnsafe("0.04-7")},
|
||||
{Feature: database.Feature{Name: "libtext-iconv-perl"}, Version: types.NewVersionUnsafe("1.7-5")},
|
||||
{Feature: database.Feature{Name: "mawk"}, Version: types.NewVersionUnsafe("1.3.3-17")},
|
||||
{Feature: database.Feature{Name: "insserv"}, Version: types.NewVersionUnsafe("1.14.0-5")},
|
||||
{Feature: database.Feature{Name: "db"}, Version: types.NewVersionUnsafe("5.1.29-5")},
|
||||
{Feature: database.Feature{Name: "ustr"}, Version: types.NewVersionUnsafe("1.0.4-3")},
|
||||
{Feature: database.Feature{Name: "xz-utils"}, Version: types.NewVersionUnsafe("5.1.1alpha+20120614-2")},
|
||||
{Feature: database.Feature{Name: "libtext-wrapi18n-perl"}, Version: "0.06-7"},
|
||||
{Feature: database.Feature{Name: "libtext-charwidth-perl"}, Version: "0.04-7"},
|
||||
{Feature: database.Feature{Name: "libtext-iconv-perl"}, Version: "1.7-5"},
|
||||
{Feature: database.Feature{Name: "mawk"}, Version: "1.3.3-17"},
|
||||
{Feature: database.Feature{Name: "insserv"}, Version: "1.14.0-5"},
|
||||
{Feature: database.Feature{Name: "db"}, Version: "5.1.29-5"},
|
||||
{Feature: database.Feature{Name: "ustr"}, Version: "1.0.4-3"},
|
||||
{Feature: database.Feature{Name: "xz-utils"}, Version: "5.1.1alpha+20120614-2"},
|
||||
}
|
||||
|
||||
// Process test layers.
|
||||
|
Loading…
Reference in New Issue
Block a user