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"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
"github.com/fernet/fernet-go"
|
"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")
|
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1")
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Message string `json:"Layer`
|
Message string `json:"Layer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Layer struct {
|
type Layer struct {
|
||||||
@ -63,7 +65,8 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
|
|||||||
feature := Feature{
|
feature := Feature{
|
||||||
Name: dbFeatureVersion.Feature.Name,
|
Name: dbFeatureVersion.Feature.Name,
|
||||||
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
|
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
|
||||||
Version: dbFeatureVersion.Version.String(),
|
VersionFormat: dbFeatureVersion.Feature.Namespace.VersionFormat,
|
||||||
|
Version: dbFeatureVersion.Version,
|
||||||
AddedBy: dbFeatureVersion.AddedBy.Name,
|
AddedBy: dbFeatureVersion.AddedBy.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,8 +80,8 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
|
|||||||
Metadata: dbVuln.Metadata,
|
Metadata: dbVuln.Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbVuln.FixedBy != types.MaxVersion {
|
if dbVuln.FixedBy != versionfmt.MaxVersion {
|
||||||
vuln.FixedBy = dbVuln.FixedBy.String()
|
vuln.FixedBy = dbVuln.FixedBy
|
||||||
}
|
}
|
||||||
feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
|
feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
|
||||||
}
|
}
|
||||||
@ -91,6 +94,7 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
|
|||||||
|
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
Name string `json:"Name,omitempty"`
|
Name string `json:"Name,omitempty"`
|
||||||
|
VersionFormat string `json:"VersionFormat,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vulnerability struct {
|
type Vulnerability struct {
|
||||||
@ -153,44 +157,51 @@ func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn b
|
|||||||
type Feature struct {
|
type Feature struct {
|
||||||
Name string `json:"Name,omitempty"`
|
Name string `json:"Name,omitempty"`
|
||||||
NamespaceName string `json:"NamespaceName,omitempty"`
|
NamespaceName string `json:"NamespaceName,omitempty"`
|
||||||
|
VersionFormat string `json:"VersionFormat,omitempty"`
|
||||||
Version string `json:"Version,omitempty"`
|
Version string `json:"Version,omitempty"`
|
||||||
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
|
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
|
||||||
AddedBy string `json:"AddedBy,omitempty"`
|
AddedBy string `json:"AddedBy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature {
|
func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature {
|
||||||
versionStr := dbFeatureVersion.Version.String()
|
version := dbFeatureVersion.Version
|
||||||
if versionStr == types.MaxVersion.String() {
|
if version == versionfmt.MaxVersion {
|
||||||
versionStr = "None"
|
version = "None"
|
||||||
}
|
}
|
||||||
|
|
||||||
return Feature{
|
return Feature{
|
||||||
Name: dbFeatureVersion.Feature.Name,
|
Name: dbFeatureVersion.Feature.Name,
|
||||||
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
|
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
|
||||||
Version: versionStr,
|
VersionFormat: dbFeatureVersion.Feature.Namespace.VersionFormat,
|
||||||
|
Version: version,
|
||||||
AddedBy: dbFeatureVersion.AddedBy.Name,
|
AddedBy: dbFeatureVersion.AddedBy.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Feature) DatabaseModel() (database.FeatureVersion, error) {
|
func (f Feature) DatabaseModel() (fv database.FeatureVersion, err error) {
|
||||||
var version types.Version
|
var version string
|
||||||
if f.Version == "None" {
|
if f.Version == "None" {
|
||||||
version = types.MaxVersion
|
version = versionfmt.MaxVersion
|
||||||
} else {
|
} else {
|
||||||
var err error
|
err = versionfmt.Valid(f.VersionFormat, f.Version)
|
||||||
version, err = types.NewVersion(f.Version)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return database.FeatureVersion{}, err
|
return
|
||||||
}
|
}
|
||||||
|
version = f.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
return database.FeatureVersion{
|
fv = database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
Namespace: database.Namespace{Name: f.NamespaceName},
|
Namespace: database.Namespace{
|
||||||
|
Name: f.NamespaceName,
|
||||||
|
VersionFormat: f.VersionFormat,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Version: version,
|
Version: version,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
@ -179,7 +179,10 @@ func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params,
|
|||||||
}
|
}
|
||||||
var namespaces []Namespace
|
var namespaces []Namespace
|
||||||
for _, dbNamespace := range dbNamespaces {
|
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})
|
writeResponse(w, r, http.StatusOK, NamespaceEnvelope{Namespaces: &namespaces})
|
||||||
|
@ -41,6 +41,7 @@ type Namespace struct {
|
|||||||
Model
|
Model
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
|
VersionFormat string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Feature struct {
|
type Feature struct {
|
||||||
@ -54,7 +55,7 @@ type FeatureVersion struct {
|
|||||||
Model
|
Model
|
||||||
|
|
||||||
Feature Feature
|
Feature Feature
|
||||||
Version types.Version
|
Version string
|
||||||
AffectedBy []Vulnerability
|
AffectedBy []Vulnerability
|
||||||
|
|
||||||
// For output purposes. Only make sense when the feature version is in the context of an image.
|
// 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
|
// For output purposes. Only make sense when the vulnerability
|
||||||
// is already about a specific Feature/FeatureVersion.
|
// is already about a specific Feature/FeatureVersion.
|
||||||
FixedBy types.Version `json:",omitempty"`
|
FixedBy string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetadataMap map[string]interface{}
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/database"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
|
|
||||||
|
// dpkg versioning is used to parse test packages.
|
||||||
|
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -46,7 +49,10 @@ func TestRaceAffects(t *testing.T) {
|
|||||||
|
|
||||||
// Insert the Feature on which we'll work.
|
// Insert the Feature on which we'll work.
|
||||||
feature := database.Feature{
|
feature := database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestRaceAffectsFeatureNamespace1"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestRaceAffectsFeatureNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestRaceAffecturesFeature1",
|
Name: "TestRaceAffecturesFeature1",
|
||||||
}
|
}
|
||||||
_, err = datastore.insertFeature(feature)
|
_, err = datastore.insertFeature(feature)
|
||||||
@ -66,7 +72,7 @@ func TestRaceAffects(t *testing.T) {
|
|||||||
|
|
||||||
featureVersions[i] = database.FeatureVersion{
|
featureVersions[i] = database.FeatureVersion{
|
||||||
Feature: feature,
|
Feature: feature,
|
||||||
Version: types.NewVersionUnsafe(strconv.Itoa(version)),
|
Version: strconv.Itoa(version),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +92,7 @@ func TestRaceAffects(t *testing.T) {
|
|||||||
FixedIn: []database.FeatureVersion{
|
FixedIn: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: feature,
|
Feature: feature,
|
||||||
Version: types.NewVersionUnsafe(strconv.Itoa(version)),
|
Version: strconv.Itoa(version),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Severity: types.Unknown,
|
Severity: types.Unknown,
|
||||||
@ -126,7 +132,7 @@ func TestRaceAffects(t *testing.T) {
|
|||||||
var expectedAffectedNames []string
|
var expectedAffectedNames []string
|
||||||
|
|
||||||
for _, featureVersion := range featureVersions {
|
for _, featureVersion := range featureVersions {
|
||||||
featureVersionVersion, _ := strconv.Atoi(featureVersion.Version.String())
|
featureVersionVersion, _ := strconv.Atoi(featureVersion.Version)
|
||||||
|
|
||||||
// Get actual affects.
|
// Get actual affects.
|
||||||
rows, err := datastore.Query(searchComplexTestFeatureVersionAffects,
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -16,11 +16,13 @@ package pgsql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (pgSQL *pgSQL) insertFeature(feature database.Feature) (int, error) {
|
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
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion) (id int, err error) {
|
func (pgSQL *pgSQL) insertFeatureVersion(fv database.FeatureVersion) (id int, err error) {
|
||||||
if featureVersion.Version.String() == "" {
|
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")
|
return 0, cerrors.NewBadRequestError("could not find/insert invalid FeatureVersion")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do cache lookup.
|
// 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 {
|
if pgSQL.cache != nil {
|
||||||
promCacheQueriesTotal.WithLabelValues("featureversion").Inc()
|
promCacheQueriesTotal.WithLabelValues("featureversion").Inc()
|
||||||
id, found := pgSQL.cache.Get(cacheIndex)
|
id, found := pgSQL.cache.Get(cacheIndex)
|
||||||
@ -82,30 +86,29 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
|||||||
|
|
||||||
// Find or create Feature first.
|
// Find or create Feature first.
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
featureID, err := pgSQL.insertFeature(featureVersion.Feature)
|
featureID, err := pgSQL.insertFeature(fv.Feature)
|
||||||
observeQueryTime("insertFeatureVersion", "insertFeature", t)
|
observeQueryTime("insertFeatureVersion", "insertFeature", t)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
featureVersion.Feature.ID = featureID
|
fv.Feature.ID = featureID
|
||||||
|
|
||||||
// Try to find the FeatureVersion.
|
// Try to find the FeatureVersion.
|
||||||
//
|
//
|
||||||
// In a populated database, the likelihood of the FeatureVersion already being there is high.
|
// 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.
|
// If we can find it here, we then avoid using a transaction and locking the database.
|
||||||
err = pgSQL.QueryRow(searchFeatureVersion, featureID, &featureVersion.Version).
|
err = pgSQL.QueryRow(searchFeatureVersion, featureID, fv.Version).Scan(&fv.ID)
|
||||||
Scan(&featureVersion.ID)
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return 0, handleError("searchFeatureVersion", err)
|
return 0, handleError("searchFeatureVersion", err)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if pgSQL.cache != 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.
|
// Begin transaction.
|
||||||
@ -132,8 +135,7 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
|||||||
var created bool
|
var created bool
|
||||||
|
|
||||||
t = time.Now()
|
t = time.Now()
|
||||||
err = tx.QueryRow(soiFeatureVersion, featureID, &featureVersion.Version).
|
err = tx.QueryRow(soiFeatureVersion, featureID, fv.Version).Scan(&created, &fv.ID)
|
||||||
Scan(&created, &featureVersion.ID)
|
|
||||||
observeQueryTime("insertFeatureVersion", "soiFeatureVersion", t)
|
observeQueryTime("insertFeatureVersion", "soiFeatureVersion", t)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -147,16 +149,16 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
|||||||
tx.Commit()
|
tx.Commit()
|
||||||
|
|
||||||
if pgSQL.cache != 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link the new FeatureVersion with every vulnerabilities that affect it, by inserting in
|
// Link the new FeatureVersion with every vulnerabilities that affect it, by inserting in
|
||||||
// Vulnerability_Affects_FeatureVersion.
|
// Vulnerability_Affects_FeatureVersion.
|
||||||
t = time.Now()
|
t = time.Now()
|
||||||
err = linkFeatureVersionToVulnerabilities(tx, featureVersion)
|
err = linkFeatureVersionToVulnerabilities(tx, fv)
|
||||||
observeQueryTime("insertFeatureVersion", "linkFeatureVersionToVulnerabilities", t)
|
observeQueryTime("insertFeatureVersion", "linkFeatureVersionToVulnerabilities", t)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -171,10 +173,10 @@ func (pgSQL *pgSQL) insertFeatureVersion(featureVersion database.FeatureVersion)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pgSQL.cache != 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Quentin-M): Batch me
|
// TODO(Quentin-M): Batch me
|
||||||
@ -195,7 +197,7 @@ func (pgSQL *pgSQL) insertFeatureVersions(featureVersions []database.FeatureVers
|
|||||||
type vulnerabilityAffectsFeatureVersion struct {
|
type vulnerabilityAffectsFeatureVersion struct {
|
||||||
vulnerabilityID int
|
vulnerabilityID int
|
||||||
fixedInID int
|
fixedInID int
|
||||||
fixedInVersion types.Version
|
fixedInVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion database.FeatureVersion) error {
|
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)
|
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
|
// The version of the FeatureVersion we are inserting is lower than the fixed version on this
|
||||||
// Vulnerability, thus, this FeatureVersion is affected by it.
|
// Vulnerability, thus, this FeatureVersion is affected by it.
|
||||||
affects = append(affects, affect)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"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) {
|
func TestInsertFeature(t *testing.T) {
|
||||||
@ -45,7 +47,10 @@ func TestInsertFeature(t *testing.T) {
|
|||||||
|
|
||||||
// Insert Feature and ensure we can find it.
|
// Insert Feature and ensure we can find it.
|
||||||
feature := database.Feature{
|
feature := database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace1"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertFeatureNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertFeature1",
|
Name: "TestInsertFeature1",
|
||||||
}
|
}
|
||||||
id1, err := datastore.insertFeature(feature)
|
id1, err := datastore.insertFeature(feature)
|
||||||
@ -58,28 +63,34 @@ func TestInsertFeature(t *testing.T) {
|
|||||||
for _, invalidFeatureVersion := range []database.FeatureVersion{
|
for _, invalidFeatureVersion := range []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{},
|
Feature: database.Feature{},
|
||||||
Version: types.NewVersionUnsafe("1.0"),
|
Version: "1.0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{},
|
Namespace: database.Namespace{},
|
||||||
Name: "TestInsertFeature2",
|
Name: "TestInsertFeature2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("1.0"),
|
Version: "1.0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace2"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertFeatureNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertFeature2",
|
Name: "TestInsertFeature2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe(""),
|
Version: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace2"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertFeatureNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertFeature2",
|
Name: "TestInsertFeature2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("bad version"),
|
Version: "bad version",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
id3, err := datastore.insertFeatureVersion(invalidFeatureVersion)
|
id3, err := datastore.insertFeatureVersion(invalidFeatureVersion)
|
||||||
@ -90,10 +101,13 @@ func TestInsertFeature(t *testing.T) {
|
|||||||
// Insert FeatureVersion and ensure we can find it.
|
// Insert FeatureVersion and ensure we can find it.
|
||||||
featureVersion := database.FeatureVersion{
|
featureVersion := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertFeatureNamespace1"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertFeatureNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertFeature1",
|
Name: "TestInsertFeature1",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("2:3.0-imba"),
|
Version: "2:3.0-imba",
|
||||||
}
|
}
|
||||||
id4, err := datastore.insertFeatureVersion(featureVersion)
|
id4, err := datastore.insertFeatureVersion(featureVersion)
|
||||||
assert.Nil(t, err)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +16,14 @@ package pgsql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/guregu/null/zero"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/guregu/null/zero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities bool) (database.Layer, error) {
|
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())
|
defer observeQueryTime("FindLayer", subquery, time.Now())
|
||||||
|
|
||||||
// Find the layer
|
// Find the layer
|
||||||
var layer database.Layer
|
var (
|
||||||
var parentID zero.Int
|
layer database.Layer
|
||||||
var parentName zero.String
|
parentID zero.Int
|
||||||
var namespaceID zero.Int
|
parentName zero.String
|
||||||
var namespaceName sql.NullString
|
nsID zero.Int
|
||||||
|
nsName sql.NullString
|
||||||
|
nsVersionFormat sql.NullString
|
||||||
|
)
|
||||||
|
|
||||||
t := time.Now()
|
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)
|
observeQueryTime("FindLayer", "searchLayer", t)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,10 +68,11 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
|
|||||||
Name: parentName.String,
|
Name: parentName.String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !namespaceID.IsZero() {
|
if !nsID.IsZero() {
|
||||||
layer.Namespace = &database.Namespace{
|
layer.Namespace = &database.Namespace{
|
||||||
Model: database.Model{ID: int(namespaceID.Int64)},
|
Model: database.Model{ID: int(nsID.Int64)},
|
||||||
Name: namespaceName.String,
|
Name: nsName.String,
|
||||||
|
VersionFormat: nsVersionFormat.String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +140,20 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
|
|||||||
var modification string
|
var modification string
|
||||||
mapFeatureVersions := make(map[int]database.FeatureVersion)
|
mapFeatureVersions := make(map[int]database.FeatureVersion)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var featureVersion database.FeatureVersion
|
var fv database.FeatureVersion
|
||||||
|
err = rows.Scan(
|
||||||
err = rows.Scan(&featureVersion.ID, &modification, &featureVersion.Feature.Namespace.ID,
|
&fv.ID,
|
||||||
&featureVersion.Feature.Namespace.Name, &featureVersion.Feature.ID,
|
&modification,
|
||||||
&featureVersion.Feature.Name, &featureVersion.ID, &featureVersion.Version,
|
&fv.Feature.Namespace.ID,
|
||||||
&featureVersion.AddedBy.ID, &featureVersion.AddedBy.Name)
|
&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 {
|
if err != nil {
|
||||||
return featureVersions, handleError("searchLayerFeatureVersion.Scan()", err)
|
return featureVersions, handleError("searchLayerFeatureVersion.Scan()", err)
|
||||||
}
|
}
|
||||||
@ -138,9 +161,9 @@ func getLayerFeatureVersions(tx *sql.Tx, layerID int) ([]database.FeatureVersion
|
|||||||
// Do transitive closure.
|
// Do transitive closure.
|
||||||
switch modification {
|
switch modification {
|
||||||
case "add":
|
case "add":
|
||||||
mapFeatureVersions[featureVersion.ID] = featureVersion
|
mapFeatureVersions[fv.ID] = fv
|
||||||
case "del":
|
case "del":
|
||||||
delete(mapFeatureVersions, featureVersion.ID)
|
delete(mapFeatureVersions, fv.ID)
|
||||||
default:
|
default:
|
||||||
log.Warningf("unknown Layer_diff_FeatureVersion's modification: %s", modification)
|
log.Warningf("unknown Layer_diff_FeatureVersion's modification: %s", modification)
|
||||||
return featureVersions, database.ErrInconsistent
|
return featureVersions, database.ErrInconsistent
|
||||||
@ -182,9 +205,18 @@ func loadAffectedBy(tx *sql.Tx, featureVersions []database.FeatureVersion) error
|
|||||||
var featureversionID int
|
var featureversionID int
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var vulnerability database.Vulnerability
|
var vulnerability database.Vulnerability
|
||||||
err := rows.Scan(&featureversionID, &vulnerability.ID, &vulnerability.Name,
|
err := rows.Scan(
|
||||||
&vulnerability.Description, &vulnerability.Link, &vulnerability.Severity,
|
&featureversionID,
|
||||||
&vulnerability.Metadata, &vulnerability.Namespace.Name, &vulnerability.FixedBy)
|
&vulnerability.ID,
|
||||||
|
&vulnerability.Name,
|
||||||
|
&vulnerability.Description,
|
||||||
|
&vulnerability.Link,
|
||||||
|
&vulnerability.Severity,
|
||||||
|
&vulnerability.Metadata,
|
||||||
|
&vulnerability.Namespace.Name,
|
||||||
|
&vulnerability.Namespace.VersionFormat,
|
||||||
|
&vulnerability.FixedBy,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handleError("searchFeatureVersionVulnerability.Scan()", err)
|
return handleError("searchFeatureVersionVulnerability.Scan()", err)
|
||||||
}
|
}
|
||||||
@ -374,9 +406,9 @@ func createNV(features []database.FeatureVersion) (map[string]*database.FeatureV
|
|||||||
sliceNV := make([]string, 0, len(features))
|
sliceNV := make([]string, 0, len(features))
|
||||||
|
|
||||||
for i := 0; i < len(features); i++ {
|
for i := 0; i < len(features); i++ {
|
||||||
featureVersion := &features[i]
|
fv := &features[i]
|
||||||
nv := featureVersion.Feature.Namespace.Name + ":" + featureVersion.Feature.Name + ":" + featureVersion.Version.String()
|
nv := strings.Join([]string{fv.Feature.Namespace.Name, fv.Feature.Name, fv.Version}, ":")
|
||||||
mapNV[nv] = featureVersion
|
mapNV[nv] = fv
|
||||||
sliceNV = append(sliceNV, nv)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/database"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"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) {
|
func TestFindLayer(t *testing.T) {
|
||||||
@ -67,9 +70,9 @@ func TestFindLayer(t *testing.T) {
|
|||||||
|
|
||||||
switch featureVersion.Feature.Name {
|
switch featureVersion.Feature.Name {
|
||||||
case "wechat":
|
case "wechat":
|
||||||
assert.Equal(t, types.NewVersionUnsafe("0.5"), featureVersion.Version)
|
assert.Equal(t, "0.5", featureVersion.Version)
|
||||||
case "openssl":
|
case "openssl":
|
||||||
assert.Equal(t, types.NewVersionUnsafe("1.0"), featureVersion.Version)
|
assert.Equal(t, "1.0", featureVersion.Version)
|
||||||
default:
|
default:
|
||||||
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
||||||
}
|
}
|
||||||
@ -83,9 +86,9 @@ func TestFindLayer(t *testing.T) {
|
|||||||
|
|
||||||
switch featureVersion.Feature.Name {
|
switch featureVersion.Feature.Name {
|
||||||
case "wechat":
|
case "wechat":
|
||||||
assert.Equal(t, types.NewVersionUnsafe("0.5"), featureVersion.Version)
|
assert.Equal(t, "0.5", featureVersion.Version)
|
||||||
case "openssl":
|
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) {
|
if assert.Len(t, featureVersion.AffectedBy, 1) {
|
||||||
assert.Equal(t, "debian:7", featureVersion.AffectedBy[0].Namespace.Name)
|
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, 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, "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, "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:
|
default:
|
||||||
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
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) {
|
func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
||||||
f1 := database.FeatureVersion{
|
f1 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature1",
|
Name: "TestInsertLayerFeature1",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("1.0"),
|
Version: "1.0",
|
||||||
}
|
}
|
||||||
f2 := database.FeatureVersion{
|
f2 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature2",
|
Name: "TestInsertLayerFeature2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.34"),
|
Version: "0.34",
|
||||||
}
|
}
|
||||||
f3 := database.FeatureVersion{
|
f3 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace2"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature3",
|
Name: "TestInsertLayerFeature3",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.56"),
|
Version: "0.56",
|
||||||
}
|
}
|
||||||
f4 := database.FeatureVersion{
|
f4 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace3",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature2",
|
Name: "TestInsertLayerFeature2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.34"),
|
Version: "0.34",
|
||||||
}
|
}
|
||||||
f5 := database.FeatureVersion{
|
f5 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace3",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature3",
|
Name: "TestInsertLayerFeature3",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.56"),
|
Version: "0.56",
|
||||||
}
|
}
|
||||||
f6 := database.FeatureVersion{
|
f6 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace3",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature4",
|
Name: "TestInsertLayerFeature4",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.666"),
|
Version: "0.666",
|
||||||
}
|
}
|
||||||
|
|
||||||
layers := []database.Layer{
|
layers := []database.Layer{
|
||||||
@ -187,13 +208,19 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
|||||||
{
|
{
|
||||||
Name: "TestInsertLayer2",
|
Name: "TestInsertLayer2",
|
||||||
Parent: &database.Layer{Name: "TestInsertLayer1"},
|
Parent: &database.Layer{Name: "TestInsertLayer1"},
|
||||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace1"},
|
Namespace: &database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// This layer changes the namespace and adds Features.
|
// This layer changes the namespace and adds Features.
|
||||||
{
|
{
|
||||||
Name: "TestInsertLayer3",
|
Name: "TestInsertLayer3",
|
||||||
Parent: &database.Layer{Name: "TestInsertLayer2"},
|
Parent: &database.Layer{Name: "TestInsertLayer2"},
|
||||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace2"},
|
Namespace: &database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Features: []database.FeatureVersion{f1, f2, f3},
|
Features: []database.FeatureVersion{f1, f2, f3},
|
||||||
},
|
},
|
||||||
// This layer covers the case where the last layer doesn't provide any new Feature.
|
// This layer covers the case where the last layer doesn't provide any new Feature.
|
||||||
@ -208,7 +235,10 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
|||||||
{
|
{
|
||||||
Name: "TestInsertLayer4b",
|
Name: "TestInsertLayer4b",
|
||||||
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
||||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespace3"},
|
Namespace: &database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace3",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Features: []database.FeatureVersion{
|
Features: []database.FeatureVersion{
|
||||||
// Deletes TestInsertLayerFeature1.
|
// Deletes TestInsertLayerFeature1.
|
||||||
// Keep TestInsertLayerFeature2 (old Namespace should be kept):
|
// Keep TestInsertLayerFeature2 (old Namespace should be kept):
|
||||||
@ -263,17 +293,23 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
|||||||
func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
|
func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
|
||||||
f7 := database.FeatureVersion{
|
f7 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "TestInsertLayerNamespace3"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespace3",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Name: "TestInsertLayerFeature7",
|
Name: "TestInsertLayerFeature7",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.01"),
|
Version: "0.01",
|
||||||
}
|
}
|
||||||
|
|
||||||
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false)
|
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false)
|
||||||
l3u := database.Layer{
|
l3u := database.Layer{
|
||||||
Name: l3.Name,
|
Name: l3.Name,
|
||||||
Parent: l3.Parent,
|
Parent: l3.Parent,
|
||||||
Namespace: &database.Namespace{Name: "TestInsertLayerNamespaceUpdated1"},
|
Namespace: &database.Namespace{
|
||||||
|
Name: "TestInsertLayerNamespaceUpdated1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Features: []database.FeatureVersion{f7},
|
Features: []database.FeatureVersion{f7},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,5 +383,5 @@ func testInsertLayerDelete(t *testing.T, datastore database.Datastore) {
|
|||||||
func cmpFV(a, b database.FeatureVersion) bool {
|
func cmpFV(a, b database.FeatureVersion) bool {
|
||||||
return a.Feature.Name == b.Feature.Name &&
|
return a.Feature.Name == b.Feature.Name &&
|
||||||
a.Feature.Namespace.Name == b.Feature.Namespace.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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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())
|
defer observeQueryTime("insertNamespace", "all", time.Now())
|
||||||
|
|
||||||
var id int
|
var id int
|
||||||
err := pgSQL.QueryRow(soiNamespace, namespace.Name).Scan(&id)
|
err := pgSQL.QueryRow(soiNamespace, namespace.Name, namespace.VersionFormat).Scan(&id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handleError("soiNamespace", err)
|
return 0, handleError("soiNamespace", err)
|
||||||
}
|
}
|
||||||
@ -58,14 +58,14 @@ func (pgSQL *pgSQL) ListNamespaces() (namespaces []database.Namespace, err error
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
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 {
|
if err != nil {
|
||||||
return namespaces, handleError("listNamespace.Scan()", err)
|
return namespaces, handleError("listNamespace.Scan()", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
namespaces = append(namespaces, namespace)
|
namespaces = append(namespaces, ns)
|
||||||
}
|
}
|
||||||
if err = rows.Err(); err != nil {
|
if err = rows.Err(); err != nil {
|
||||||
return namespaces, handleError("listNamespace.Rows()", err)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"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) {
|
func TestInsertNamespace(t *testing.T) {
|
||||||
@ -37,9 +40,15 @@ func TestInsertNamespace(t *testing.T) {
|
|||||||
assert.Zero(t, id0)
|
assert.Zero(t, id0)
|
||||||
|
|
||||||
// Insert Namespace and ensure we can find it.
|
// 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)
|
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.Nil(t, err)
|
||||||
assert.Equal(t, id1, id2)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"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) {
|
func TestNotification(t *testing.T) {
|
||||||
@ -40,12 +44,18 @@ func TestNotification(t *testing.T) {
|
|||||||
// Create some data.
|
// Create some data.
|
||||||
f1 := database.Feature{
|
f1 := database.Feature{
|
||||||
Name: "TestNotificationFeature1",
|
Name: "TestNotificationFeature1",
|
||||||
Namespace: database.Namespace{Name: "TestNotificationNamespace1"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestNotificationNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f2 := database.Feature{
|
f2 := database.Feature{
|
||||||
Name: "TestNotificationFeature2",
|
Name: "TestNotificationFeature2",
|
||||||
Namespace: database.Namespace{Name: "TestNotificationNamespace1"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "TestNotificationNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
l1 := database.Layer{
|
l1 := database.Layer{
|
||||||
@ -53,7 +63,7 @@ func TestNotification(t *testing.T) {
|
|||||||
Features: []database.FeatureVersion{
|
Features: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: f1,
|
Feature: f1,
|
||||||
Version: types.NewVersionUnsafe("0.1"),
|
Version: "0.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -63,7 +73,7 @@ func TestNotification(t *testing.T) {
|
|||||||
Features: []database.FeatureVersion{
|
Features: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: f1,
|
Feature: f1,
|
||||||
Version: types.NewVersionUnsafe("0.2"),
|
Version: "0.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -73,7 +83,7 @@ func TestNotification(t *testing.T) {
|
|||||||
Features: []database.FeatureVersion{
|
Features: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: f1,
|
Feature: f1,
|
||||||
Version: types.NewVersionUnsafe("0.3"),
|
Version: "0.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -83,7 +93,7 @@ func TestNotification(t *testing.T) {
|
|||||||
Features: []database.FeatureVersion{
|
Features: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: f2,
|
Feature: f2,
|
||||||
Version: types.NewVersionUnsafe("0.1"),
|
Version: "0.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -105,7 +115,7 @@ func TestNotification(t *testing.T) {
|
|||||||
FixedIn: []database.FeatureVersion{
|
FixedIn: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: f1,
|
Feature: f1,
|
||||||
Version: types.NewVersionUnsafe("1.0"),
|
Version: "1.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -165,11 +175,11 @@ func TestNotification(t *testing.T) {
|
|||||||
v1b.FixedIn = []database.FeatureVersion{
|
v1b.FixedIn = []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: f1,
|
Feature: f1,
|
||||||
Version: types.MinVersion,
|
Version: versionfmt.MinVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: f2,
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -29,8 +29,8 @@ const (
|
|||||||
// namespace.go
|
// namespace.go
|
||||||
soiNamespace = `
|
soiNamespace = `
|
||||||
WITH new_namespace AS (
|
WITH new_namespace AS (
|
||||||
INSERT INTO Namespace(name)
|
INSERT INTO Namespace(name, version_format)
|
||||||
SELECT CAST($1 AS VARCHAR)
|
SELECT CAST($1 AS VARCHAR), CAST($2 AS VARCHAR)
|
||||||
WHERE NOT EXISTS (SELECT name FROM Namespace WHERE name = $1)
|
WHERE NOT EXISTS (SELECT name FROM Namespace WHERE name = $1)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
)
|
)
|
||||||
@ -39,7 +39,7 @@ const (
|
|||||||
SELECT id FROM new_namespace`
|
SELECT id FROM new_namespace`
|
||||||
|
|
||||||
searchNamespace = `SELECT id FROM Namespace WHERE name = $1`
|
searchNamespace = `SELECT id FROM Namespace WHERE name = $1`
|
||||||
listNamespace = `SELECT id, name FROM Namespace`
|
listNamespace = `SELECT id, name, version_format FROM Namespace`
|
||||||
|
|
||||||
// feature.go
|
// feature.go
|
||||||
soiFeature = `
|
soiFeature = `
|
||||||
@ -72,12 +72,12 @@ const (
|
|||||||
WHERE feature_id = $1`
|
WHERE feature_id = $1`
|
||||||
|
|
||||||
insertVulnerabilityAffectsFeatureVersion = `
|
insertVulnerabilityAffectsFeatureVersion = `
|
||||||
INSERT INTO Vulnerability_Affects_FeatureVersion(vulnerability_id,
|
INSERT INTO Vulnerability_Affects_FeatureVersion(vulnerability_id, featureversion_id, fixedin_id)
|
||||||
featureversion_id, fixedin_id) VALUES($1, $2, $3)`
|
VALUES($1, $2, $3)`
|
||||||
|
|
||||||
// layer.go
|
// layer.go
|
||||||
searchLayer = `
|
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
|
FROM Layer l
|
||||||
LEFT JOIN Layer p ON l.parent_id = p.id
|
LEFT JOIN Layer p ON l.parent_id = p.id
|
||||||
LEFT JOIN Namespace n ON l.namespace_id = n.id
|
LEFT JOIN Namespace n ON l.namespace_id = n.id
|
||||||
@ -93,7 +93,7 @@ const (
|
|||||||
FROM Layer l, layer_tree lt
|
FROM Layer l, layer_tree lt
|
||||||
WHERE l.id = lt.parent_id
|
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
|
FROM Layer_diff_FeatureVersion ldf
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT row_number() over (ORDER BY depth DESC), id, name FROM layer_tree
|
SELECT row_number() over (ORDER BY depth DESC), id, name FROM layer_tree
|
||||||
@ -103,7 +103,7 @@ const (
|
|||||||
|
|
||||||
searchFeatureVersionVulnerability = `
|
searchFeatureVersionVulnerability = `
|
||||||
SELECT vafv.featureversion_id, v.id, v.name, v.description, v.link, v.severity, v.metadata,
|
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,
|
FROM Vulnerability_Affects_FeatureVersion vafv, Vulnerability v,
|
||||||
Namespace vn, Vulnerability_FixedIn_Feature vfif
|
Namespace vn, Vulnerability_FixedIn_Feature vfif
|
||||||
WHERE vafv.featureversion_id = ANY($1::integer[])
|
WHERE vafv.featureversion_id = ANY($1::integer[])
|
||||||
@ -140,7 +140,7 @@ const (
|
|||||||
|
|
||||||
// vulnerability.go
|
// vulnerability.go
|
||||||
searchVulnerabilityBase = `
|
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`
|
FROM Vulnerability v JOIN Namespace n ON v.namespace_id = n.id`
|
||||||
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
|
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
|
||||||
searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND v.name = $2 AND v.deleted_at IS NULL`
|
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
|
-- See the License for the specific language governing permissions and
|
||||||
-- limitations under the License.
|
-- limitations under the License.
|
||||||
|
|
||||||
INSERT INTO namespace (id, name) VALUES
|
INSERT INTO namespace (id, name, version_format) VALUES
|
||||||
(1, 'debian:7'),
|
(1, 'debian:7', 'dpkg'),
|
||||||
(2, 'debian:8');
|
(2, 'debian:8', 'dpkg');
|
||||||
|
|
||||||
INSERT INTO feature (id, namespace_id, name) VALUES
|
INSERT INTO feature (id, namespace_id, name) VALUES
|
||||||
(1, 1, 'wechat'),
|
(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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -22,9 +22,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/guregu/null/zero"
|
"github.com/guregu/null/zero"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -60,6 +60,7 @@ func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID
|
|||||||
&vulnerability.Name,
|
&vulnerability.Name,
|
||||||
&vulnerability.Namespace.ID,
|
&vulnerability.Namespace.ID,
|
||||||
&vulnerability.Namespace.Name,
|
&vulnerability.Namespace.Name,
|
||||||
|
&vulnerability.Namespace.VersionFormat,
|
||||||
&vulnerability.Description,
|
&vulnerability.Description,
|
||||||
&vulnerability.Link,
|
&vulnerability.Link,
|
||||||
&vulnerability.Severity,
|
&vulnerability.Severity,
|
||||||
@ -117,6 +118,7 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.
|
|||||||
&vulnerability.Name,
|
&vulnerability.Name,
|
||||||
&vulnerability.Namespace.ID,
|
&vulnerability.Namespace.ID,
|
||||||
&vulnerability.Namespace.Name,
|
&vulnerability.Namespace.Name,
|
||||||
|
&vulnerability.Namespace.VersionFormat,
|
||||||
&vulnerability.Description,
|
&vulnerability.Description,
|
||||||
&vulnerability.Link,
|
&vulnerability.Link,
|
||||||
&vulnerability.Severity,
|
&vulnerability.Severity,
|
||||||
@ -163,7 +165,7 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.
|
|||||||
Namespace: vulnerability.Namespace,
|
Namespace: vulnerability.Namespace,
|
||||||
Name: featureVersionFeatureName.String,
|
Name: featureVersionFeatureName.String,
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe(featureVersionVersion.String),
|
Version: featureVersionVersion.String,
|
||||||
}
|
}
|
||||||
vulnerability.FixedIn = append(vulnerability.FixedIn, featureVersion)
|
vulnerability.FixedIn = append(vulnerability.FixedIn, featureVersion)
|
||||||
}
|
}
|
||||||
@ -182,7 +184,6 @@ func (pgSQL *pgSQL) InsertVulnerabilities(vulnerabilities []database.Vulnerabili
|
|||||||
for _, vulnerability := range vulnerabilities {
|
for _, vulnerability := range vulnerabilities {
|
||||||
err := pgSQL.insertVulnerability(vulnerability, false, generateNotifications)
|
err := pgSQL.insertVulnerability(vulnerability, false, generateNotifications)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%#v\n", vulnerability)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,7 +275,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
|
|||||||
// for diffing existing vulnerabilities.
|
// for diffing existing vulnerabilities.
|
||||||
var fixedIn []database.FeatureVersion
|
var fixedIn []database.FeatureVersion
|
||||||
for _, fv := range vulnerability.FixedIn {
|
for _, fv := range vulnerability.FixedIn {
|
||||||
if fv.Version != types.MinVersion {
|
if fv.Version != versionfmt.MinVersion {
|
||||||
fixedIn = append(fixedIn, fv)
|
fixedIn = append(fixedIn, fv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,7 +351,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F
|
|||||||
different := false
|
different := false
|
||||||
|
|
||||||
for _, name := range addedNames {
|
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,
|
// MinVersion only makes sense when a Feature is already fixed in some version,
|
||||||
// in which case we would be in the "inBothNames".
|
// in which case we would be in the "inBothNames".
|
||||||
continue
|
continue
|
||||||
@ -363,7 +364,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F
|
|||||||
for _, name := range inBothNames {
|
for _, name := range inBothNames {
|
||||||
fv := diffMap[name]
|
fv := diffMap[name]
|
||||||
|
|
||||||
if fv.Version == types.MinVersion {
|
if fv.Version == versionfmt.MinVersion {
|
||||||
// MinVersion means that the Feature doesn't affect the Vulnerability anymore.
|
// MinVersion means that the Feature doesn't affect the Vulnerability anymore.
|
||||||
delete(currentMap, name)
|
delete(currentMap, name)
|
||||||
different = true
|
different = true
|
||||||
@ -453,7 +454,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert Vulnerability_Affects_FeatureVersion.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -462,7 +463,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner
|
|||||||
return nil
|
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.
|
// Find every FeatureVersions of the Feature that the vulnerability affects.
|
||||||
// TODO(Quentin-M): LIMIT
|
// TODO(Quentin-M): LIMIT
|
||||||
rows, err := tx.Query(searchFeatureVersionByFeature, featureID)
|
rows, err := tx.Query(searchFeatureVersionByFeature, featureID)
|
||||||
@ -480,7 +481,11 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
|
|||||||
return handleError("searchFeatureVersionByFeature.Scan()", err)
|
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,
|
// The version of the FeatureVersion is lower than the fixed version of this vulnerability,
|
||||||
// thus, this FeatureVersion is affected by it.
|
// thus, this FeatureVersion is affected by it.
|
||||||
affecteds = append(affecteds, affected)
|
affecteds = append(affecteds, affected)
|
||||||
@ -494,8 +499,7 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
|
|||||||
// Insert into Vulnerability_Affects_FeatureVersion.
|
// Insert into Vulnerability_Affects_FeatureVersion.
|
||||||
for _, affected := range affecteds {
|
for _, affected := range affecteds {
|
||||||
// TODO(Quentin-M): Batch me.
|
// TODO(Quentin-M): Batch me.
|
||||||
_, err := tx.Exec(insertVulnerabilityAffectsFeatureVersion, vulnerabilityID,
|
_, err := tx.Exec(insertVulnerabilityAffectsFeatureVersion, vulnerabilityID, affected.ID, fixedInID)
|
||||||
affected.ID, fixedInID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handleError("insertVulnerabilityAffectsFeatureVersion", err)
|
return handleError("insertVulnerabilityAffectsFeatureVersion", err)
|
||||||
}
|
}
|
||||||
@ -534,7 +538,7 @@ func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerability
|
|||||||
Name: vulnerabilityNamespace,
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"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) {
|
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",
|
Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0",
|
||||||
Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7",
|
Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7",
|
||||||
Severity: types.High,
|
Severity: types.High,
|
||||||
Namespace: database.Namespace{Name: "debian:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "debian:7",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
FixedIn: []database.FeatureVersion{
|
FixedIn: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "openssl"},
|
Feature: database.Feature{Name: "openssl"},
|
||||||
Version: types.NewVersionUnsafe("2.0"),
|
Version: "2.0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "libssl"},
|
Feature: database.Feature{Name: "libssl"},
|
||||||
Version: types.NewVersionUnsafe("1.9-abc"),
|
Version: "1.9-abc",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -65,7 +72,10 @@ func TestFindVulnerability(t *testing.T) {
|
|||||||
v2 := database.Vulnerability{
|
v2 := database.Vulnerability{
|
||||||
Name: "CVE-NOPE",
|
Name: "CVE-NOPE",
|
||||||
Description: "A vulnerability affecting nothing",
|
Description: "A vulnerability affecting nothing",
|
||||||
Namespace: database.Namespace{Name: "debian:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "debian:7",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
},
|
||||||
Severity: types.Unknown,
|
Severity: types.Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,58 +116,64 @@ func TestInsertVulnerability(t *testing.T) {
|
|||||||
defer datastore.Close()
|
defer datastore.Close()
|
||||||
|
|
||||||
// Create some data.
|
// Create some data.
|
||||||
n1 := database.Namespace{Name: "TestInsertVulnerabilityNamespace1"}
|
n1 := database.Namespace{
|
||||||
n2 := database.Namespace{Name: "TestInsertVulnerabilityNamespace2"}
|
Name: "TestInsertVulnerabilityNamespace1",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
}
|
||||||
|
n2 := database.Namespace{
|
||||||
|
Name: "TestInsertVulnerabilityNamespace2",
|
||||||
|
VersionFormat: "dpkg",
|
||||||
|
}
|
||||||
|
|
||||||
f1 := database.FeatureVersion{
|
f1 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion1",
|
Name: "TestInsertVulnerabilityFeatureVersion1",
|
||||||
Namespace: n1,
|
Namespace: n1,
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("1.0"),
|
Version: "1.0",
|
||||||
}
|
}
|
||||||
f2 := database.FeatureVersion{
|
f2 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion1",
|
Name: "TestInsertVulnerabilityFeatureVersion1",
|
||||||
Namespace: n2,
|
Namespace: n2,
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("1.0"),
|
Version: "1.0",
|
||||||
}
|
}
|
||||||
f3 := database.FeatureVersion{
|
f3 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion2",
|
Name: "TestInsertVulnerabilityFeatureVersion2",
|
||||||
},
|
},
|
||||||
Version: types.MaxVersion,
|
Version: versionfmt.MaxVersion,
|
||||||
}
|
}
|
||||||
f4 := database.FeatureVersion{
|
f4 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion2",
|
Name: "TestInsertVulnerabilityFeatureVersion2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("1.4"),
|
Version: "1.4",
|
||||||
}
|
}
|
||||||
f5 := database.FeatureVersion{
|
f5 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion3",
|
Name: "TestInsertVulnerabilityFeatureVersion3",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("1.5"),
|
Version: "1.5",
|
||||||
}
|
}
|
||||||
f6 := database.FeatureVersion{
|
f6 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion4",
|
Name: "TestInsertVulnerabilityFeatureVersion4",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.1"),
|
Version: "0.1",
|
||||||
}
|
}
|
||||||
f7 := database.FeatureVersion{
|
f7 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion5",
|
Name: "TestInsertVulnerabilityFeatureVersion5",
|
||||||
},
|
},
|
||||||
Version: types.MaxVersion,
|
Version: versionfmt.MaxVersion,
|
||||||
}
|
}
|
||||||
f8 := database.FeatureVersion{
|
f8 := database.FeatureVersion{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Name: "TestInsertVulnerabilityFeatureVersion5",
|
Name: "TestInsertVulnerabilityFeatureVersion5",
|
||||||
},
|
},
|
||||||
Version: types.MinVersion,
|
Version: versionfmt.MinVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert invalid vulnerabilities.
|
// 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/pkg/capnslog"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/updater"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
|
|
||||||
|
// dpkg versioning is used to parse Alpine Linux packages.
|
||||||
|
_ "github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -219,7 +223,7 @@ func parse33YAML(r io.Reader) (vulns []database.Vulnerability, err error) {
|
|||||||
for _, pack := range file.Packages {
|
for _, pack := range file.Packages {
|
||||||
pkg := pack.Pkg
|
pkg := pack.Pkg
|
||||||
for _, fix := range pkg.Fixes {
|
for _, fix := range pkg.Fixes {
|
||||||
version, err := types.NewVersion(pkg.Version)
|
err = versionfmt.Valid("dpkg", pkg.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("could not parse package version '%s': %s. skipping", pkg.Version, err.Error())
|
log.Warningf("could not parse package version '%s': %s. skipping", pkg.Version, err.Error())
|
||||||
continue
|
continue
|
||||||
@ -235,7 +239,7 @@ func parse33YAML(r io.Reader) (vulns []database.Vulnerability, err error) {
|
|||||||
Namespace: database.Namespace{Name: "alpine:" + file.Distro},
|
Namespace: database.Namespace{Name: "alpine:" + file.Distro},
|
||||||
Name: pkg.Name,
|
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 {
|
for _, pack := range file.Packages {
|
||||||
pkg := pack.Pkg
|
pkg := pack.Pkg
|
||||||
for versionStr, vulnStrs := range pkg.Fixes {
|
for version, vulnStrs := range pkg.Fixes {
|
||||||
version, err := types.NewVersion(versionStr)
|
err := versionfmt.Valid("dpkg", version)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 clair authors
|
// Copyright 2016 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -23,11 +23,16 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/updater"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"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 (
|
const (
|
||||||
@ -168,23 +173,24 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the version of the package the vulnerability affects.
|
// Determine the version of the package the vulnerability affects.
|
||||||
var version types.Version
|
var version string
|
||||||
var err error
|
var err error
|
||||||
if releaseNode.FixedVersion == "0" {
|
if releaseNode.FixedVersion == "0" {
|
||||||
// This means that the package is not affected by this vulnerability.
|
// This means that the package is not affected by this vulnerability.
|
||||||
version = types.MinVersion
|
version = versionfmt.MinVersion
|
||||||
} else if releaseNode.Status == "open" {
|
} else if releaseNode.Status == "open" {
|
||||||
// Open means that the package is currently vulnerable in the latest
|
// Open means that the package is currently vulnerable in the latest
|
||||||
// version of this Debian release.
|
// version of this Debian release.
|
||||||
version = types.MaxVersion
|
version = versionfmt.MaxVersion
|
||||||
} else if releaseNode.Status == "resolved" {
|
} else if releaseNode.Status == "resolved" {
|
||||||
// Resolved means that the vulnerability has been fixed in
|
// Resolved means that the vulnerability has been fixed in
|
||||||
// "fixed_version" (if affected).
|
// "fixed_version" (if affected).
|
||||||
version, err = types.NewVersion(releaseNode.FixedVersion)
|
err = versionfmt.Valid("dpkg", releaseNode.FixedVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("could not parse package version '%s': %s. skipping", releaseNode.FixedVersion, err.Error())
|
log.Warningf("could not parse package version '%s': %s. skipping", releaseNode.FixedVersion, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
version = releaseNode.FixedVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and add the feature version.
|
// 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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -44,7 +45,7 @@ func TestDebianParser(t *testing.T) {
|
|||||||
Namespace: database.Namespace{Name: "debian:8"},
|
Namespace: database.Namespace{Name: "debian:8"},
|
||||||
Name: "aptdaemon",
|
Name: "aptdaemon",
|
||||||
},
|
},
|
||||||
Version: types.MaxVersion,
|
Version: versionfmt.MaxVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
@ -52,7 +53,7 @@ func TestDebianParser(t *testing.T) {
|
|||||||
|
|
||||||
Name: "aptdaemon",
|
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"},
|
Namespace: database.Namespace{Name: "debian:8"},
|
||||||
Name: "aptdaemon",
|
Name: "aptdaemon",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.7.0"),
|
Version: "0.7.0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "debian:unstable"},
|
Namespace: database.Namespace{Name: "debian:unstable"},
|
||||||
Name: "aptdaemon",
|
Name: "aptdaemon",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.7.0"),
|
Version: "0.7.0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "debian:8"},
|
Namespace: database.Namespace{Name: "debian:8"},
|
||||||
Name: "asterisk",
|
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"},
|
Namespace: database.Namespace{Name: "debian:8"},
|
||||||
Name: "asterisk",
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -24,10 +24,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/updater"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
|
// rpm versioning is used to parse Oracle Linux packages.
|
||||||
|
_ "github.com/coreos/clair/ext/versionfmt/rpm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -98,7 +102,6 @@ func (f *OracleFetcher) FetchUpdate(datastore database.Datastore) (resp updater.
|
|||||||
firstELSA = firstOracle5ELSA
|
firstELSA = firstOracle5ELSA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fetch the update list.
|
// Fetch the update list.
|
||||||
r, err := http.Get(ovalURI)
|
r, err := http.Get(ovalURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -282,16 +285,20 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
|
|||||||
} else if strings.Contains(c.Comment, " is earlier than ") {
|
} else if strings.Contains(c.Comment, " is earlier than ") {
|
||||||
const prefixLen = len(" is earlier than ")
|
const prefixLen = len(" is earlier than ")
|
||||||
featureVersion.Feature.Name = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " 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 {
|
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.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
|
featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion
|
||||||
} else {
|
} else {
|
||||||
log.Warningf("could not determine a valid package from criterions: %v", criterions)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/database"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestOracleParser(t *testing.T) {
|
||||||
@ -43,24 +46,33 @@ func TestOracleParser(t *testing.T) {
|
|||||||
expectedFeatureVersions := []database.FeatureVersion{
|
expectedFeatureVersions := []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "oracle:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "oracle:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "xerces-c",
|
Name: "xerces-c",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
Version: "0:3.1.1-7.el7_1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "oracle:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "oracle:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "xerces-c-devel",
|
Name: "xerces-c-devel",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
Version: "0:3.1.1-7.el7_1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "oracle:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "oracle:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "xerces-c-doc",
|
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{
|
expectedFeatureVersions := []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "oracle:6"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "oracle:6",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "firefox",
|
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{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "oracle:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "oracle:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "firefox",
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -23,11 +23,16 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/updater"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"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 (
|
const (
|
||||||
@ -283,9 +288,13 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
|
|||||||
} else if strings.Contains(c.Comment, " is earlier than ") {
|
} else if strings.Contains(c.Comment, " is earlier than ") {
|
||||||
const prefixLen = len(" is earlier than ")
|
const prefixLen = len(" is earlier than ")
|
||||||
featureVersion.Feature.Name = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " 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 {
|
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
|
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
|
featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion
|
||||||
} else {
|
} else {
|
||||||
log.Warningf("could not determine a valid package from criterions: %v", criterions)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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/database"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestRHELParser(t *testing.T) {
|
||||||
@ -41,24 +44,33 @@ func TestRHELParser(t *testing.T) {
|
|||||||
expectedFeatureVersions := []database.FeatureVersion{
|
expectedFeatureVersions := []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "centos:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "centos:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "xerces-c",
|
Name: "xerces-c",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
Version: "0:3.1.1-7.el7_1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "centos:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "centos:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "xerces-c-devel",
|
Name: "xerces-c-devel",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("3.1.1-7.el7_1"),
|
Version: "0:3.1.1-7.el7_1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "centos:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "centos:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "xerces-c-doc",
|
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{
|
expectedFeatureVersions := []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "centos:6"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "centos:6",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "firefox",
|
Name: "firefox",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("38.1.0-1.el6_6"),
|
Version: "0:38.1.0-1.el6_6",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "centos:7"},
|
Namespace: database.Namespace{
|
||||||
|
Name: "centos:7",
|
||||||
|
VersionFormat: "rpm",
|
||||||
|
},
|
||||||
Name: "firefox",
|
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"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/updater"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/coreos/pkg/capnslog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -344,21 +346,22 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var version types.Version
|
var version string
|
||||||
if md["status"] == "released" {
|
if md["status"] == "released" {
|
||||||
if md["note"] != "" {
|
if md["note"] != "" {
|
||||||
var err error
|
var err error
|
||||||
version, err = types.NewVersion(md["note"])
|
err = versionfmt.Valid("dpkg", md["note"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("could not parse package version '%s': %s. skipping", md["note"], err)
|
log.Warningf("could not parse package version '%s': %s. skipping", md["note"], err)
|
||||||
}
|
}
|
||||||
|
version = md["note"]
|
||||||
}
|
}
|
||||||
} else if md["status"] == "not-affected" {
|
} else if md["status"] == "not-affected" {
|
||||||
version = types.MinVersion
|
version = versionfmt.MinVersion
|
||||||
} else {
|
} else {
|
||||||
version = types.MaxVersion
|
version = versionfmt.MaxVersion
|
||||||
}
|
}
|
||||||
if version.String() == "" {
|
if version == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 clair authors
|
// Copyright 2016 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -20,9 +20,11 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestUbuntuParser(t *testing.T) {
|
||||||
@ -48,21 +50,21 @@ func TestUbuntuParser(t *testing.T) {
|
|||||||
Namespace: database.Namespace{Name: "ubuntu:14.04"},
|
Namespace: database.Namespace{Name: "ubuntu:14.04"},
|
||||||
Name: "libmspack",
|
Name: "libmspack",
|
||||||
},
|
},
|
||||||
Version: types.MaxVersion,
|
Version: versionfmt.MaxVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "ubuntu:15.04"},
|
Namespace: database.Namespace{Name: "ubuntu:15.04"},
|
||||||
Name: "libmspack",
|
Name: "libmspack",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.4-3"),
|
Version: "0.4-3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{
|
Feature: database.Feature{
|
||||||
Namespace: database.Namespace{Name: "ubuntu:15.10"},
|
Namespace: database.Namespace{Name: "ubuntu:15.10"},
|
||||||
Name: "libmspack-anotherpkg",
|
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
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
||||||
@ -15,7 +29,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
|||||||
Namespace: database.Namespace{Name: "Namespace1"},
|
Namespace: database.Namespace{Name: "Namespace1"},
|
||||||
Name: "Feature1",
|
Name: "Feature1",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.1"),
|
Version: "0.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
fv2 := database.FeatureVersion{
|
fv2 := database.FeatureVersion{
|
||||||
@ -23,7 +37,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
|||||||
Namespace: database.Namespace{Name: "Namespace2"},
|
Namespace: database.Namespace{Name: "Namespace2"},
|
||||||
Name: "Feature1",
|
Name: "Feature1",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.2"),
|
Version: "0.2",
|
||||||
}
|
}
|
||||||
|
|
||||||
fv3 := database.FeatureVersion{
|
fv3 := database.FeatureVersion{
|
||||||
@ -31,7 +45,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
|
|||||||
Namespace: database.Namespace{Name: "Namespace2"},
|
Namespace: database.Namespace{Name: "Namespace2"},
|
||||||
Name: "Feature2",
|
Name: "Feature2",
|
||||||
},
|
},
|
||||||
Version: types.NewVersionUnsafe("0.3"),
|
Version: "0.3",
|
||||||
}
|
}
|
||||||
|
|
||||||
vulnerability := database.Vulnerability{
|
vulnerability := database.Vulnerability{
|
||||||
|
@ -18,10 +18,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"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/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")
|
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:":
|
case line[:2] == "P:":
|
||||||
ipkg.Feature.Name = line[2:]
|
ipkg.Feature.Name = line[2:]
|
||||||
case line[:2] == "V:":
|
case line[:2] == "V:":
|
||||||
var err error
|
version := string(line[2:])
|
||||||
ipkg.Version, err = types.NewVersion(line[2:])
|
err := versionfmt.Valid("dpkg", version)
|
||||||
if err != nil {
|
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
|
// If we have a whole feature, store it in the set and try to parse a new
|
||||||
// one.
|
// one.
|
||||||
if ipkg.Feature.Name != "" && ipkg.Version.String() != "" {
|
if ipkg.Feature.Name != "" && ipkg.Version != "" {
|
||||||
pkgSet[ipkg.Feature.Name+"#"+ipkg.Version.String()] = ipkg
|
pkgSet[ipkg.Feature.Name+"#"+ipkg.Version] = ipkg
|
||||||
ipkg = database.FeatureVersion{}
|
ipkg = database.FeatureVersion{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/coreos/clair/worker/detectors/feature"
|
"github.com/coreos/clair/worker/detectors/feature"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,47 +27,47 @@ func TestAPKFeatureDetection(t *testing.T) {
|
|||||||
FeatureVersions: []database.FeatureVersion{
|
FeatureVersions: []database.FeatureVersion{
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "musl"},
|
Feature: database.Feature{Name: "musl"},
|
||||||
Version: types.NewVersionUnsafe("1.1.14-r10"),
|
Version: "1.1.14-r10",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "busybox"},
|
Feature: database.Feature{Name: "busybox"},
|
||||||
Version: types.NewVersionUnsafe("1.24.2-r9"),
|
Version: "1.24.2-r9",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "alpine-baselayout"},
|
Feature: database.Feature{Name: "alpine-baselayout"},
|
||||||
Version: types.NewVersionUnsafe("3.0.3-r0"),
|
Version: "3.0.3-r0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "alpine-keys"},
|
Feature: database.Feature{Name: "alpine-keys"},
|
||||||
Version: types.NewVersionUnsafe("1.1-r0"),
|
Version: "1.1-r0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "zlib"},
|
Feature: database.Feature{Name: "zlib"},
|
||||||
Version: types.NewVersionUnsafe("1.2.8-r2"),
|
Version: "1.2.8-r2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "libcrypto1.0"},
|
Feature: database.Feature{Name: "libcrypto1.0"},
|
||||||
Version: types.NewVersionUnsafe("1.0.2h-r1"),
|
Version: "1.0.2h-r1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "libssl1.0"},
|
Feature: database.Feature{Name: "libssl1.0"},
|
||||||
Version: types.NewVersionUnsafe("1.0.2h-r1"),
|
Version: "1.0.2h-r1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "apk-tools"},
|
Feature: database.Feature{Name: "apk-tools"},
|
||||||
Version: types.NewVersionUnsafe("2.6.7-r0"),
|
Version: "2.6.7-r0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "scanelf"},
|
Feature: database.Feature{Name: "scanelf"},
|
||||||
Version: types.NewVersionUnsafe("1.1.6-r0"),
|
Version: "1.1.6-r0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "musl-utils"},
|
Feature: database.Feature{Name: "musl-utils"},
|
||||||
Version: types.NewVersionUnsafe("1.1.14-r10"),
|
Version: "1.1.14-r10",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "libc-utils"},
|
Feature: database.Feature{Name: "libc-utils"},
|
||||||
Version: types.NewVersionUnsafe("0.7-r0"),
|
Version: "0.7-r0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
|
@ -19,10 +19,14 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"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/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 (
|
var (
|
||||||
@ -60,7 +64,7 @@ func (detector *DpkgFeaturesDetector) Detect(data map[string][]byte) ([]database
|
|||||||
// Defines the name of the package
|
// Defines the name of the package
|
||||||
|
|
||||||
pkg.Feature.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
|
pkg.Feature.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
|
||||||
pkg.Version = types.Version{}
|
pkg.Version = ""
|
||||||
} else if strings.HasPrefix(line, "Source: ") {
|
} else if strings.HasPrefix(line, "Source: ") {
|
||||||
// Source line (Optionnal)
|
// Source line (Optionnal)
|
||||||
// Gives the name of the source package
|
// 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"]
|
pkg.Feature.Name = md["name"]
|
||||||
if md["version"] != "" {
|
if md["version"] != "" {
|
||||||
pkg.Version, err = types.NewVersion(md["version"])
|
version := md["version"]
|
||||||
|
err = versionfmt.Valid("dpkg", version)
|
||||||
if err != nil {
|
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
|
// Version line
|
||||||
// Defines the version of the package
|
// Defines the version of the package
|
||||||
// This version is less important than a version retrieved from a Source line
|
// 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
|
// 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
|
// 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 {
|
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
|
// Add the package to the result array if we have all the informations
|
||||||
if pkg.Feature.Name != "" && pkg.Version.String() != "" {
|
if pkg.Feature.Name != "" && pkg.Version != "" {
|
||||||
packagesMap[pkg.Feature.Name+"#"+pkg.Version.String()] = pkg
|
packagesMap[pkg.Feature.Name+"#"+pkg.Version] = pkg
|
||||||
pkg.Feature.Name = ""
|
pkg.Feature.Name = ""
|
||||||
pkg.Version = types.Version{}
|
pkg.Version = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/coreos/clair/worker/detectors/feature"
|
"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
|
// Two packages from this source are installed, it should only appear one time
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "pam"},
|
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
|
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
|
Version: "2.3.1-93ubuntu1", // The version comes from the "Version:" line
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "gcc-5"},
|
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{
|
Data: map[string][]byte{
|
||||||
|
@ -20,12 +20,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/coreos/clair/worker/detectors"
|
"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")
|
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "rpm")
|
||||||
@ -88,7 +92,8 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse version
|
// 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 {
|
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", line[1], err.Error())
|
||||||
continue
|
continue
|
||||||
@ -101,7 +106,7 @@ func (detector *RpmFeaturesDetector) Detect(data map[string][]byte) ([]database.
|
|||||||
},
|
},
|
||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
packagesMap[pkg.Feature.Name+"#"+pkg.Version.String()] = pkg
|
packagesMap[pkg.Feature.Name+"#"+pkg.Version] = pkg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the map to a slice
|
// 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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -18,7 +18,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
"github.com/coreos/clair/worker/detectors/feature"
|
"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
|
// Two packages from this source are installed, it should only appear once
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "centos-release"},
|
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
|
// Two packages from this source are installed, it should only appear once
|
||||||
{
|
{
|
||||||
Feature: database.Feature{Name: "filesystem"},
|
Feature: database.Feature{Name: "filesystem"},
|
||||||
Version: types.NewVersionUnsafe("3.2-18.el7"),
|
Version: "3.2-18.el7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
|
@ -180,7 +180,7 @@ func detectFeatureVersions(name string, data map[string][]byte, namespace *datab
|
|||||||
parentFeatureNamespaces := make(map[string]database.Namespace)
|
parentFeatureNamespaces := make(map[string]database.Namespace)
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
for _, parentFeature := range parent.Features {
|
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
|
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.
|
// The FeatureVersion is present in the parent layer; associate with their Namespace.
|
||||||
features[i].Feature.Namespace = parentFeatureNamespace
|
features[i].Feature.Namespace = parentFeatureNamespace
|
||||||
continue
|
continue
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
|
||||||
|
|
||||||
// Register the required detectors.
|
// Register the required detectors.
|
||||||
_ "github.com/coreos/clair/worker/detectors/data/docker"
|
_ "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.
|
// Create the list of FeatureVersions that should not been upgraded from one layer to another.
|
||||||
nonUpgradedFeatureVersions := []database.FeatureVersion{
|
nonUpgradedFeatureVersions := []database.FeatureVersion{
|
||||||
{Feature: database.Feature{Name: "libtext-wrapi18n-perl"}, Version: types.NewVersionUnsafe("0.06-7")},
|
{Feature: database.Feature{Name: "libtext-wrapi18n-perl"}, Version: "0.06-7"},
|
||||||
{Feature: database.Feature{Name: "libtext-charwidth-perl"}, Version: types.NewVersionUnsafe("0.04-7")},
|
{Feature: database.Feature{Name: "libtext-charwidth-perl"}, Version: "0.04-7"},
|
||||||
{Feature: database.Feature{Name: "libtext-iconv-perl"}, Version: types.NewVersionUnsafe("1.7-5")},
|
{Feature: database.Feature{Name: "libtext-iconv-perl"}, Version: "1.7-5"},
|
||||||
{Feature: database.Feature{Name: "mawk"}, Version: types.NewVersionUnsafe("1.3.3-17")},
|
{Feature: database.Feature{Name: "mawk"}, Version: "1.3.3-17"},
|
||||||
{Feature: database.Feature{Name: "insserv"}, Version: types.NewVersionUnsafe("1.14.0-5")},
|
{Feature: database.Feature{Name: "insserv"}, Version: "1.14.0-5"},
|
||||||
{Feature: database.Feature{Name: "db"}, Version: types.NewVersionUnsafe("5.1.29-5")},
|
{Feature: database.Feature{Name: "db"}, Version: "5.1.29-5"},
|
||||||
{Feature: database.Feature{Name: "ustr"}, Version: types.NewVersionUnsafe("1.0.4-3")},
|
{Feature: database.Feature{Name: "ustr"}, Version: "1.0.4-3"},
|
||||||
{Feature: database.Feature{Name: "xz-utils"}, Version: types.NewVersionUnsafe("5.1.1alpha+20120614-2")},
|
{Feature: database.Feature{Name: "xz-utils"}, Version: "5.1.1alpha+20120614-2"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process test layers.
|
// Process test layers.
|
||||||
|
Loading…
Reference in New Issue
Block a user