clair/api/v3/rpc.go
Sida Chen 48427e9b88 api: Add detectors for RPC
Change the V3 implementation to accommondate the detectors.
2018-10-08 10:42:40 -04:00

222 lines
6.6 KiB
Go

// 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 v3
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/coreos/clair"
pb "github.com/coreos/clair/api/v3/clairpb"
"github.com/coreos/clair/database"
"github.com/coreos/clair/pkg/commonerr"
"github.com/coreos/clair/pkg/pagination"
)
// NotificationServer implements NotificationService interface for serving RPC.
type NotificationServer struct {
Store database.Datastore
}
// AncestryServer implements AncestryService interface for serving RPC.
type AncestryServer struct {
Store database.Datastore
}
// StatusServer implements StatusService interface for serving RPC.
type StatusServer struct {
Store database.Datastore
}
// GetStatus implements getting the current status of Clair via the Clair service.
func (s *StatusServer) GetStatus(ctx context.Context, req *pb.GetStatusRequest) (*pb.GetStatusResponse, error) {
clairStatus, err := GetClairStatus(s.Store)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.GetStatusResponse{Status: clairStatus}, nil
}
// PostAncestry implements posting an ancestry via the Clair gRPC service.
func (s *AncestryServer) PostAncestry(ctx context.Context, req *pb.PostAncestryRequest) (*pb.PostAncestryResponse, error) {
ancestryName := req.GetAncestryName()
if ancestryName == "" {
return nil, status.Error(codes.InvalidArgument, "ancestry name should not be empty")
}
layers := req.GetLayers()
if len(layers) == 0 {
return nil, status.Error(codes.InvalidArgument, "ancestry should have at least one layer")
}
ancestryFormat := req.GetFormat()
if ancestryFormat == "" {
return nil, status.Error(codes.InvalidArgument, "ancestry format should not be empty")
}
ancestryLayers := []clair.LayerRequest{}
for _, layer := range layers {
if layer == nil {
err := status.Error(codes.InvalidArgument, "ancestry layer is invalid")
return nil, err
}
if layer.GetHash() == "" {
return nil, status.Error(codes.InvalidArgument, "ancestry layer hash should not be empty")
}
if layer.GetPath() == "" {
return nil, status.Error(codes.InvalidArgument, "ancestry layer path should not be empty")
}
ancestryLayers = append(ancestryLayers, clair.LayerRequest{
Hash: layer.Hash,
Headers: layer.Headers,
Path: layer.Path,
})
}
err := clair.ProcessAncestry(s.Store, ancestryFormat, ancestryName, ancestryLayers)
if err != nil {
return nil, status.Error(codes.Internal, "ancestry is failed to be processed: "+err.Error())
}
clairStatus, err := GetClairStatus(s.Store)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.PostAncestryResponse{Status: clairStatus}, nil
}
// GetAncestry implements retrieving an ancestry via the Clair gRPC service.
func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryRequest) (*pb.GetAncestryResponse, error) {
name := req.GetAncestryName()
if name == "" {
return nil, status.Errorf(codes.InvalidArgument, "ancestry name should not be empty")
}
tx, err := s.Store.Begin()
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
defer tx.Rollback()
ancestry, ok, err := tx.FindAncestry(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 := &pb.GetAncestryResponse_Ancestry{
Name: ancestry.Name,
Detectors: pb.DetectorsFromDatabaseModel(ancestry.By),
}
for _, layer := range ancestry.Layers {
pbLayer, err := GetPbAncestryLayer(tx, layer)
if err != nil {
return nil, err
}
pbAncestry.Layers = append(pbAncestry.Layers, pbLayer)
}
pbClairStatus, err := GetClairStatus(s.Store)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.GetAncestryResponse{
Status: pbClairStatus,
Ancestry: pbAncestry,
}, nil
}
// GetNotification implements retrieving a notification via the Clair gRPC
// service.
func (s *NotificationServer) GetNotification(ctx context.Context, req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) {
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "notification name should not be empty")
}
if req.GetLimit() <= 0 {
return nil, status.Error(codes.InvalidArgument, "notification page limit should not be empty or less than 1")
}
tx, err := s.Store.Begin()
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
defer tx.Rollback()
dbNotification, ok, err := tx.FindVulnerabilityNotification(
req.GetName(),
int(req.GetLimit()),
pagination.Token(req.GetOldVulnerabilityPage()),
pagination.Token(req.GetNewVulnerabilityPage()),
)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if !ok {
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested notification '%s' is not found", req.GetName()))
}
notification, err := pb.NotificationFromDatabaseModel(dbNotification)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.GetNotificationResponse{Notification: notification}, nil
}
// MarkNotificationAsRead implements deleting a notification via the Clair gRPC
// service.
func (s *NotificationServer) MarkNotificationAsRead(ctx context.Context, req *pb.MarkNotificationAsReadRequest) (*pb.MarkNotificationAsReadResponse, error) {
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "notification name should not be empty")
}
tx, err := s.Store.Begin()
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
defer tx.Rollback()
err = tx.DeleteNotification(req.GetName())
if err == commonerr.ErrNotFound {
return nil, status.Error(codes.NotFound, "requested notification \""+req.GetName()+"\" is not found")
} else if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if err := tx.Commit(); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.MarkNotificationAsReadResponse{}, nil
}