1b9ed99646
Move all transaction related logic to dbutil to simplify and later unify the db interface.
527 lines
13 KiB
Go
527 lines
13 KiB
Go
// Copyright 2018 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 database
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/coreos/clair/pkg/commonerr"
|
|
"github.com/coreos/clair/pkg/pagination"
|
|
"github.com/deckarep/golang-set"
|
|
)
|
|
|
|
// DeduplicateNamespaces deduplicates a list of namespaces.
|
|
func DeduplicateNamespaces(namespaces ...Namespace) []Namespace {
|
|
nsSet := mapset.NewSet()
|
|
for _, ns := range namespaces {
|
|
nsSet.Add(ns)
|
|
}
|
|
|
|
uniqueNamespaces := make([]Namespace, 0, nsSet.Cardinality())
|
|
for ns := range nsSet.Iter() {
|
|
uniqueNamespaces = append(uniqueNamespaces, ns.(Namespace))
|
|
}
|
|
|
|
return uniqueNamespaces
|
|
}
|
|
|
|
// DeduplicateFeatures deduplicates a list of list of features.
|
|
func DeduplicateFeatures(features ...Feature) []Feature {
|
|
fSet := mapset.NewSet()
|
|
for _, f := range features {
|
|
fSet.Add(f)
|
|
}
|
|
|
|
return ConvertFeatureSetToFeatures(fSet)
|
|
}
|
|
|
|
// ConvertFeatureSetToFeatures converts a feature set to an array of features
|
|
func ConvertFeatureSetToFeatures(features mapset.Set) []Feature {
|
|
uniqueFeatures := make([]Feature, 0, features.Cardinality())
|
|
for f := range features.Iter() {
|
|
uniqueFeatures = append(uniqueFeatures, f.(Feature))
|
|
}
|
|
|
|
return uniqueFeatures
|
|
}
|
|
|
|
// FindKeyValueAndRollback wraps session FindKeyValue function with begin and
|
|
// roll back.
|
|
func FindKeyValueAndRollback(datastore Datastore, key string) (value string, ok bool, err error) {
|
|
var tx Session
|
|
tx, err = datastore.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
value, ok, err = tx.FindKeyValue(key)
|
|
return
|
|
}
|
|
|
|
// PersistPartialLayerAndCommit wraps session PersistLayer function with begin and
|
|
// commit.
|
|
func PersistPartialLayerAndCommit(datastore Datastore, layer *Layer) error {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := tx.PersistLayer(layer.Hash, layer.Features, layer.Namespaces, layer.By); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// PersistFeaturesAndCommit wraps session PersistFeaturesAndCommit function with begin and commit.
|
|
func PersistFeaturesAndCommit(datastore Datastore, features []Feature) error {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := tx.PersistFeatures(features); err != nil {
|
|
serialized, _ := json.Marshal(features)
|
|
log.WithError(err).WithField("feature", string(serialized)).Error("failed to store features")
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// PersistNamespacesAndCommit wraps session PersistNamespaces function with
|
|
// begin and commit.
|
|
func PersistNamespacesAndCommit(datastore Datastore, namespaces []Namespace) error {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := tx.PersistNamespaces(namespaces); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// FindAncestryAndRollback wraps session FindAncestry function with begin and
|
|
// rollback.
|
|
func FindAncestryAndRollback(datastore Datastore, name string) (Ancestry, bool, error) {
|
|
tx, err := datastore.Begin()
|
|
defer tx.Rollback()
|
|
|
|
if err != nil {
|
|
return Ancestry{}, false, err
|
|
}
|
|
|
|
return tx.FindAncestry(name)
|
|
}
|
|
|
|
// FindLayerAndRollback wraps session FindLayer function with begin and rollback.
|
|
func FindLayerAndRollback(datastore Datastore, hash string) (layer *Layer, ok bool, err error) {
|
|
var tx Session
|
|
if tx, err = datastore.Begin(); err != nil {
|
|
return
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
// TODO(sidac): In order to make the session interface more idiomatic, we'll
|
|
// return the pointer value in the future.
|
|
var dereferencedLayer Layer
|
|
dereferencedLayer, ok, err = tx.FindLayer(hash)
|
|
layer = &dereferencedLayer
|
|
return
|
|
}
|
|
|
|
// DeduplicateNamespacedFeatures returns a copy of all unique features in the
|
|
// input.
|
|
func DeduplicateNamespacedFeatures(features []NamespacedFeature) []NamespacedFeature {
|
|
nsSet := mapset.NewSet()
|
|
for _, ns := range features {
|
|
nsSet.Add(ns)
|
|
}
|
|
|
|
uniqueFeatures := make([]NamespacedFeature, 0, nsSet.Cardinality())
|
|
for ns := range nsSet.Iter() {
|
|
uniqueFeatures = append(uniqueFeatures, ns.(NamespacedFeature))
|
|
}
|
|
|
|
return uniqueFeatures
|
|
}
|
|
|
|
// GetAncestryFeatures returns a list of unique namespaced features in the
|
|
// ancestry.
|
|
func GetAncestryFeatures(ancestry Ancestry) []NamespacedFeature {
|
|
features := []NamespacedFeature{}
|
|
for _, layer := range ancestry.Layers {
|
|
features = append(features, layer.GetFeatures()...)
|
|
}
|
|
|
|
return DeduplicateNamespacedFeatures(features)
|
|
}
|
|
|
|
// UpsertAncestryAndCommit wraps session UpsertAncestry function with begin and commit.
|
|
func UpsertAncestryAndCommit(datastore Datastore, ancestry *Ancestry) error {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = tx.UpsertAncestry(*ancestry); err != nil {
|
|
log.WithError(err).Error("failed to upsert the ancestry")
|
|
serialized, _ := json.Marshal(ancestry)
|
|
log.Debug(string(serialized))
|
|
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PersistNamespacedFeaturesAndCommit wraps session PersistNamespacedFeatures function
|
|
// with begin and commit.
|
|
func PersistNamespacedFeaturesAndCommit(datastore Datastore, features []NamespacedFeature) error {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.PersistNamespacedFeatures(features); err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CacheRelatedVulnerabilityAndCommit wraps session CacheAffectedNamespacedFeatures
|
|
// function with begin and commit.
|
|
func CacheRelatedVulnerabilityAndCommit(datastore Datastore, features []NamespacedFeature) error {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.CacheAffectedNamespacedFeatures(features); err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// IntersectDetectors returns the detectors in both d1 and d2.
|
|
func IntersectDetectors(d1 []Detector, d2 []Detector) []Detector {
|
|
d1Set := mapset.NewSet()
|
|
for _, d := range d1 {
|
|
d1Set.Add(d)
|
|
}
|
|
|
|
d2Set := mapset.NewSet()
|
|
for _, d := range d2 {
|
|
d2Set.Add(d)
|
|
}
|
|
|
|
inter := d1Set.Intersect(d2Set)
|
|
detectors := make([]Detector, 0, inter.Cardinality())
|
|
for d := range inter.Iter() {
|
|
detectors = append(detectors, d.(Detector))
|
|
}
|
|
|
|
return detectors
|
|
}
|
|
|
|
// DiffDetectors returns the detectors belongs to d1 but not d2
|
|
func DiffDetectors(d1 []Detector, d2 []Detector) []Detector {
|
|
d1Set := mapset.NewSet()
|
|
for _, d := range d1 {
|
|
d1Set.Add(d)
|
|
}
|
|
|
|
d2Set := mapset.NewSet()
|
|
for _, d := range d2 {
|
|
d2Set.Add(d)
|
|
}
|
|
|
|
diff := d1Set.Difference(d2Set)
|
|
detectors := make([]Detector, 0, diff.Cardinality())
|
|
for d := range diff.Iter() {
|
|
detectors = append(detectors, d.(Detector))
|
|
}
|
|
|
|
return detectors
|
|
}
|
|
|
|
// MergeLayers merges all content in new layer to l, where the content is
|
|
// updated.
|
|
func MergeLayers(l *Layer, new *Layer) *Layer {
|
|
featureSet := mapset.NewSet()
|
|
namespaceSet := mapset.NewSet()
|
|
bySet := mapset.NewSet()
|
|
|
|
for _, f := range l.Features {
|
|
featureSet.Add(f)
|
|
}
|
|
|
|
for _, ns := range l.Namespaces {
|
|
namespaceSet.Add(ns)
|
|
}
|
|
|
|
for _, d := range l.By {
|
|
bySet.Add(d)
|
|
}
|
|
|
|
for _, feature := range new.Features {
|
|
if !featureSet.Contains(feature) {
|
|
l.Features = append(l.Features, feature)
|
|
featureSet.Add(feature)
|
|
}
|
|
}
|
|
|
|
for _, namespace := range new.Namespaces {
|
|
if !namespaceSet.Contains(namespace) {
|
|
l.Namespaces = append(l.Namespaces, namespace)
|
|
namespaceSet.Add(namespace)
|
|
}
|
|
}
|
|
|
|
for _, detector := range new.By {
|
|
if !bySet.Contains(detector) {
|
|
l.By = append(l.By, detector)
|
|
bySet.Add(detector)
|
|
}
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// AcquireLock acquires a named global lock for a duration.
|
|
func AcquireLock(datastore Datastore, name, owner string, duration time.Duration) (acquired bool, expiration time.Time) {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return false, time.Time{}
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
locked, t, err := tx.AcquireLock(name, owner, duration)
|
|
if err != nil {
|
|
return false, time.Time{}
|
|
}
|
|
|
|
if locked {
|
|
if err := tx.Commit(); err != nil {
|
|
return false, time.Time{}
|
|
}
|
|
}
|
|
|
|
return locked, t
|
|
}
|
|
|
|
// ExtendLock extends the duration of an existing global lock for the given
|
|
// duration.
|
|
func ExtendLock(ds Datastore, name, whoami string, desiredLockDuration time.Duration) (extended bool, expiration time.Time) {
|
|
tx, err := ds.Begin()
|
|
if err != nil {
|
|
return false, time.Time{}
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
locked, expiration, err := tx.ExtendLock(name, whoami, desiredLockDuration)
|
|
if err != nil {
|
|
return false, time.Time{}
|
|
}
|
|
|
|
if locked {
|
|
if err := tx.Commit(); err == nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return false, time.Time{}
|
|
}
|
|
|
|
// ReleaseLock releases a named global lock.
|
|
func ReleaseLock(datastore Datastore, name, owner string) {
|
|
tx, err := datastore.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := tx.ReleaseLock(name, owner); err != nil {
|
|
return
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// PersistDetectorsAndCommit stores the detectors in the data store.
|
|
func PersistDetectorsAndCommit(store Datastore, detectors []Detector) error {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
if err := tx.PersistDetectors(detectors); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MarkNotificationAsReadAndCommit marks a notification as read.
|
|
func MarkNotificationAsReadAndCommit(store Datastore, name string) (bool, error) {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
err = tx.DeleteNotification(name)
|
|
if err == commonerr.ErrNotFound {
|
|
return false, nil
|
|
} else if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// FindAffectedNamespacedFeaturesAndRollback finds the vulnerabilities on each
|
|
// feature.
|
|
func FindAffectedNamespacedFeaturesAndRollback(store Datastore, features []NamespacedFeature) ([]NullableAffectedNamespacedFeature, error) {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
nullableFeatures, err := tx.FindAffectedNamespacedFeatures(features)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nullableFeatures, nil
|
|
}
|
|
|
|
// FindVulnerabilityNotificationAndRollback finds the vulnerability notification
|
|
// and rollback.
|
|
func FindVulnerabilityNotificationAndRollback(store Datastore, name string, limit int, oldVulnerabilityPage pagination.Token, newVulnerabilityPage pagination.Token) (VulnerabilityNotificationWithVulnerable, bool, error) {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return VulnerabilityNotificationWithVulnerable{}, false, err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
return tx.FindVulnerabilityNotification(name, limit, oldVulnerabilityPage, newVulnerabilityPage)
|
|
}
|
|
|
|
// FindNewNotification finds notifications either never notified or notified
|
|
// before the given time.
|
|
func FindNewNotification(store Datastore, notifiedBefore time.Time) (NotificationHook, bool, error) {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return NotificationHook{}, false, err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
return tx.FindNewNotification(notifiedBefore)
|
|
}
|
|
|
|
// UpdateKeyValueAndCommit stores the key value to storage.
|
|
func UpdateKeyValueAndCommit(store Datastore, key, value string) error {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
if err = tx.UpdateKeyValue(key, value); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// InsertVulnerabilityNotificationsAndCommit inserts the notifications into db
|
|
// and commit.
|
|
func InsertVulnerabilityNotificationsAndCommit(store Datastore, notifications []VulnerabilityNotification) error {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := tx.InsertVulnerabilityNotifications(notifications); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// FindVulnerabilitiesAndRollback finds the vulnerabilities based on given ids.
|
|
func FindVulnerabilitiesAndRollback(store Datastore, ids []VulnerabilityID) ([]NullableVulnerability, error) {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
return tx.FindVulnerabilities(ids)
|
|
}
|
|
|
|
func UpdateVulnerabilitiesAndCommit(store Datastore, toRemove []VulnerabilityID, toAdd []VulnerabilityWithAffected) error {
|
|
tx, err := store.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.DeleteVulnerabilities(toRemove); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.InsertVulnerabilities(toAdd); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|