5b2376498b
- Add Ancestry builder - Change RPC to use the ancestry builder
146 lines
5.1 KiB
Go
146 lines
5.1 KiB
Go
// Copyright 2019 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 clair
|
|
|
|
import (
|
|
"context"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/coreos/clair/database"
|
|
"github.com/coreos/clair/ext/featurefmt"
|
|
"github.com/coreos/clair/ext/featurens"
|
|
"github.com/coreos/clair/ext/imagefmt"
|
|
)
|
|
|
|
// AnalyzeError represents an failure when analyzing layer or constructing
|
|
// ancestry.
|
|
type AnalyzeError string
|
|
|
|
func (e AnalyzeError) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
var (
|
|
// StorageError represents an analyze error caused by the storage
|
|
StorageError = AnalyzeError("failed to query the database.")
|
|
// RetrieveBlobError represents an analyze error caused by failure of
|
|
// downloading or extracting layer blobs.
|
|
RetrieveBlobError = AnalyzeError("failed to download layer blob.")
|
|
// ExtractBlobError represents an analyzer error caused by failure of
|
|
// extracting a layer blob by imagefmt.
|
|
ExtractBlobError = AnalyzeError("failed to extract files from layer blob.")
|
|
// FeatureDetectorError is an error caused by failure of feature listing by
|
|
// featurefmt.
|
|
FeatureDetectorError = AnalyzeError("failed to scan feature from layer blob files.")
|
|
// NamespaceDetectorError is an error caused by failure of namespace
|
|
// detection by featurens.
|
|
NamespaceDetectorError = AnalyzeError("failed to scan namespace from layer blob files.")
|
|
)
|
|
|
|
// AnalyzeLayer retrieves the clair layer with all extracted features and namespaces.
|
|
// If a layer is already scanned by all enabled detectors in the Clair instance, it returns directly.
|
|
// Otherwise, it re-download the layer blob and scan the features and namespaced again.
|
|
func AnalyzeLayer(ctx context.Context, store database.Datastore, blobSha256 string, blobFormat string, downloadURI string, downloadHeaders map[string]string) (*database.Layer, error) {
|
|
layer, found, err := database.FindLayerAndRollback(store, blobSha256)
|
|
logFields := log.Fields{"layer.Hash": blobSha256}
|
|
if err != nil {
|
|
log.WithError(err).WithFields(logFields).Error("failed to find layer in the storage")
|
|
return nil, StorageError
|
|
}
|
|
|
|
var scannedBy []database.Detector
|
|
if found {
|
|
scannedBy = layer.By
|
|
}
|
|
|
|
// layer will be scanned by detectors not scanned the layer already.
|
|
toScan := database.DiffDetectors(EnabledDetectors(), scannedBy)
|
|
if len(toScan) != 0 {
|
|
log.WithFields(logFields).Debug("scan layer blob not already scanned")
|
|
newLayerScanResult := &database.Layer{Hash: blobSha256, By: toScan}
|
|
blob, err := retrieveLayerBlob(ctx, downloadURI, downloadHeaders)
|
|
if err != nil {
|
|
log.WithError(err).WithFields(logFields).Error("failed to retrieve layer blob")
|
|
return nil, RetrieveBlobError
|
|
}
|
|
|
|
defer func() {
|
|
if err := blob.Close(); err != nil {
|
|
log.WithFields(logFields).Error("failed to close layer blob reader")
|
|
}
|
|
}()
|
|
|
|
files := append(featurefmt.RequiredFilenames(toScan), featurens.RequiredFilenames(toScan)...)
|
|
fileMap, err := imagefmt.Extract(blobFormat, blob, files)
|
|
if err != nil {
|
|
log.WithFields(logFields).WithError(err).Error("failed to extract layer blob")
|
|
return nil, ExtractBlobError
|
|
}
|
|
|
|
newLayerScanResult.Features, err = featurefmt.ListFeatures(fileMap, toScan)
|
|
if err != nil {
|
|
log.WithFields(logFields).WithError(err).Error("failed to detect features")
|
|
return nil, FeatureDetectorError
|
|
}
|
|
|
|
newLayerScanResult.Namespaces, err = featurens.Detect(fileMap, toScan)
|
|
if err != nil {
|
|
log.WithFields(logFields).WithError(err).Error("failed to detect namespaces")
|
|
return nil, NamespaceDetectorError
|
|
}
|
|
|
|
if err = saveLayerChange(store, newLayerScanResult); err != nil {
|
|
log.WithFields(logFields).WithError(err).Error("failed to store layer change")
|
|
return nil, StorageError
|
|
}
|
|
|
|
layer = database.MergeLayers(layer, newLayerScanResult)
|
|
} else {
|
|
log.WithFields(logFields).Debug("found scanned layer blob")
|
|
}
|
|
|
|
return layer, nil
|
|
}
|
|
|
|
// EnabledDetectors retrieves a list of all detectors installed in the Clair
|
|
// instance.
|
|
func EnabledDetectors() []database.Detector {
|
|
return append(featurefmt.ListListers(), featurens.ListDetectors()...)
|
|
}
|
|
|
|
// RegisterConfiguredDetectors populates the database with registered detectors.
|
|
func RegisterConfiguredDetectors(store database.Datastore) {
|
|
if err := database.PersistDetectorsAndCommit(store, EnabledDetectors()); err != nil {
|
|
panic("failed to initialize Clair analyzer")
|
|
}
|
|
}
|
|
|
|
func saveLayerChange(store database.Datastore, layer *database.Layer) error {
|
|
if err := database.PersistFeaturesAndCommit(store, layer.GetFeatures()); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := database.PersistNamespacesAndCommit(store, layer.GetNamespaces()); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := database.PersistPartialLayerAndCommit(store, layer); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|