Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Ancestry Layer-wise feature API #605

Merged
merged 3 commits into from
Sep 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 121 additions & 107 deletions api/v3/clairpb/clair.pb.go

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions api/v3/clairpb/clair.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 10 additions & 6 deletions api/v3/clairpb/clair.proto
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,21 @@ message GetAncestryRequest {
}

message GetAncestryResponse {
message AncestryLayer {
// The layer's information.
Layer layer = 1;
// The features detected in this layer.
repeated Feature detected_features = 2;
}
message Ancestry {
// The name of the desired ancestry.
string name = 1;
// The list of features present in the ancestry.
// This will only be provided if requested.
repeated Feature features = 2;
// The layers present in the ancestry.
repeated Layer layers = 3;
// The configured list of feature listers used to scan this ancestry.
repeated string scanned_listers = 4;
// The configured list of namespace detectors used to scan an ancestry.
repeated string scanned_detectors = 5;
// The list of layers along with detected features in each.
repeated AncestryLayer layers = 6;
}
// The ancestry requested.
Ancestry ancestry = 1;
Expand All @@ -128,7 +131,8 @@ message PostAncestryRequest {
string ancestry_name = 1;
// The format of the image being uploaded.
string format = 2;
// The layers to be scanned for this particular ancestry.
// The layers to be scanned for this Ancestry, ordered in the way that i th
// layer is the parent of i + 1 th layer.
repeated PostLayer layers = 3;
}

Expand Down
43 changes: 22 additions & 21 deletions api/v3/clairpb/clair.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,6 @@
}
}
},
"parameters": [
{
"name": "test",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"StatusService"
]
Expand All @@ -187,33 +179,42 @@
"type": "string",
"description": "The name of the desired ancestry."
},
"features": {
"scanned_listers": {
"type": "array",
"items": {
"$ref": "#/definitions/clairFeature"
"type": "string"
},
"description": "The list of features present in the ancestry.\nThis will only be provided if requested."
"description": "The configured list of feature listers used to scan this ancestry."
},
"layers": {
"scanned_detectors": {
"type": "array",
"items": {
"$ref": "#/definitions/clairLayer"
"type": "string"
},
"description": "The layers present in the ancestry."
"description": "The configured list of namespace detectors used to scan an ancestry."
},
"scanned_listers": {
"layers": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/GetAncestryResponseAncestryLayer"
},
"description": "The configured list of feature listers used to scan this ancestry."
"description": "The list of layers along with detected features in each."
}
}
},
"GetAncestryResponseAncestryLayer": {
"type": "object",
"properties": {
"layer": {
"$ref": "#/definitions/clairLayer",
"description": "The layer's information."
},
"scanned_detectors": {
"detected_features": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/clairFeature"
},
"description": "The configured list of namespace detectors used to scan an ancestry."
"description": "The features detected in this layer."
}
}
},
Expand Down Expand Up @@ -420,7 +421,7 @@
"items": {
"$ref": "#/definitions/PostAncestryRequestPostLayer"
},
"description": "The layers to be scanned for this particular ancestry."
"description": "The layers to be scanned for this Ancestry, ordered in the way that i th\nlayer is the parent of i + 1 th layer."
}
}
},
Expand Down
11 changes: 9 additions & 2 deletions api/v3/clairpb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,18 @@ func VulnerabilityWithFixedInFromDatabaseModel(dbVuln database.VulnerabilityWith
// AncestryFromDatabaseModel converts database ancestry to api ancestry.
func AncestryFromDatabaseModel(dbAncestry database.Ancestry) *GetAncestryResponse_Ancestry {
ancestry := &GetAncestryResponse_Ancestry{
Name: dbAncestry.Name,
Name: dbAncestry.Name,
ScannedDetectors: dbAncestry.ProcessedBy.Detectors,
ScannedListers: dbAncestry.ProcessedBy.Listers,
}

for _, layer := range dbAncestry.Layers {
ancestry.Layers = append(ancestry.Layers, LayerFromDatabaseModel(layer))
ancestry.Layers = append(ancestry.Layers,
&GetAncestryResponse_AncestryLayer{
Layer: LayerFromDatabaseModel(layer),
})
}

return ancestry
}

Expand Down
84 changes: 51 additions & 33 deletions api/v3/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ func (s *AncestryServer) PostAncestry(ctx context.Context, req *pb.PostAncestryR

// GetAncestry implements retrieving an ancestry via the Clair gRPC service.
func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryRequest) (*pb.GetAncestryResponse, error) {
if req.GetAncestryName() == "" {
var (
respAncestry *pb.GetAncestryResponse_Ancestry
name = req.GetAncestryName()
)

if name == "" {
return nil, status.Errorf(codes.InvalidArgument, "ancestry name should not be empty")
}

Expand All @@ -115,54 +120,67 @@ func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryReq
}
defer tx.Rollback()

ancestry, _, ok, err := tx.FindAncestry(req.GetAncestryName())
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
} else if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
}

pbAncestry := pb.AncestryFromDatabaseModel(ancestry)
if req.GetWithFeatures() || req.GetWithVulnerabilities() {
ancestryWFeature, ok, err := tx.FindAncestryFeatures(ancestry.Name)
ancestry, ok, err := tx.FindAncestryWithContent(name)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
}
pbAncestry.ScannedDetectors = ancestryWFeature.ProcessedBy.Detectors
pbAncestry.ScannedListers = ancestryWFeature.ProcessedBy.Listers

if req.GetWithVulnerabilities() {
featureVulnerabilities, err := tx.FindAffectedNamespacedFeatures(ancestryWFeature.Features)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
respAncestry = &pb.GetAncestryResponse_Ancestry{
Name: name,
ScannedDetectors: ancestry.ProcessedBy.Detectors,
ScannedListers: ancestry.ProcessedBy.Listers,
}

for _, layer := range ancestry.Layers {
ancestryLayer := &pb.GetAncestryResponse_AncestryLayer{
Layer: &pb.Layer{
Hash: layer.Hash,
},
}

for _, fv := range featureVulnerabilities {
// Ensure that every feature can be found.
if !fv.Valid {
return nil, status.Error(codes.Internal, "ancestry feature is not found")
if req.GetWithVulnerabilities() {
featureVulnerabilities, err := tx.FindAffectedNamespacedFeatures(layer.DetectedFeatures)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

pbFeature := pb.NamespacedFeatureFromDatabaseModel(fv.NamespacedFeature)
for _, v := range fv.AffectedBy {
pbVuln, err := pb.VulnerabilityWithFixedInFromDatabaseModel(v)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
for _, fv := range featureVulnerabilities {
// Ensure that every feature can be found.
if !fv.Valid {
return nil, status.Error(codes.Internal, "ancestry feature is not found")
}
pbFeature.Vulnerabilities = append(pbFeature.Vulnerabilities, pbVuln)
}

pbAncestry.Features = append(pbAncestry.Features, pbFeature)
}
} else {
for _, f := range ancestryWFeature.Features {
pbAncestry.Features = append(pbAncestry.Features, pb.NamespacedFeatureFromDatabaseModel(f))
feature := pb.NamespacedFeatureFromDatabaseModel(fv.NamespacedFeature)
for _, v := range fv.AffectedBy {
vuln, err := pb.VulnerabilityWithFixedInFromDatabaseModel(v)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
}
ancestryLayer.DetectedFeatures = append(ancestryLayer.DetectedFeatures, feature)
}
} else {
for _, dbFeature := range layer.DetectedFeatures {
ancestryLayer.DetectedFeatures = append(ancestryLayer.DetectedFeatures, pb.NamespacedFeatureFromDatabaseModel(dbFeature))
}
}

respAncestry.Layers = append(respAncestry.Layers, ancestryLayer)
}
} else {
dbAncestry, ok, err := tx.FindAncestry(name)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
} else if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
}
respAncestry = pb.AncestryFromDatabaseModel(dbAncestry)
}

clairStatus, err := GetClairStatus(s.Store)
Expand All @@ -172,7 +190,7 @@ func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryReq

return &pb.GetAncestryResponse{
Status: clairStatus,
Ancestry: pbAncestry,
Ancestry: respAncestry,
}, nil
}

Expand Down
18 changes: 9 additions & 9 deletions database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,18 @@ type Session interface {

// UpsertAncestry inserts or replaces an ancestry and its namespaced
// features and processors used to scan the ancestry.
UpsertAncestry(ancestry Ancestry, features []NamespacedFeature, processedBy Processors) error
UpsertAncestry(AncestryWithContent) error

// FindAncestry retrieves an ancestry with processors used to scan the
// ancestry. If the ancestry is not found, return false.
//
// The ancestry's processors are returned to short cut processing ancestry
// if it has been processed by all processors in the current Clair instance.
FindAncestry(name string) (ancestry Ancestry, processedBy Processors, found bool, err error)
FindAncestry(name string) (ancestry Ancestry, found bool, err error)

// FindAncestryFeatures retrieves an ancestry with all detected namespaced
// features. If the ancestry is not found, return false.
FindAncestryFeatures(name string) (ancestry AncestryWithFeatures, found bool, err error)
// FindAncestryWithContent retrieves an ancestry with all detected
// namespaced features. If the ancestry is not found, return false.
FindAncestryWithContent(name string) (ancestry AncestryWithContent, found bool, err error)

// PersistFeatures inserts a set of features if not in the database.
PersistFeatures(features []Feature) error
Expand All @@ -125,8 +125,8 @@ type Session interface {
// PersistNamespaces inserts a set of namespaces if not in the database.
PersistNamespaces([]Namespace) error

// PersistLayer inserts a layer if not in the datastore.
PersistLayer(Layer) error
// PersistLayer creates a layer using the blob Sum hash.
PersistLayer(hash string) error

// PersistLayerContent persists a layer's content in the database. The given
// namespaces and features can be partial content of this layer.
Expand All @@ -135,8 +135,8 @@ type Session interface {
// in the database.
PersistLayerContent(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error

// FindLayer retrieves a layer and the processors scanned the layer.
FindLayer(hash string) (layer Layer, processedBy Processors, found bool, err error)
// FindLayer retrieves the metadata of a layer.
FindLayer(hash string) (layer Layer, found bool, err error)

// FindLayerWithContent returns a layer with all detected features and
// namespaces.
Expand Down
26 changes: 13 additions & 13 deletions database/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ import "time"
type MockSession struct {
FctCommit func() error
FctRollback func() error
FctUpsertAncestry func(Ancestry, []NamespacedFeature, Processors) error
FctFindAncestry func(name string) (Ancestry, Processors, bool, error)
FctFindAncestryFeatures func(name string) (AncestryWithFeatures, bool, error)
FctUpsertAncestry func(AncestryWithContent) error
FctFindAncestry func(name string) (Ancestry, bool, error)
FctFindAncestryWithContent func(name string) (AncestryWithContent, bool, error)
FctFindAffectedNamespacedFeatures func(features []NamespacedFeature) ([]NullableAffectedNamespacedFeature, error)
FctPersistNamespaces func([]Namespace) error
FctPersistFeatures func([]Feature) error
FctPersistNamespacedFeatures func([]NamespacedFeature) error
FctCacheAffectedNamespacedFeatures func([]NamespacedFeature) error
FctPersistLayer func(Layer) error
FctPersistLayer func(hash string) error
FctPersistLayerContent func(hash string, namespaces []Namespace, features []Feature, processedBy Processors) error
FctFindLayer func(name string) (Layer, Processors, bool, error)
FctFindLayer func(name string) (Layer, bool, error)
FctFindLayerWithContent func(name string) (LayerWithContent, bool, error)
FctInsertVulnerabilities func([]VulnerabilityWithAffected) error
FctFindVulnerabilities func([]VulnerabilityID) ([]NullableVulnerability, error)
Expand Down Expand Up @@ -63,23 +63,23 @@ func (ms *MockSession) Rollback() error {
panic("required mock function not implemented")
}

func (ms *MockSession) UpsertAncestry(ancestry Ancestry, features []NamespacedFeature, processedBy Processors) error {
func (ms *MockSession) UpsertAncestry(ancestry AncestryWithContent) error {
if ms.FctUpsertAncestry != nil {
return ms.FctUpsertAncestry(ancestry, features, processedBy)
return ms.FctUpsertAncestry(ancestry)
}
panic("required mock function not implemented")
}

func (ms *MockSession) FindAncestry(name string) (Ancestry, Processors, bool, error) {
func (ms *MockSession) FindAncestry(name string) (Ancestry, bool, error) {
if ms.FctFindAncestry != nil {
return ms.FctFindAncestry(name)
}
panic("required mock function not implemented")
}

func (ms *MockSession) FindAncestryFeatures(name string) (AncestryWithFeatures, bool, error) {
if ms.FctFindAncestryFeatures != nil {
return ms.FctFindAncestryFeatures(name)
func (ms *MockSession) FindAncestryWithContent(name string) (AncestryWithContent, bool, error) {
if ms.FctFindAncestryWithContent != nil {
return ms.FctFindAncestryWithContent(name)
}
panic("required mock function not implemented")
}
Expand Down Expand Up @@ -119,7 +119,7 @@ func (ms *MockSession) CacheAffectedNamespacedFeatures(namespacedFeatures []Name
panic("required mock function not implemented")
}

func (ms *MockSession) PersistLayer(layer Layer) error {
func (ms *MockSession) PersistLayer(layer string) error {
if ms.FctPersistLayer != nil {
return ms.FctPersistLayer(layer)
}
Expand All @@ -133,7 +133,7 @@ func (ms *MockSession) PersistLayerContent(hash string, namespaces []Namespace,
panic("required mock function not implemented")
}

func (ms *MockSession) FindLayer(name string) (Layer, Processors, bool, error) {
func (ms *MockSession) FindLayer(name string) (Layer, bool, error) {
if ms.FctFindLayer != nil {
return ms.FctFindLayer(name)
}
Expand Down
Loading