diff --git a/ancestry.go b/ancestry.go index 9f47e784..e48ce569 100644 --- a/ancestry.go +++ b/ancestry.go @@ -211,9 +211,20 @@ func (b *AncestryBuilder) createLayerIndexedFeature(namespace *layerIndexedNames func (b *AncestryBuilder) lookupNamespace(feature *database.LayerFeature) (*layerIndexedNamespace, bool) { matchedNamespaces := []*layerIndexedNamespace{} - for i, namespace := range b.namespaces { - if namespace.Namespace.VersionFormat == feature.VersionFormat { - matchedNamespaces = append(matchedNamespaces, &b.namespaces[i]) + if feature.PotentialNamespace.Name != "" { + a := &layerIndexedNamespace{ + Namespace: database.LayerNamespace{ + Namespace: feature.PotentialNamespace, + }, + IntroducedIn: b.layerIndex, + } + matchedNamespaces = append(matchedNamespaces, a) + } else { + + for i, namespace := range b.namespaces { + if namespace.Namespace.VersionFormat == feature.VersionFormat { + matchedNamespaces = append(matchedNamespaces, &b.namespaces[i]) + } } } diff --git a/ancestry_test.go b/ancestry_test.go index 437cf703..25e8e2ad 100644 --- a/ancestry_test.go +++ b/ancestry_test.go @@ -31,6 +31,7 @@ var ( aptsources = database.NewNamespaceDetector("apt-sources", "1.0") ubuntu = *database.NewNamespace("ubuntu:14.04", "dpkg") ubuntu16 = *database.NewNamespace("ubuntu:16.04", "dpkg") + rhel7 = *database.NewNamespace("cpe:/o:redhat:enterprise_linux:7::computenode", "rpm") debian = *database.NewNamespace("debian:7", "dpkg") python2 = *database.NewNamespace("python:2", "pip") sed = *database.NewSourcePackage("sed", "4.4-2", "dpkg") @@ -39,6 +40,8 @@ var ( tar = *database.NewBinaryPackage("tar", "1.29b-2", "dpkg") scipy = *database.NewSourcePackage("scipy", "3.0.0", "pip") + emptyNamespace = database.Namespace{} + detectors = []database.Detector{dpkg, osrelease, rpm} multinamespaceDetectors = []database.Detector{dpkg, osrelease, pip} ) @@ -97,10 +100,11 @@ func (b *layerBuilder) addNamespace(detector database.Detector, ns database.Name return b } -func (b *layerBuilder) addFeature(detector database.Detector, f database.Feature) *layerBuilder { +func (b *layerBuilder) addFeature(detector database.Detector, f database.Feature, ns database.Namespace) *layerBuilder { b.layer.Features = append(b.layer.Features, database.LayerFeature{ - Feature: f, - By: detector, + Feature: f, + By: detector, + PotentialNamespace: ns, }) return b @@ -112,39 +116,43 @@ var testImage = []*database.Layer{ // ubuntu namespace newLayerBuilder("1").addNamespace(osrelease, ubuntu).layer, // install sed - newLayerBuilder("2").addFeature(dpkg, sed).layer, + newLayerBuilder("2").addFeature(dpkg, sed, emptyNamespace).layer, // install tar - newLayerBuilder("3").addFeature(dpkg, sed).addFeature(dpkg, tar).layer, + newLayerBuilder("3").addFeature(dpkg, sed, emptyNamespace).addFeature(dpkg, tar, emptyNamespace).layer, // remove tar - newLayerBuilder("4").addFeature(dpkg, sed).layer, + newLayerBuilder("4").addFeature(dpkg, sed, emptyNamespace).layer, // upgrade ubuntu newLayerBuilder("5").addNamespace(osrelease, ubuntu16).layer, // no change to the detectable files newLayerBuilder("6").layer, // change to the package installer database but no features are affected. - newLayerBuilder("7").addFeature(dpkg, sed).layer, + newLayerBuilder("7").addFeature(dpkg, sed, emptyNamespace).layer, } var invalidNamespace = []*database.Layer{ // add package without namespace, this indicates that the namespace detector // could not detect the namespace. - newLayerBuilder("0").addFeature(dpkg, sed).layer, + newLayerBuilder("0").addFeature(dpkg, sed, emptyNamespace).layer, } var noMatchingNamespace = []*database.Layer{ - newLayerBuilder("0").addFeature(rpm, sedByRPM).addFeature(dpkg, sed).addNamespace(osrelease, ubuntu).layer, + newLayerBuilder("0").addFeature(rpm, sedByRPM, emptyNamespace).addFeature(dpkg, sed, emptyNamespace).addNamespace(osrelease, ubuntu).layer, } var multiplePackagesOnFirstLayer = []*database.Layer{ - newLayerBuilder("0").addFeature(dpkg, sed).addFeature(dpkg, tar).addFeature(dpkg, sedBin).addNamespace(osrelease, ubuntu16).layer, + newLayerBuilder("0").addFeature(dpkg, sed, emptyNamespace).addFeature(dpkg, tar, emptyNamespace).addFeature(dpkg, sedBin, emptyNamespace).addNamespace(osrelease, ubuntu16).layer, } var twoNamespaceDetectorsWithSameResult = []*database.Layer{ - newLayerBuilderWithoutDetector("0").addDetectors(dpkg, aptsources, osrelease).addFeature(dpkg, sed).addNamespace(aptsources, ubuntu).addNamespace(osrelease, ubuntu).layer, + newLayerBuilderWithoutDetector("0").addDetectors(dpkg, aptsources, osrelease).addFeature(dpkg, sed, emptyNamespace).addNamespace(aptsources, ubuntu).addNamespace(osrelease, ubuntu).layer, } var sameVersionFormatDiffName = []*database.Layer{ - newLayerBuilder("0").addFeature(dpkg, sed).addNamespace(aptsources, ubuntu).addNamespace(osrelease, debian).layer, + newLayerBuilder("0").addFeature(dpkg, sed, emptyNamespace).addNamespace(aptsources, ubuntu).addNamespace(osrelease, debian).layer, +} + +var potentialFeatureNamespace = []*database.Layer{ + newLayerBuilder("0").addFeature(rpm, sed, rhel7).layer, } func TestAddLayer(t *testing.T) { @@ -262,6 +270,10 @@ func TestAddLayer(t *testing.T) { title: "noMatchingNamespace", image: noMatchingNamespace, expectedAncestry: *newAncestryBuilder(ancestryName([]string{"0"})).addDetectors(detectors...).addLayer("0", ancestryFeature(ubuntu, sed, osrelease, dpkg)).ancestry, + }, { + title: "featureWithPotentialNamespace", + image: potentialFeatureNamespace, + expectedAncestry: *newAncestryBuilder(ancestryName([]string{"0"})).addDetectors(detectors...).addLayer("0", ancestryFeature(rhel7, sed, database.Detector{}, rpm)).ancestry, }, } diff --git a/database/pgsql/ancestry/ancestry_feature.go b/database/pgsql/ancestry/ancestry_feature.go index 33096d20..433323fd 100644 --- a/database/pgsql/ancestry/ancestry_feature.go +++ b/database/pgsql/ancestry/ancestry_feature.go @@ -50,7 +50,7 @@ func FindAncestryFeatures(tx *sql.Tx, ancestryID int64, detectors detector.Detec for rows.Next() { var ( featureDetectorID int64 - namespaceDetectorID int64 + namespaceDetectorID sql.NullInt64 feature database.NamespacedFeature // index is used to determine which layer the feature belongs to. index sql.NullInt64 @@ -81,9 +81,14 @@ func FindAncestryFeatures(tx *sql.Tx, ancestryID int64, detectors detector.Detec return nil, database.ErrInconsistent } - nsDetector, ok := detectors.ByID[namespaceDetectorID] - if !ok { - return nil, database.ErrInconsistent + var nsDetector database.Detector + if !namespaceDetectorID.Valid { + nsDetector = database.Detector{} + } else { + nsDetector, ok = detectors.ByID[namespaceDetectorID.Int64] + if !ok { + return nil, database.ErrInconsistent + } } featureMap[index.Int64] = append(featureMap[index.Int64], database.AncestryFeature{ @@ -120,9 +125,11 @@ func InsertAncestryFeatures(tx *sql.Tx, ancestryLayerID int64, layer database.An return database.ErrMissingEntities } - namespaceDetectorID, ok := detectors.ByValue[layer.Features[index].NamespaceBy] - if !ok { - return database.ErrMissingEntities + var namespaceDetectorID sql.NullInt64 + var ok bool + namespaceDetectorID.Int64, ok = detectors.ByValue[layer.Features[index].NamespaceBy] + if ok { + namespaceDetectorID.Valid = true } featureDetectorID, ok := detectors.ByValue[layer.Features[index].FeatureBy] diff --git a/database/pgsql/migrations/00001_initial_schema.go b/database/pgsql/migrations/00001_initial_schema.go index 0072857c..0ff9addb 100644 --- a/database/pgsql/migrations/00001_initial_schema.go +++ b/database/pgsql/migrations/00001_initial_schema.go @@ -127,7 +127,7 @@ var ( ancestry_layer_id INT NOT NULL REFERENCES ancestry_layer ON DELETE CASCADE, namespaced_feature_id INT NOT NULL REFERENCES namespaced_feature ON DELETE CASCADE, feature_detector_id INT NOT NULL REFERENCES detector ON DELETE CASCADE, - namespace_detector_id INT NOT NULL REFERENCES detector ON DELETE CASCADE, + namespace_detector_id INT REFERENCES detector ON DELETE CASCADE, UNIQUE (ancestry_layer_id, namespaced_feature_id));`, `CREATE TABLE IF NOT EXISTS ancestry_detector(