// Copyright 2017 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 defines the Clair's models and a common interface for // database implementations. package database import ( "errors" "fmt" "time" "github.com/coreos/clair/pkg/pagination" ) var ( // ErrBackendException is an error that occurs when the database backend // does not work properly (ie. unreachable). ErrBackendException = errors.New("database: an error occurred when querying the backend") // ErrInconsistent is an error that occurs when a database consistency check // fails (i.e. when an entity which is supposed to be unique is detected // twice) ErrInconsistent = errors.New("database: inconsistent database") // ErrInvalidParameters is an error that occurs when the parameters are not valid. ErrInvalidParameters = errors.New("database: parameters are not valid") // ErrMissingEntities is an error that occurs when an associated immutable // entity doesn't exist in the database. This error can indicate a wrong // implementation or corrupted database. ErrMissingEntities = errors.New("database: associated immutable entities are missing in the database") ) // RegistrableComponentConfig is a configuration block that can be used to // determine which registrable component should be initialized and pass custom // configuration to it. type RegistrableComponentConfig struct { Type string Options map[string]interface{} } var drivers = make(map[string]Driver) // Driver is a function that opens a Datastore specified by its database driver // type and specific configuration. type Driver func(RegistrableComponentConfig) (Datastore, error) // Register makes a Constructor available by the provided name. // // If this function is called twice with the same name or if the Constructor is // nil, it panics. func Register(name string, driver Driver) { if driver == nil { panic("database: could not register nil Driver") } if _, dup := drivers[name]; dup { panic("database: could not register duplicate Driver: " + name) } drivers[name] = driver } // Open opens a Datastore specified by a configuration. func Open(cfg RegistrableComponentConfig) (Datastore, error) { driver, ok := drivers[cfg.Type] if !ok { return nil, fmt.Errorf("database: unknown Driver %q (forgotten configuration or import?)", cfg.Type) } return driver(cfg) } // Session contains the required operations on a persistent data store for a // Clair deployment. // // Session is started by Datastore.Begin and terminated with Commit or Rollback. // Besides Commit and Rollback, other functions cannot be called after the // session is terminated. // Any function is not guaranteed to be called successfully if there's a session // failure. type Session interface { // Commit commits changes to datastore. // // Commit call after Rollback does no-op. Commit() error // Rollback drops changes to datastore. // // Rollback call after Commit does no-op. Rollback() error // UpsertAncestry inserts or replaces an ancestry and its namespaced // features and processors used to scan the ancestry. UpsertAncestry(Ancestry) error // FindAncestry retrieves an ancestry with all detected // namespaced features. If the ancestry is not found, return false. FindAncestry(name string) (ancestry Ancestry, found bool, err error) // PersistDetector inserts a slice of detectors if not in the database. PersistDetectors(detectors []Detector) error // PersistFeatures inserts a set of features if not in the database. PersistFeatures(features []Feature) error // PersistNamespacedFeatures inserts a set of namespaced features if not in // the database. PersistNamespacedFeatures([]NamespacedFeature) error // CacheAffectedNamespacedFeatures relates the namespaced features with the // vulnerabilities affecting these features. // // NOTE(Sida): it's not necessary for every database implementation and so // this function may have a better home. CacheAffectedNamespacedFeatures([]NamespacedFeature) error // FindAffectedNamespacedFeatures retrieves a set of namespaced features // with affecting vulnerabilities. FindAffectedNamespacedFeatures(features []NamespacedFeature) ([]NullableAffectedNamespacedFeature, error) // PersistNamespaces inserts a set of namespaces if not in the database. PersistNamespaces([]Namespace) error // PersistLayer appends a layer's content in the database. // // If any feature, namespace, or detector is not in the database, it returns not found error. PersistLayer(hash string, features []LayerFeature, namespaces []LayerNamespace, detectedBy []Detector) error // FindLayer returns a layer with all detected features and // namespaces. FindLayer(hash string) (layer Layer, found bool, err error) // InsertVulnerabilities inserts a set of UNIQUE vulnerabilities with // affected features into database, assuming that all vulnerabilities // provided are NOT in database and all vulnerabilities' namespaces are // already in the database. InsertVulnerabilities([]VulnerabilityWithAffected) error // FindVulnerability retrieves a set of Vulnerabilities with affected // features. FindVulnerabilities([]VulnerabilityID) ([]NullableVulnerability, error) // DeleteVulnerability removes a set of Vulnerabilities assuming that the // requested vulnerabilities are in the database. DeleteVulnerabilities([]VulnerabilityID) error // InsertVulnerabilityNotifications inserts a set of unique vulnerability // notifications into datastore, assuming that they are not in the database. InsertVulnerabilityNotifications([]VulnerabilityNotification) error // FindNewNotification retrieves a notification, which has never been // notified or notified before a certain time. FindNewNotification(notifiedBefore time.Time) (hook NotificationHook, found bool, err error) // FindVulnerabilityNotification retrieves a vulnerability notification with // affected ancestries affected by old or new vulnerability. // // Because the number of affected ancestries maybe large, they are paginated // and their pages are specified by the pagination token, which should be // considered first page when it's empty. FindVulnerabilityNotification(name string, limit int, oldVulnerabilityPage pagination.Token, newVulnerabilityPage pagination.Token) (noti VulnerabilityNotificationWithVulnerable, found bool, err error) // MarkNotificationAsRead marks a Notification as notified now, assuming // the requested notification is in the database. MarkNotificationAsRead(name string) error // DeleteNotification removes a Notification in the database. DeleteNotification(name string) error // UpdateKeyValue stores or updates a simple key/value pair. UpdateKeyValue(key, value string) error // FindKeyValue retrieves a value from the given key. FindKeyValue(key string) (value string, found bool, err error) // Lock creates or renew a Lock in the database with the given name, owner // and duration. // // After the specified duration, the Lock expires by itself if it hasn't been // unlocked, and thus, let other users create a Lock with the same name. // However, the owner can renew its Lock by setting renew to true. // Lock should not block, it should instead returns whether the Lock has been // successfully acquired/renewed. If it's the case, the expiration time of // that Lock is returned as well. Lock(name string, owner string, duration time.Duration, renew bool) (success bool, expiration time.Time, err error) // Unlock releases an existing Lock. Unlock(name, owner string) error // FindLock returns the owner of a Lock specified by the name, and its // expiration time if it exists. FindLock(name string) (owner string, expiration time.Time, found bool, err error) } // Datastore represents a persistent data store type Datastore interface { // Begin starts a session to change. Begin() (Session, error) // Ping returns the health status of the database. Ping() bool // Close closes the database and frees any allocated resource. Close() }