2015-11-13 19:11:28 +00:00
|
|
|
// Copyright 2015 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 implements every database models and the functions that
|
|
|
|
// manipulate them.
|
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/barakmich/glog"
|
|
|
|
"github.com/coreos/clair/health"
|
|
|
|
"github.com/coreos/clair/utils"
|
2015-11-16 21:22:16 +00:00
|
|
|
"github.com/coreos/pkg/capnslog"
|
2015-11-13 19:11:28 +00:00
|
|
|
"github.com/google/cayley"
|
|
|
|
"github.com/google/cayley/graph"
|
|
|
|
"github.com/google/cayley/graph/path"
|
|
|
|
|
|
|
|
// Load all supported backends.
|
|
|
|
_ "github.com/google/cayley/graph/bolt"
|
|
|
|
_ "github.com/google/cayley/graph/leveldb"
|
|
|
|
_ "github.com/google/cayley/graph/memstore"
|
|
|
|
_ "github.com/google/cayley/graph/mongo"
|
|
|
|
_ "github.com/google/cayley/graph/sql"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// FieldIs is the graph predicate defining the type of an entity.
|
|
|
|
FieldIs = "is"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
log = capnslog.NewPackageLogger("github.com/coreos/clair", "database")
|
|
|
|
|
|
|
|
// ErrTransaction is an error that occurs when a database transaction fails.
|
|
|
|
ErrTransaction = errors.New("database: transaction failed (concurrent modification?)")
|
|
|
|
// ErrBackendException is an error that occurs when the database backend does
|
|
|
|
// not work properly (ie. unreachable).
|
|
|
|
ErrBackendException = errors.New("database: could not query backend")
|
|
|
|
// ErrInconsistent is an error that occurs when a database consistency check
|
|
|
|
// fails (ie. when an entity which is supposed to be unique is detected twice)
|
|
|
|
ErrInconsistent = errors.New("database: inconsistent database")
|
|
|
|
// ErrCantOpen is an error that occurs when the database could not be opened
|
|
|
|
ErrCantOpen = errors.New("database: could not open database")
|
|
|
|
|
|
|
|
store *cayley.Handle
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
health.RegisterHealthchecker("database", Healthcheck)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open opens a Cayley database, creating it if necessary and return its handle
|
|
|
|
func Open(dbType, dbPath string) error {
|
|
|
|
if store != nil {
|
|
|
|
log.Errorf("could not open database at %s : a database is already opened", dbPath)
|
|
|
|
return ErrCantOpen
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
2015-11-16 21:22:16 +00:00
|
|
|
options := make(graph.Options)
|
2015-11-13 19:11:28 +00:00
|
|
|
|
2015-11-16 21:22:16 +00:00
|
|
|
switch dbType {
|
|
|
|
case "bolt", "leveldb":
|
2015-11-13 19:11:28 +00:00
|
|
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
|
|
|
log.Infof("database at %s does not exist yet, creating it", dbPath)
|
|
|
|
|
2015-11-16 21:22:16 +00:00
|
|
|
err = graph.InitQuadStore(dbType, dbPath, options)
|
|
|
|
if err != nil {
|
2015-11-13 19:11:28 +00:00
|
|
|
log.Errorf("could not create database at %s : %s", dbPath, err)
|
|
|
|
return ErrCantOpen
|
|
|
|
}
|
|
|
|
}
|
2015-11-16 21:22:16 +00:00
|
|
|
case "sql":
|
|
|
|
// Replaces the PostgreSQL's slow COUNT query with a fast estimator.
|
|
|
|
// See:
|
|
|
|
// Ref: https://wiki.postgresql.org/wiki/Count_estimate
|
|
|
|
options["use_estimates"] = true
|
|
|
|
|
|
|
|
graph.InitQuadStore(dbType, dbPath, options)
|
2015-11-13 19:11:28 +00:00
|
|
|
}
|
|
|
|
|
2015-11-16 21:22:16 +00:00
|
|
|
store, err = cayley.NewGraph(dbType, dbPath, options)
|
2015-11-13 19:11:28 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("could not open database at %s : %s", dbPath, err)
|
|
|
|
return ErrCantOpen
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes a Cayley database
|
|
|
|
func Close() {
|
|
|
|
if store != nil {
|
|
|
|
store.Close()
|
|
|
|
store = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Healthcheck simply adds and then remove a quad in Cayley to ensure it is working
|
|
|
|
// It returns true when everything is ok
|
|
|
|
func Healthcheck() health.Status {
|
|
|
|
var err error
|
|
|
|
if store != nil {
|
|
|
|
t := cayley.NewTransaction()
|
|
|
|
q := cayley.Quad("cayley", "is", "healthy", "")
|
|
|
|
t.AddQuad(q)
|
|
|
|
t.RemoveQuad(q)
|
|
|
|
glog.SetStderrThreshold("FATAL") // TODO REMOVE ME
|
|
|
|
err = store.ApplyTransaction(t)
|
|
|
|
glog.SetStderrThreshold("ERROR") // TODO REMOVE ME
|
|
|
|
}
|
|
|
|
|
|
|
|
return health.Status{IsEssential: true, IsHealthy: err == nil, Details: nil}
|
|
|
|
}
|
|
|
|
|
|
|
|
// toValue returns a single value from a path
|
|
|
|
// If the path does not lead to a value, an empty string is returned
|
|
|
|
// If the path leads to multiple values or if a database error occurs, an empty string and an error are returned
|
|
|
|
func toValue(p *path.Path) (string, error) {
|
|
|
|
var value string
|
|
|
|
|
|
|
|
it, _ := p.BuildIterator().Optimize()
|
|
|
|
defer it.Close()
|
|
|
|
for cayley.RawNext(it) {
|
|
|
|
if value != "" {
|
|
|
|
log.Error("failed query in toValue: used on an iterator containing multiple values")
|
|
|
|
return "", ErrInconsistent
|
|
|
|
}
|
|
|
|
|
|
|
|
if it.Result() != nil {
|
|
|
|
value = store.NameOf(it.Result())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if it.Err() != nil {
|
|
|
|
log.Errorf("failed query in toValue: %s", it.Err())
|
|
|
|
return "", ErrBackendException
|
|
|
|
}
|
|
|
|
|
|
|
|
return value, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// toValues returns multiple values from a path
|
|
|
|
// If the path does not lead to any value, an empty array is returned
|
|
|
|
// If a database error occurs, an empty array and an error are returned
|
|
|
|
func toValues(p *path.Path) ([]string, error) {
|
|
|
|
var values []string
|
|
|
|
|
|
|
|
it, _ := p.BuildIterator().Optimize()
|
|
|
|
defer it.Close()
|
|
|
|
for cayley.RawNext(it) {
|
|
|
|
if it.Result() != nil {
|
|
|
|
value := store.NameOf(it.Result())
|
|
|
|
if value != "" {
|
|
|
|
values = append(values, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if it.Err() != nil {
|
|
|
|
log.Errorf("failed query in toValues: %s", it.Err())
|
|
|
|
return []string{}, ErrBackendException
|
|
|
|
}
|
|
|
|
|
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// saveFields appends cayley's Save method to a path for each field in
|
|
|
|
// selectedFields, except the ones that appears also in exceptFields
|
|
|
|
func saveFields(p *path.Path, selectedFields []string, exceptFields []string) {
|
|
|
|
for _, selectedField := range selectedFields {
|
|
|
|
if utils.Contains(selectedField, exceptFields) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
p = p.Save(selectedField, selectedField)
|
|
|
|
}
|
|
|
|
}
|