284 lines
8.2 KiB
Go
284 lines
8.2 KiB
Go
package pgsql
|
|
|
|
import (
|
|
"database/sql"
|
|
|
|
"github.com/coreos/clair/database"
|
|
cerrors "github.com/coreos/clair/utils/errors"
|
|
"github.com/guregu/null/zero"
|
|
)
|
|
|
|
func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities bool) (database.Layer, error) {
|
|
// Find the layer
|
|
var layer database.Layer
|
|
var parentName sql.NullString
|
|
var namespaceName sql.NullString
|
|
|
|
err := pgSQL.QueryRow(getQuery("s_layer"), name).
|
|
Scan(&layer.ID, &layer.Name, &layer.EngineVersion, &parentName, &namespaceName)
|
|
|
|
if err == sql.ErrNoRows {
|
|
return layer, cerrors.ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return layer, err
|
|
}
|
|
|
|
if parentName.Valid {
|
|
layer.Parent = &database.Layer{Name: parentName.String}
|
|
}
|
|
if namespaceName.Valid {
|
|
layer.Namespace = &database.Namespace{Name: namespaceName.String}
|
|
}
|
|
|
|
// Find its features
|
|
if withFeatures || withVulnerabilities {
|
|
featureVersions, err := pgSQL.getLayerFeatureVersions(layer.ID, !withFeatures)
|
|
if err != nil {
|
|
return layer, err
|
|
}
|
|
layer.Features = featureVersions
|
|
|
|
if withVulnerabilities {
|
|
// Load the vulnerabilities that affect the FeatureVersions.
|
|
err := pgSQL.loadAffectedBy(layer.Features)
|
|
if err != nil {
|
|
return layer, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return layer, nil
|
|
}
|
|
|
|
// getLayerFeatureVersions returns list of database.FeatureVersion that a database.Layer has.
|
|
// if idOnly is specified, the returned structs will only have their ID filled. Otherwise,
|
|
// it also gets their versions, feature's names, feature's namespace's names.
|
|
func (pgSQL *pgSQL) getLayerFeatureVersions(layerID int, idOnly bool) ([]database.FeatureVersion, error) {
|
|
var featureVersions []database.FeatureVersion
|
|
|
|
// Build query
|
|
var query string
|
|
if idOnly {
|
|
query = getQuery("s_layer_featureversion_id_only")
|
|
} else {
|
|
query = getQuery("s_layer_featureversion")
|
|
}
|
|
|
|
// Query
|
|
rows, err := pgSQL.Query(query, layerID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return featureVersions, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
// Scan query
|
|
var modification string
|
|
mapFeatureVersions := make(map[int]database.FeatureVersion)
|
|
for rows.Next() {
|
|
var featureVersion database.FeatureVersion
|
|
|
|
if idOnly {
|
|
err = rows.Scan(&featureVersion.ID, &modification)
|
|
if err != nil {
|
|
return featureVersions, err
|
|
}
|
|
} else {
|
|
err = rows.Scan(&featureVersion.ID, &modification, &featureVersion.Feature.Namespace.ID,
|
|
&featureVersion.Feature.Namespace.Name, &featureVersion.Feature.ID,
|
|
&featureVersion.Feature.Name, &featureVersion.ID, &featureVersion.Version)
|
|
if err != nil {
|
|
return featureVersions, err
|
|
}
|
|
}
|
|
|
|
// Do transitive closure
|
|
switch modification {
|
|
case "add":
|
|
mapFeatureVersions[featureVersion.ID] = featureVersion
|
|
case "del":
|
|
delete(mapFeatureVersions, featureVersion.ID)
|
|
default:
|
|
log.Warningf("unknown Layer_diff_FeatureVersion's modification: %s", modification)
|
|
return featureVersions, database.ErrInconsistent
|
|
}
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return featureVersions, err
|
|
}
|
|
|
|
// Build result by converting our map to a slice
|
|
for _, featureVersion := range mapFeatureVersions {
|
|
featureVersions = append(featureVersions, featureVersion)
|
|
}
|
|
|
|
return featureVersions, nil
|
|
}
|
|
|
|
// loadAffectedBy returns the list of database.Vulnerability that affect the given
|
|
// FeatureVersion.
|
|
func (pgSQL *pgSQL) loadAffectedBy(featureVersions []database.FeatureVersion) error {
|
|
if len(featureVersions) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Construct list of FeatureVersion IDs, we will do a single query
|
|
featureVersionIDs := make([]int, 0, len(featureVersions))
|
|
for i := 0; i < len(featureVersions); i++ {
|
|
featureVersionIDs = append(featureVersionIDs, featureVersions[i].ID)
|
|
}
|
|
|
|
rows, err := pgSQL.Query(getQuery("s_featureversions_vulnerabilities"),
|
|
buildInputArray(featureVersionIDs))
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
vulnerabilities := make(map[int][]database.Vulnerability, len(featureVersions))
|
|
var featureversionID int
|
|
for rows.Next() {
|
|
var vulnerability database.Vulnerability
|
|
err := rows.Scan(&featureversionID, &vulnerability.ID, &vulnerability.Name,
|
|
&vulnerability.Description, &vulnerability.Link, &vulnerability.Severity,
|
|
&vulnerability.Namespace.Name, &vulnerability.FixedBy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vulnerabilities[featureversionID] = append(vulnerabilities[featureversionID], vulnerability)
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Assign vulnerabilities to every FeatureVersions
|
|
for i := 0; i < len(featureVersions); i++ {
|
|
featureVersions[i].AffectedBy = vulnerabilities[featureVersions[i].ID]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// InsertLayer insert a single layer in the database
|
|
//
|
|
// The Name and EngineVersion fields are required.
|
|
// The Parent, Namespace, Features are optional.
|
|
// However, please note that the Parent field, if provided, is expected to have been retrieved
|
|
// using FindLayer with its Features.
|
|
//
|
|
// The Name must be unique for two different layers.
|
|
//
|
|
// If the Layer already exists and the EngineVersion value of the inserted layer is higher than the
|
|
// stored value, the EngineVersion, the Namespace and the Feature list will be updated.
|
|
//
|
|
// Internally, only Feature additions/removals are stored for each layer. If a layer has a parent,
|
|
// the Feature list will be compared to the parent's Feature list and the difference will be stored.
|
|
// Note that when the Namespace of a layer differs from its parent, it is expected that several
|
|
// Feature that were already included a parent will have their Namespace updated as well
|
|
// (happens when Feature detectors relies on the detected layer Namespace). However, if the listed
|
|
// Feature has the same Name/Version as its parent, InsertLayer considers that the Feature hasn't
|
|
// been modified.
|
|
// TODO(Quentin-M): This behavior should be implemented at the Feature detectors level.
|
|
func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error {
|
|
// Verify parameters
|
|
if layer.Name == "" {
|
|
log.Warning("could not insert a layer which has an empty Name")
|
|
return cerrors.NewBadRequestError("could not insert a layer which has an empty Name")
|
|
}
|
|
|
|
// Get a potentially existing layer.
|
|
existingLayer, err := pgSQL.FindLayer(layer.Name, true, false)
|
|
if err != nil && err != cerrors.ErrNotFound {
|
|
return err
|
|
} else if err == nil {
|
|
layer.ID = existingLayer.ID
|
|
}
|
|
|
|
// Begin transaction.
|
|
tx, err := pgSQL.Begin()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
// Find or insert namespace if provided.
|
|
var namespaceID zero.Int
|
|
if layer.Namespace != nil {
|
|
n, err := pgSQL.insertNamespace(*layer.Namespace)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
namespaceID = zero.IntFrom(int64(n))
|
|
}
|
|
|
|
if layer.ID == 0 {
|
|
// Insert a new layer.
|
|
var parentID zero.Int
|
|
if layer.Parent != nil {
|
|
if layer.Parent.ID == 0 {
|
|
log.Warning("Parent is expected to be retrieved from database when inserting a layer.")
|
|
return cerrors.NewBadRequestError("Parent is expected to be retrieved from database when inserting a layer.")
|
|
}
|
|
|
|
parentID = zero.IntFrom(int64(layer.Parent.ID))
|
|
|
|
// Import the Namespace from the parent is this layer doesn't specify one.
|
|
if zero.IsNull(namespaceID) {
|
|
namespaceID = zero.IntFrom(int64(layer.Parent.Namespace.ID))
|
|
}
|
|
}
|
|
|
|
err = tx.QueryRow(getQuery("i_layer"), layer.Name, layer.EngineVersion, parentID, namespaceID).
|
|
Scan(&layer.ID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
} else {
|
|
if existingLayer.EngineVersion >= layer.EngineVersion {
|
|
// The layer exists and has an equal or higher engine verison, do nothing.
|
|
return nil
|
|
}
|
|
|
|
// Update an existing layer.
|
|
_, err = tx.Exec(getQuery("u_layer"), layer.ID, layer.EngineVersion, namespaceID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Update Layer_diff_FeatureVersion now.
|
|
updateDiffFeatureVersions(tx, &layer, &existingLayer)
|
|
|
|
// Commit transaction.
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func updateDiffFeatureVersions(tx *sql.Tx, layer, existingLayer *database.Layer) {
|
|
// TODO
|
|
|
|
if existingLayer != nil {
|
|
// We are updating a layer, we need to diff the Features with the existing Layer.
|
|
|
|
} else if layer.Parent == nil {
|
|
// There is no parent, every Features are added.
|
|
|
|
} else if layer.Parent != nil {
|
|
// There is a parent, we need to diff the Features with it.
|
|
|
|
}
|
|
}
|
|
|
|
func (pgSQL *pgSQL) DeleteLayer(name string) error {
|
|
// TODO
|
|
return nil
|
|
}
|