featurefmt: use namespace's versionfmt to specify listers
use namespace's versionfmt to specify listers used to scan features the content detection functions are changed accordingly in worker
This commit is contained in:
parent
50437f32a1
commit
9561d623c2
@ -29,7 +29,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
featurefmt.RegisterLister("apk", &lister{})
|
featurefmt.RegisterLister("apk", dpkg.ParserName, &lister{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type lister struct{}
|
type lister struct{}
|
||||||
|
@ -37,7 +37,7 @@ var (
|
|||||||
type lister struct{}
|
type lister struct{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
featurefmt.RegisterLister("dpkg", &lister{})
|
featurefmt.RegisterLister("dpkg", dpkg.ParserName, &lister{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.FeatureVersion, error) {
|
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.FeatureVersion, error) {
|
||||||
|
@ -23,14 +23,17 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/pkg/tarutil"
|
"github.com/coreos/clair/pkg/tarutil"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
listersM sync.RWMutex
|
listersM sync.RWMutex
|
||||||
listers = make(map[string]Lister)
|
listers = make(map[string]Lister)
|
||||||
|
versionfmtListerName = make(map[string][]string)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lister represents an ability to list the features present in an image layer.
|
// Lister represents an ability to list the features present in an image layer.
|
||||||
@ -49,7 +52,7 @@ type Lister interface {
|
|||||||
//
|
//
|
||||||
// If called twice with the same name, the name is blank, or if the provided
|
// If called twice with the same name, the name is blank, or if the provided
|
||||||
// Lister is nil, this function panics.
|
// Lister is nil, this function panics.
|
||||||
func RegisterLister(name string, l Lister) {
|
func RegisterLister(name string, versionfmt string, l Lister) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
panic("featurefmt: could not register a Lister with an empty name")
|
panic("featurefmt: could not register a Lister with an empty name")
|
||||||
}
|
}
|
||||||
@ -65,19 +68,35 @@ func RegisterLister(name string, l Lister) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listers[name] = l
|
listers[name] = l
|
||||||
|
versionfmtListerName[versionfmt] = append(versionfmtListerName[versionfmt], name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFeatures produces the list of FeatureVersions in an image layer using
|
// ListFeatures produces the list of FeatureVersions in an image layer using
|
||||||
// every registered Lister.
|
// every registered Lister.
|
||||||
func ListFeatures(files tarutil.FilesMap) ([]database.FeatureVersion, error) {
|
func ListFeatures(files tarutil.FilesMap, namespace *database.Namespace) ([]database.FeatureVersion, error) {
|
||||||
listersM.RLock()
|
listersM.RLock()
|
||||||
defer listersM.RUnlock()
|
defer listersM.RUnlock()
|
||||||
|
|
||||||
var totalFeatures []database.FeatureVersion
|
var (
|
||||||
for _, lister := range listers {
|
totalFeatures []database.FeatureVersion
|
||||||
features, err := lister.ListFeatures(files)
|
listersName []string
|
||||||
|
found bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if namespace == nil {
|
||||||
|
log.Debug("Can't detect features without namespace")
|
||||||
|
return totalFeatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if listersName, found = versionfmtListerName[namespace.VersionFormat]; !found {
|
||||||
|
log.WithFields(log.Fields{"namespace": namespace.Name, "version format": namespace.VersionFormat}).Debug("Unsupported Namespace")
|
||||||
|
return totalFeatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, listerName := range listersName {
|
||||||
|
features, err := listers[listerName].ListFeatures(files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []database.FeatureVersion{}, err
|
return totalFeatures, err
|
||||||
}
|
}
|
||||||
totalFeatures = append(totalFeatures, features...)
|
totalFeatures = append(totalFeatures, features...)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ import (
|
|||||||
type lister struct{}
|
type lister struct{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
featurefmt.RegisterLister("rpm", &lister{})
|
featurefmt.RegisterLister("rpm", rpm.ParserName, &lister{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.FeatureVersion, error) {
|
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.FeatureVersion, error) {
|
||||||
|
101
worker.go
101
worker.go
@ -128,16 +128,11 @@ func detectContent(imageFormat, name, path string, headers map[string]string, pa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect features.
|
featureVersions, err = detectFeatureVersions(name, files, namespaces, parent)
|
||||||
var fv []database.FeatureVersion
|
if err != nil {
|
||||||
// detect feature versions in all namespaces
|
return
|
||||||
for _, namespace := range namespaces {
|
|
||||||
fv, err = detectFeatureVersions(name, files, &namespace, parent)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
featureVersions = append(featureVersions, fv...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(featureVersions) > 0 {
|
if len(featureVersions) > 0 {
|
||||||
log.WithFields(log.Fields{logLayerName: name, "feature count": len(featureVersions)}).Debug("detected features")
|
log.WithFields(log.Fields{logLayerName: name, "feature count": len(featureVersions)}).Debug("detected features")
|
||||||
}
|
}
|
||||||
@ -153,43 +148,27 @@ func detectNamespaces(name string, files tarutil.FilesMap, parent *database.Laye
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ns := range nsCurrent {
|
|
||||||
nsSet[ns.Name] = &ns
|
|
||||||
log.WithFields(log.Fields{logLayerName: name, "detected namespace": ns.Name}).Debug("detected namespace")
|
|
||||||
}
|
|
||||||
|
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
for _, ns := range parent.Namespaces {
|
for _, ns := range parent.Namespaces {
|
||||||
nsSet[ns.Name] = &ns
|
// Under assumption that one version format corresponds to one type
|
||||||
log.WithFields(log.Fields{logLayerName: name, "detected namespace": ns.Name}).Debug("detected namespace (from parent)")
|
// of namespace.
|
||||||
|
nsSet[ns.VersionFormat] = &ns
|
||||||
|
log.WithFields(log.Fields{logLayerName: name, "detected namespace": ns.Name, "version format": ns.VersionFormat}).Debug("detected namespace (from parent)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ns := range nsCurrent {
|
||||||
|
nsSet[ns.VersionFormat] = &ns
|
||||||
|
log.WithFields(log.Fields{logLayerName: name, "detected namespace": ns.Name, "version format": ns.VersionFormat}).Debug("detected namespace")
|
||||||
|
}
|
||||||
|
|
||||||
for _, ns := range nsSet {
|
for _, ns := range nsSet {
|
||||||
namespaces = append(namespaces, *ns)
|
namespaces = append(namespaces, *ns)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectFeatureVersions(name string, files tarutil.FilesMap, namespace *database.Namespace, parent *database.Layer) (features []database.FeatureVersion, err error) {
|
func detectFeatureVersions(name string, files tarutil.FilesMap, namespaces []database.Namespace, parent *database.Layer) (features []database.FeatureVersion, err error) {
|
||||||
// TODO(Quentin-M): We need to pass the parent image to DetectFeatures because it's possible that
|
|
||||||
// some detectors would need it in order to produce the entire feature list (if they can only
|
|
||||||
// detect a diff). Also, we should probably pass the detected namespace so detectors could
|
|
||||||
// make their own decision.
|
|
||||||
features, err = featurefmt.ListFeatures(files)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no FeatureVersions, use parent's FeatureVersions if possible.
|
|
||||||
// TODO(Quentin-M): We eventually want to give the choice to each detectors to use none/some of
|
|
||||||
// their parent's FeatureVersions. It would be useful for detectors that can't find their entire
|
|
||||||
// result using one Layer.
|
|
||||||
if len(features) == 0 && parent != nil {
|
|
||||||
features = parent.Features
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a map of the namespaces for each FeatureVersion in our parent layer.
|
// Build a map of the namespaces for each FeatureVersion in our parent layer.
|
||||||
parentFeatureNamespaces := make(map[string]database.Namespace)
|
parentFeatureNamespaces := make(map[string]database.Namespace)
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
@ -198,28 +177,44 @@ func detectFeatureVersions(name string, files tarutil.FilesMap, namespace *datab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that each FeatureVersion has an associated Namespace.
|
for _, ns := range namespaces {
|
||||||
for i, feature := range features {
|
// TODO(Quentin-M): We need to pass the parent image to DetectFeatures because it's possible that
|
||||||
if feature.Feature.Namespace.Name != "" {
|
// some detectors would need it in order to produce the entire feature list (if they can only
|
||||||
// There is a Namespace associated.
|
// detect a diff). Also, we should probably pass the detected namespace so detectors could
|
||||||
continue
|
// make their own decision.
|
||||||
|
detectedFeatures, err := featurefmt.ListFeatures(files, &ns)
|
||||||
|
if err != nil {
|
||||||
|
return features, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if parentFeatureNamespace, ok := parentFeatureNamespaces[feature.Feature.Name+":"+feature.Version]; ok {
|
// Ensure that each FeatureVersion has an associated Namespace.
|
||||||
// The FeatureVersion is present in the parent layer; associate with their Namespace.
|
for i, feature := range detectedFeatures {
|
||||||
features[i].Feature.Namespace = parentFeatureNamespace
|
if feature.Feature.Namespace.Name != "" {
|
||||||
continue
|
// There is a Namespace associated.
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if namespace != nil {
|
if parentFeatureNamespace, ok := parentFeatureNamespaces[feature.Feature.Name+":"+feature.Version]; ok {
|
||||||
// The Namespace has been detected in this layer; associate it.
|
// The FeatureVersion is present in the parent layer; associate
|
||||||
features[i].Feature.Namespace = *namespace
|
// with their Namespace.
|
||||||
continue
|
// This might cause problem because a package with same feature
|
||||||
}
|
// name and version could be different in parent layer's
|
||||||
|
// namespace and current layer's namespace
|
||||||
|
detectedFeatures[i].Feature.Namespace = parentFeatureNamespace
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{"feature name": feature.Feature.Name, "feature version": feature.Version, logLayerName: name}).Warning("Namespace unknown")
|
detectedFeatures[i].Feature.Namespace = ns
|
||||||
err = ErrUnsupported
|
}
|
||||||
return
|
features = append(features, detectedFeatures...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no FeatureVersions, use parent's FeatureVersions if possible.
|
||||||
|
// TODO(Quentin-M): We eventually want to give the choice to each detectors to use none/some of
|
||||||
|
// their parent's FeatureVersions. It would be useful for detectors that can't find their entire
|
||||||
|
// result using one Layer.
|
||||||
|
if len(features) == 0 && parent != nil {
|
||||||
|
features = parent.Features
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -101,16 +101,9 @@ func TestProcessWithDistUpgrade(t *testing.T) {
|
|||||||
// Ensure that the 'wheezy' layer has the expected namespace and non-upgraded features.
|
// Ensure that the 'wheezy' layer has the expected namespace and non-upgraded features.
|
||||||
jessie, ok := datastore.layers["jessie"]
|
jessie, ok := datastore.layers["jessie"]
|
||||||
if assert.True(t, ok, "layer 'jessie' not processed") {
|
if assert.True(t, ok, "layer 'jessie' not processed") {
|
||||||
if !assert.Len(t, jessie.Namespaces, 2) {
|
assert.Len(t, jessie.Namespaces, 1)
|
||||||
return
|
assert.Equal(t, "debian:8", jessie.Namespaces[0].Name)
|
||||||
}
|
assert.Len(t, jessie.Features, 74)
|
||||||
nsNames := []string{jessie.Namespaces[0].Name, jessie.Namespaces[1].Name}
|
|
||||||
// the old features and namespace should remain here with only the features in new namespaces detected
|
|
||||||
assert.Contains(t, nsNames, "debian:8")
|
|
||||||
assert.Contains(t, nsNames, "debian:7")
|
|
||||||
|
|
||||||
// because the featurefmt detects the features under "debian:8" and "debian:7", therefore the number of features is duplicated
|
|
||||||
assert.Len(t, jessie.Features, 148)
|
|
||||||
|
|
||||||
for _, nufv := range nonUpgradedFeatureVersions {
|
for _, nufv := range nonUpgradedFeatureVersions {
|
||||||
nufv.Feature.Namespace.Name = "debian:7"
|
nufv.Feature.Namespace.Name = "debian:7"
|
||||||
|
Loading…
Reference in New Issue
Block a user