mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-16 20:01:35 +01:00
feat(scan): support to scan image index (#11001)
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
6a25e6b2c6
commit
12f16c8cec
@ -10,6 +10,7 @@ schemes:
|
||||
basePath: /api/v2.0
|
||||
produces:
|
||||
- application/json
|
||||
- text/plain
|
||||
consumes:
|
||||
- application/json
|
||||
securityDefinitions:
|
||||
@ -333,7 +334,7 @@ paths:
|
||||
summary: Scan the artifact
|
||||
description: Scan the specified artifact
|
||||
tags:
|
||||
- artifact
|
||||
- scan
|
||||
operationId: scanArtifact
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
@ -351,6 +352,38 @@ paths:
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/{report_id}/log:
|
||||
get:
|
||||
summary: Get the log of the scan report
|
||||
description: Get the log of the scan report
|
||||
tags:
|
||||
- scan
|
||||
operationId: getReportLog
|
||||
produces:
|
||||
- text/plain
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/reference'
|
||||
- name: report_id
|
||||
type: string
|
||||
in: path
|
||||
required: true
|
||||
description: The report id to get the log
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully get scan log file
|
||||
schema:
|
||||
type: string
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/tags:
|
||||
post:
|
||||
summary: Create tag
|
||||
@ -989,6 +1022,10 @@ definitions:
|
||||
format: date-time
|
||||
description: 'The end time of the scan process that generating report'
|
||||
example: '2006-01-02T15:04:05'
|
||||
complete_percent:
|
||||
type: integer
|
||||
description: 'The complete percent of the scanning which value is between 0 and 100'
|
||||
example: 100
|
||||
VulnerabilitySummary:
|
||||
type: object
|
||||
description: |
|
||||
|
@ -22,7 +22,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -36,18 +35,9 @@ func HandleCheckIn(ctx context.Context, checkIn string) {
|
||||
batchSize := 50
|
||||
for repo := range fetchRepositories(ctx, batchSize) {
|
||||
for artifact := range fetchArtifacts(ctx, repo.RepositoryID, batchSize) {
|
||||
for _, tag := range artifact.Tags {
|
||||
art := &v1.Artifact{
|
||||
NamespaceID: artifact.ProjectID,
|
||||
Repository: repo.Name,
|
||||
Tag: tag.Name,
|
||||
Digest: artifact.Digest,
|
||||
MimeType: artifact.ManifestMediaType,
|
||||
}
|
||||
if err := DefaultController.Scan(art, WithRequester(checkIn)); err != nil {
|
||||
// Just logged
|
||||
log.Error(errors.Wrap(err, "handle check in"))
|
||||
}
|
||||
if err := DefaultController.Scan(ctx, artifact, WithRequester(checkIn)); err != nil {
|
||||
// Just logged
|
||||
log.Error(errors.Wrap(err, "handle check in"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,7 +57,7 @@ func fetchArtifacts(ctx context.Context, repositoryID int64, chunkSize int) <-ch
|
||||
}
|
||||
|
||||
for {
|
||||
artifacts, err := artifact.Ctl.List(ctx, query, &artifact.Option{WithTag: true})
|
||||
artifacts, err := artifact.Ctl.List(ctx, query, nil)
|
||||
if err != nil {
|
||||
log.Errorf("[scan all]: list artifacts failed, error: %v", err)
|
||||
return
|
||||
|
@ -15,9 +15,12 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
ar "github.com/goharbor/harbor/src/api/artifact"
|
||||
sc "github.com/goharbor/harbor/src/api/scanner"
|
||||
cj "github.com/goharbor/harbor/src/common/job"
|
||||
jm "github.com/goharbor/harbor/src/common/job/models"
|
||||
@ -62,6 +65,8 @@ type jcGetter func() cj.Client
|
||||
type basicController struct {
|
||||
// Manage the scan report records
|
||||
manager report.Manager
|
||||
// Artifact controller
|
||||
ar ar.Controller
|
||||
// Scanner controller
|
||||
sc sc.Controller
|
||||
// Robot account controller
|
||||
@ -79,6 +84,8 @@ func NewController() Controller {
|
||||
return &basicController{
|
||||
// New report manager
|
||||
manager: report.NewManager(),
|
||||
// Refer to the default artifact controller
|
||||
ar: ar.Ctl,
|
||||
// Refer to the default scanner controller
|
||||
sc: sc.DefaultController,
|
||||
// Refer to the default robot account controller
|
||||
@ -110,26 +117,57 @@ func NewController() Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Collect artifacts itself or its children (exclude child which is image index and not supported by the scanner) when the artifact is scannable.
|
||||
// Report placeholders will be created to track when scan the artifact.
|
||||
// The reports of these artifacts will make together when get the reports of the artifact.
|
||||
// There are two scenarios when artifact is scannable:
|
||||
// 1. The scanner has capability for the artifact directly, eg the artifact is docker image.
|
||||
// 2. The artifact is image index and the scanner has capability for any artifact which is referenced by the artifact.
|
||||
func (bc *basicController) collectScanningArtifacts(ctx context.Context, r *scanner.Registration, artifact *ar.Artifact) ([]*ar.Artifact, bool, error) {
|
||||
var (
|
||||
scannable bool
|
||||
artifacts []*ar.Artifact
|
||||
)
|
||||
|
||||
walkFn := func(a *ar.Artifact) error {
|
||||
hasCapability := HasCapability(r, a)
|
||||
|
||||
if !hasCapability && a.HasChildren() {
|
||||
// image index not supported by the scanner, so continue to walk its children
|
||||
return nil
|
||||
}
|
||||
|
||||
artifacts = append(artifacts, a)
|
||||
|
||||
if hasCapability {
|
||||
scannable = true
|
||||
return ar.ErrSkip // this artifact supported by the scanner, skip to walk its children
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bc.ar.Walk(ctx, artifact, walkFn, nil); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return artifacts, scannable, nil
|
||||
}
|
||||
|
||||
// Scan ...
|
||||
func (bc *basicController) Scan(artifact *v1.Artifact, options ...Option) error {
|
||||
func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, options ...Option) error {
|
||||
if artifact == nil {
|
||||
return errors.New("nil artifact to scan")
|
||||
}
|
||||
|
||||
// Parse options
|
||||
ops, err := parseOptions(options...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scan controller: scan")
|
||||
}
|
||||
|
||||
r, err := bc.sc.GetRegistrationByProject(artifact.NamespaceID)
|
||||
r, err := bc.sc.GetRegistrationByProject(artifact.ProjectID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scan controller: scan")
|
||||
}
|
||||
|
||||
// In case it does not exist
|
||||
if r == nil {
|
||||
return errs.WithCode(errs.PreconditionFailed, errs.Errorf("no available scanner for project: %d", artifact.NamespaceID))
|
||||
return errs.WithCode(errs.PreconditionFailed, errs.Errorf("no available scanner for project: %d", artifact.ProjectID))
|
||||
}
|
||||
|
||||
// Check if it is disabled
|
||||
@ -137,91 +175,114 @@ func (bc *basicController) Scan(artifact *v1.Artifact, options ...Option) error
|
||||
return errs.WithCode(errs.PreconditionFailed, errs.Errorf("scanner %s is disabled", r.Name))
|
||||
}
|
||||
|
||||
// Check the health of the registration by ping.
|
||||
// The metadata of the scanner adapter is also returned.
|
||||
meta, err := bc.sc.Ping(r)
|
||||
artifacts, scannable, err := bc.collectScanningArtifacts(ctx, r, artifact)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scan controller: scan")
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate a UUID as track ID which groups the report records generated
|
||||
// by the specified registration for the digest with given mime type.
|
||||
if !scannable {
|
||||
return errors.Errorf("the configured scanner %s does not support scanning artifact with mime type %s", r.Name, artifact.ManifestMediaType)
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
Artifact *ar.Artifact
|
||||
TrackID string
|
||||
ProducesMimes []string
|
||||
}
|
||||
|
||||
params := []*Param{}
|
||||
|
||||
var errs []error
|
||||
for _, art := range artifacts {
|
||||
trackID, producesMimes, err := bc.makeReportPlaceholder(ctx, r, art, options...)
|
||||
if err != nil {
|
||||
if ierror.IsConflictErr(err) {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(producesMimes) > 0 {
|
||||
params = append(params, &Param{Artifact: art, TrackID: trackID, ProducesMimes: producesMimes})
|
||||
}
|
||||
}
|
||||
|
||||
// all report placeholder conflicted
|
||||
if len(errs) == len(artifacts) {
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
errs = errs[:0]
|
||||
for _, param := range params {
|
||||
if err := bc.scanArtifact(ctx, r, param.Artifact, param.TrackID, param.ProducesMimes); err != nil {
|
||||
log.Warningf("scan artifact %s@%s failed, error: %v", artifact.RepositoryName, artifact.Digest, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
// all scanning of the artifacts failed
|
||||
if len(errs) == len(params) {
|
||||
return fmt.Errorf("scan artifact %s@%s failed", artifact.RepositoryName, artifact.Digest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner.Registration, art *ar.Artifact, options ...Option) (string, []string, error) {
|
||||
trackID, err := bc.uuid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scan controller: scan")
|
||||
return "", nil, errors.Wrap(err, "scan controller: scan")
|
||||
}
|
||||
|
||||
producesMimes := make([]string, 0)
|
||||
matched := false
|
||||
statusConflict := false
|
||||
for _, ca := range meta.Capabilities {
|
||||
for _, cm := range ca.ConsumesMimeTypes {
|
||||
if cm == artifact.MimeType {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
// Parse options
|
||||
ops, err := parseOptions(options...)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "scan controller: scan")
|
||||
}
|
||||
|
||||
create := func(ctx context.Context, digest, registrationUUID, mimeType, trackID string, status job.Status) error {
|
||||
reportPlaceholder := &scan.Report{
|
||||
Digest: digest,
|
||||
RegistrationUUID: registrationUUID,
|
||||
Status: status.String(),
|
||||
StatusCode: status.Code(),
|
||||
TrackID: trackID,
|
||||
MimeType: mimeType,
|
||||
}
|
||||
// Set requester if it is specified
|
||||
if len(ops.Requester) > 0 {
|
||||
reportPlaceholder.Requester = ops.Requester
|
||||
} else {
|
||||
// Use the trackID as the requester
|
||||
reportPlaceholder.Requester = trackID
|
||||
}
|
||||
|
||||
if matched {
|
||||
for _, pm := range ca.ProducesMimeTypes {
|
||||
// Create report placeholder first
|
||||
reportPlaceholder := &scan.Report{
|
||||
Digest: artifact.Digest,
|
||||
RegistrationUUID: r.UUID,
|
||||
Status: job.PendingStatus.String(),
|
||||
StatusCode: job.PendingStatus.Code(),
|
||||
TrackID: trackID,
|
||||
MimeType: pm,
|
||||
}
|
||||
// Set requester if it is specified
|
||||
if len(ops.Requester) > 0 {
|
||||
reportPlaceholder.Requester = ops.Requester
|
||||
} else {
|
||||
// Use the trackID as the requester
|
||||
reportPlaceholder.Requester = trackID
|
||||
}
|
||||
_, e := bc.manager.Create(reportPlaceholder)
|
||||
return e
|
||||
}
|
||||
|
||||
_, e := bc.manager.Create(reportPlaceholder)
|
||||
if e != nil {
|
||||
// Check if it is a status conflict error with common error format.
|
||||
// Common error returned if and only if status conflicts.
|
||||
if !statusConflict {
|
||||
statusConflict = errs.AsError(e, errs.Conflict)
|
||||
}
|
||||
if HasCapability(r, art) {
|
||||
var producesMimes []string
|
||||
|
||||
// Recorded by error wrap and logged at the same time.
|
||||
if err == nil {
|
||||
err = e
|
||||
} else {
|
||||
err = errors.Wrap(e, err.Error())
|
||||
}
|
||||
|
||||
logger.Error(errors.Wrap(e, "scan controller: scan"))
|
||||
continue
|
||||
}
|
||||
|
||||
producesMimes = append(producesMimes, pm)
|
||||
for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType) {
|
||||
if err = create(ctx, art.Digest, r.UUID, pm, trackID, job.PendingStatus); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
break
|
||||
producesMimes = append(producesMimes, pm)
|
||||
}
|
||||
|
||||
if len(producesMimes) > 0 {
|
||||
return trackID, producesMimes, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Scanner does not support scanning the given artifact.
|
||||
if !matched {
|
||||
return errors.Errorf("the configured scanner %s does not support scanning artifact with mime type %s", r.Name, artifact.MimeType)
|
||||
}
|
||||
|
||||
// If all the record are created failed.
|
||||
if len(producesMimes) == 0 {
|
||||
// Return the last error
|
||||
if statusConflict {
|
||||
return errs.WithCode(errs.Conflict, errs.Wrap(err, "scan controller: scan"))
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "scan controller: scan")
|
||||
}
|
||||
err = create(ctx, art.Digest, r.UUID, v1.MimeTypeNativeReport, trackID, job.ErrorStatus)
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
func (bc *basicController) scanArtifact(ctx context.Context, r *scanner.Registration, artifact *ar.Artifact, trackID string, producesMimes []string) error {
|
||||
jobID, err := bc.launchScanJob(trackID, artifact, r, producesMimes)
|
||||
if err != nil {
|
||||
// Update the status to the concrete error
|
||||
@ -243,7 +304,7 @@ func (bc *basicController) Scan(artifact *v1.Artifact, options ...Option) error
|
||||
}
|
||||
|
||||
// GetReport ...
|
||||
func (bc *basicController) GetReport(artifact *v1.Artifact, mimeTypes []string) ([]*scan.Report, error) {
|
||||
func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) ([]*scan.Report, error) {
|
||||
if artifact == nil {
|
||||
return nil, errors.New("no way to get report for nil artifact")
|
||||
}
|
||||
@ -256,26 +317,67 @@ func (bc *basicController) GetReport(artifact *v1.Artifact, mimeTypes []string)
|
||||
}
|
||||
|
||||
// Get current scanner settings
|
||||
r, err := bc.sc.GetRegistrationByProject(artifact.NamespaceID)
|
||||
r, err := bc.sc.GetRegistrationByProject(artifact.ProjectID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scan controller: get report")
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return nil, ierror.NotFoundError(nil).WithMessage("no scanner registration configured for project: %d", artifact.NamespaceID)
|
||||
return nil, ierror.NotFoundError(nil).WithMessage("no scanner registration configured for project: %d", artifact.ProjectID)
|
||||
}
|
||||
|
||||
return bc.manager.GetBy(artifact.Digest, r.UUID, mimes)
|
||||
artifacts, scannable, err := bc.collectScanningArtifacts(ctx, r, artifact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !scannable {
|
||||
return nil, ierror.NotFoundError(nil).WithMessage("report not found for %s@%s", artifact.RepositoryName, artifact.Digest)
|
||||
}
|
||||
|
||||
groupReports := make([][]*scan.Report, len(artifacts))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i, a := range artifacts {
|
||||
wg.Add(1)
|
||||
|
||||
go func(i int, a *ar.Artifact) {
|
||||
defer wg.Done()
|
||||
|
||||
reports, err := bc.manager.GetBy(a.Digest, r.UUID, mimes)
|
||||
if err != nil {
|
||||
log.Warningf("get reports of %s@%s failed, error: %v", a.RepositoryName, a.Digest, err)
|
||||
return
|
||||
}
|
||||
|
||||
groupReports[i] = reports
|
||||
}(i, a)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var reports []*scan.Report
|
||||
for _, group := range groupReports {
|
||||
if len(group) != 0 {
|
||||
reports = append(reports, group...)
|
||||
} else {
|
||||
// NOTE: If the artifact is OCI image, this happened when the artifact is not scanned.
|
||||
// If the artifact is OCI image index, this happened when the artifact is not scanned,
|
||||
// but its children artifacts may scanned so return empty report
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
// GetSummary ...
|
||||
func (bc *basicController) GetSummary(artifact *v1.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
if artifact == nil {
|
||||
return nil, errors.New("no way to get report summaries for nil artifact")
|
||||
}
|
||||
|
||||
// Get reports first
|
||||
rps, err := bc.GetReport(artifact, mimeTypes)
|
||||
rps, err := bc.GetReport(ctx, artifact, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -287,7 +389,16 @@ func (bc *basicController) GetSummary(artifact *v1.Artifact, mimeTypes []string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summaries[rp.MimeType] = sum
|
||||
if s, ok := summaries[rp.MimeType]; ok {
|
||||
r, err := report.MergeSummary(rp.MimeType, s, sum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summaries[rp.MimeType] = r
|
||||
} else {
|
||||
summaries[rp.MimeType] = sum
|
||||
}
|
||||
}
|
||||
|
||||
return summaries, nil
|
||||
@ -423,7 +534,7 @@ func (bc *basicController) makeRobotAccount(projectID int64, repository string)
|
||||
}
|
||||
|
||||
// launchScanJob launches a job to run scan
|
||||
func (bc *basicController) launchScanJob(trackID string, artifact *v1.Artifact, registration *scanner.Registration, mimes []string) (jobID string, err error) {
|
||||
func (bc *basicController) launchScanJob(trackID string, artifact *ar.Artifact, registration *scanner.Registration, mimes []string) (jobID string, err error) {
|
||||
var ck string
|
||||
if registration.UseInternalAddr {
|
||||
ck = configCoreInternalAddr
|
||||
@ -436,7 +547,7 @@ func (bc *basicController) launchScanJob(trackID string, artifact *v1.Artifact,
|
||||
return "", errors.Wrap(err, "scan controller: launch scan job")
|
||||
}
|
||||
|
||||
robot, err := bc.makeRobotAccount(artifact.NamespaceID, artifact.Repository)
|
||||
robot, err := bc.makeRobotAccount(artifact.ProjectID, artifact.RepositoryName)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "scan controller: launch scan job")
|
||||
}
|
||||
@ -450,7 +561,12 @@ func (bc *basicController) launchScanJob(trackID string, artifact *v1.Artifact,
|
||||
URL: registryAddr,
|
||||
Authorization: authorization,
|
||||
},
|
||||
Artifact: artifact,
|
||||
Artifact: &v1.Artifact{
|
||||
NamespaceID: artifact.ProjectID,
|
||||
Repository: artifact.RepositoryName,
|
||||
Digest: artifact.Digest,
|
||||
MimeType: artifact.ManifestMediaType,
|
||||
},
|
||||
}
|
||||
|
||||
rJSON, err := registration.ToJSON()
|
||||
|
@ -15,12 +15,14 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
cj "github.com/goharbor/harbor/src/common/job"
|
||||
cjm "github.com/goharbor/harbor/src/common/job/models"
|
||||
@ -30,12 +32,14 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
sca "github.com/goharbor/harbor/src/pkg/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/api/artifact"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/api/scanner"
|
||||
mocktesting "github.com/goharbor/harbor/src/testing/mock"
|
||||
reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -47,9 +51,11 @@ type ControllerTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
registration *scanner.Registration
|
||||
artifact *v1.Artifact
|
||||
artifact *artifact.Artifact
|
||||
rawReport string
|
||||
c Controller
|
||||
|
||||
ar artifact.Controller
|
||||
c Controller
|
||||
}
|
||||
|
||||
// TestController is the entry point of ControllerTestSuite.
|
||||
@ -59,21 +65,11 @@ func TestController(t *testing.T) {
|
||||
|
||||
// SetupSuite ...
|
||||
func (suite *ControllerTestSuite) SetupSuite() {
|
||||
suite.registration = &scanner.Registration{
|
||||
ID: 1,
|
||||
UUID: "uuid001",
|
||||
Name: "Test-scan-controller",
|
||||
URL: "http://testing.com:3128",
|
||||
IsDefault: true,
|
||||
}
|
||||
|
||||
suite.artifact = &v1.Artifact{
|
||||
NamespaceID: 1,
|
||||
Repository: "scan",
|
||||
Tag: "golang",
|
||||
Digest: "digest-code",
|
||||
MimeType: v1.MimeTypeDockerArtifact,
|
||||
}
|
||||
suite.artifact = &artifact.Artifact{}
|
||||
suite.artifact.ProjectID = 1
|
||||
suite.artifact.RepositoryName = "library/photon"
|
||||
suite.artifact.Digest = "digest-code"
|
||||
suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact
|
||||
|
||||
m := &v1.ScannerAdapterMetadata{
|
||||
Scanner: &v1.Scanner{
|
||||
@ -95,11 +91,20 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
},
|
||||
}
|
||||
|
||||
suite.registration = &scanner.Registration{
|
||||
ID: 1,
|
||||
UUID: "uuid001",
|
||||
Name: "Test-scan-controller",
|
||||
URL: "http://testing.com:3128",
|
||||
IsDefault: true,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
sc := &scannertesting.Controller{}
|
||||
sc.On("GetRegistrationByProject", suite.artifact.NamespaceID).Return(suite.registration, nil)
|
||||
sc.On("GetRegistrationByProject", suite.artifact.ProjectID).Return(suite.registration, nil)
|
||||
sc.On("Ping", suite.registration).Return(m, nil)
|
||||
|
||||
mgr := &MockReportManager{}
|
||||
mgr := &reporttesting.Manager{}
|
||||
mgr.On("Create", &scan.Report{
|
||||
Digest: "digest-code",
|
||||
RegistrationUUID: "uuid001",
|
||||
@ -161,7 +166,7 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
|
||||
rc := &MockRobotController{}
|
||||
|
||||
resource := fmt.Sprintf("/project/%d/repository", suite.artifact.NamespaceID)
|
||||
resource := fmt.Sprintf("/project/%d/repository", suite.artifact.ProjectID)
|
||||
access := []*rbac.Policy{{
|
||||
Resource: rbac.Resource(resource),
|
||||
Action: rbac.ActionScannerPull,
|
||||
@ -171,7 +176,7 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
account := &model.RobotCreate{
|
||||
Name: rname,
|
||||
Description: "for scan",
|
||||
ProjectID: suite.artifact.NamespaceID,
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
Access: access,
|
||||
}
|
||||
rc.On("CreateRobotAccount", account).Return(&model.Robot{
|
||||
@ -179,7 +184,7 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
Name: common.RobotPrefix + rname,
|
||||
Token: "robot-account",
|
||||
Description: "for scan",
|
||||
ProjectID: suite.artifact.NamespaceID,
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
}, nil)
|
||||
|
||||
// Set job parameters
|
||||
@ -188,7 +193,12 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
URL: "https://core.com",
|
||||
Authorization: "Basic " + base64.StdEncoding.EncodeToString([]byte(common.RobotPrefix+"the-uuid-123:robot-account")),
|
||||
},
|
||||
Artifact: suite.artifact,
|
||||
Artifact: &v1.Artifact{
|
||||
NamespaceID: suite.artifact.ProjectID,
|
||||
Digest: suite.artifact.Digest,
|
||||
Repository: suite.artifact.RepositoryName,
|
||||
MimeType: suite.artifact.ManifestMediaType,
|
||||
},
|
||||
}
|
||||
|
||||
rJSON, err := req.ToJSON()
|
||||
@ -215,8 +225,15 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
jc.On("SubmitJob", j).Return("the-job-id", nil)
|
||||
jc.On("GetJobLog", "the-job-id").Return([]byte("job log"), nil)
|
||||
|
||||
suite.ar = &artifacttesting.Controller{}
|
||||
mocktesting.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) {
|
||||
walkFn := args.Get(2).(func(*artifact.Artifact) error)
|
||||
walkFn(suite.artifact)
|
||||
})
|
||||
|
||||
suite.c = &basicController{
|
||||
manager: mgr,
|
||||
ar: suite.ar,
|
||||
sc: sc,
|
||||
jc: func() cj.Client {
|
||||
return jc
|
||||
@ -243,20 +260,20 @@ func (suite *ControllerTestSuite) TearDownSuite() {}
|
||||
|
||||
// TestScanControllerScan ...
|
||||
func (suite *ControllerTestSuite) TestScanControllerScan() {
|
||||
err := suite.c.Scan(suite.artifact)
|
||||
err := suite.c.Scan(context.TODO(), suite.artifact)
|
||||
require.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
// TestScanControllerGetReport ...
|
||||
func (suite *ControllerTestSuite) TestScanControllerGetReport() {
|
||||
rep, err := suite.c.GetReport(suite.artifact, []string{v1.MimeTypeNativeReport})
|
||||
rep, err := suite.c.GetReport(context.TODO(), suite.artifact, []string{v1.MimeTypeNativeReport})
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(rep))
|
||||
}
|
||||
|
||||
// TestScanControllerGetSummary ...
|
||||
func (suite *ControllerTestSuite) TestScanControllerGetSummary() {
|
||||
sum, err := suite.c.GetSummary(suite.artifact, []string{v1.MimeTypeNativeReport})
|
||||
sum, err := suite.c.GetSummary(context.TODO(), suite.artifact, []string{v1.MimeTypeNativeReport})
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 1, len(sum))
|
||||
}
|
||||
@ -298,73 +315,6 @@ func (suite *ControllerTestSuite) TestScanControllerHandleJobHooks() {
|
||||
|
||||
// Mock things
|
||||
|
||||
// MockReportManager ...
|
||||
type MockReportManager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (mrm *MockReportManager) Create(r *scan.Report) (string, error) {
|
||||
args := mrm.Called(r)
|
||||
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
// UpdateScanJobID ...
|
||||
func (mrm *MockReportManager) UpdateScanJobID(trackID string, jobID string) error {
|
||||
args := mrm.Called(trackID, jobID)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (mrm *MockReportManager) UpdateStatus(trackID string, status string, rev int64) error {
|
||||
args := mrm.Called(trackID, status, rev)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (mrm *MockReportManager) UpdateReportData(uuid string, report string, rev int64) error {
|
||||
args := mrm.Called(uuid, report, rev)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (mrm *MockReportManager) GetBy(digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) {
|
||||
args := mrm.Called(digest, registrationUUID, mimeTypes)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).([]*scan.Report), args.Error(1)
|
||||
}
|
||||
|
||||
func (mrm *MockReportManager) Get(uuid string) (*scan.Report, error) {
|
||||
args := mrm.Called(uuid)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*scan.Report), args.Error(1)
|
||||
}
|
||||
|
||||
func (mrm *MockReportManager) DeleteByDigests(digests ...string) error {
|
||||
args := mrm.Called(digests)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (mrm *MockReportManager) GetStats(requester string) (*all.Stats, error) {
|
||||
args := mrm.Called(requester)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*all.Stats), args.Error(1)
|
||||
}
|
||||
|
||||
// MockJobServiceClient ...
|
||||
type MockJobServiceClient struct {
|
||||
mock.Mock
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"github.com/goharbor/harbor/src/api/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
models "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
)
|
||||
|
||||
// Checker checker which can check that the artifact is scannable
|
||||
@ -31,24 +31,28 @@ type Checker interface {
|
||||
// NewChecker returns checker
|
||||
func NewChecker() Checker {
|
||||
return &checker{
|
||||
artifactCtl: artifact.Ctl,
|
||||
scannerCtl: scanner.DefaultController,
|
||||
scannerMetadatas: map[int64]*v1.ScannerAdapterMetadata{},
|
||||
artifactCtl: artifact.Ctl,
|
||||
scannerCtl: scanner.DefaultController,
|
||||
registrations: map[int64]*models.Registration{},
|
||||
}
|
||||
}
|
||||
|
||||
type checker struct {
|
||||
artifactCtl artifact.Controller
|
||||
scannerCtl scanner.Controller
|
||||
scannerMetadatas map[int64]*v1.ScannerAdapterMetadata
|
||||
artifactCtl artifact.Controller
|
||||
scannerCtl scanner.Controller
|
||||
registrations map[int64]*models.Registration
|
||||
}
|
||||
|
||||
func (c *checker) IsScannable(ctx context.Context, art *artifact.Artifact) (bool, error) {
|
||||
// There are two scenarios when artifact is scannable:
|
||||
// 1. The scanner has capability for the artifact directly, eg the artifact is docker image.
|
||||
// 2. The artifact is image index and the scanner has capability for any artifact which is referenced by the artifact.
|
||||
|
||||
projectID := art.ProjectID
|
||||
|
||||
metadata, ok := c.scannerMetadatas[projectID]
|
||||
r, ok := c.registrations[projectID]
|
||||
if !ok {
|
||||
registration, err := c.scannerCtl.GetRegistrationByProject(projectID, scanner.WithPing(false))
|
||||
registration, err := c.scannerCtl.GetRegistrationByProject(projectID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -57,20 +61,15 @@ func (c *checker) IsScannable(ctx context.Context, art *artifact.Artifact) (bool
|
||||
return false, nil
|
||||
}
|
||||
|
||||
md, err := c.scannerCtl.Ping(registration)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
metadata = md
|
||||
c.scannerMetadatas[projectID] = md
|
||||
r = registration
|
||||
c.registrations[projectID] = registration
|
||||
}
|
||||
|
||||
var scannable bool
|
||||
|
||||
walkFn := func(a *artifact.Artifact) error {
|
||||
scannable = metadata.HasCapability(a.ManifestMediaType)
|
||||
if scannable {
|
||||
if HasCapability(r, a) {
|
||||
scannable = true
|
||||
return artifact.ErrBreak
|
||||
}
|
||||
|
||||
@ -83,3 +82,13 @@ func (c *checker) IsScannable(ctx context.Context, art *artifact.Artifact) (bool
|
||||
|
||||
return scannable, nil
|
||||
}
|
||||
|
||||
// HasCapability returns true when scanner has capability for the artifact
|
||||
// See https://github.com/goharbor/pluggable-scanner-spec/issues/2 to get more info
|
||||
func HasCapability(r *models.Registration, a *artifact.Artifact) bool {
|
||||
if a.Type == "CHART" || a.Type == "UNKNOWN" {
|
||||
return false
|
||||
}
|
||||
|
||||
return r.HasCapability(a.ManifestMediaType)
|
||||
}
|
||||
|
@ -36,9 +36,9 @@ func (suite *CheckerTestSuite) new() *checker {
|
||||
scannerCtl := &scannertesting.Controller{}
|
||||
|
||||
return &checker{
|
||||
artifactCtl: artifactCtl,
|
||||
scannerCtl: scannerCtl,
|
||||
scannerMetadatas: map[int64]*v1.ScannerAdapterMetadata{},
|
||||
artifactCtl: artifactCtl,
|
||||
scannerCtl: scannerCtl,
|
||||
registrations: map[int64]*scanner.Registration{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,10 +59,11 @@ func (suite *CheckerTestSuite) TestIsScannable() {
|
||||
|
||||
supportMimeType := "support mime type"
|
||||
|
||||
mock.OnAnything(c.scannerCtl, "GetRegistrationByProject").Return(&scanner.Registration{}, nil)
|
||||
mock.OnAnything(c.scannerCtl, "Ping").Return(&v1.ScannerAdapterMetadata{
|
||||
Capabilities: []*v1.ScannerCapability{
|
||||
{ConsumesMimeTypes: []string{supportMimeType}},
|
||||
mock.OnAnything(c.scannerCtl, "GetRegistrationByProject").Return(&scanner.Registration{
|
||||
Metadata: &v1.ScannerAdapterMetadata{
|
||||
Capabilities: []*v1.ScannerCapability{
|
||||
{ConsumesMimeTypes: []string{supportMimeType}},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
|
@ -15,11 +15,13 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
// Controller provides the related operations for triggering scan.
|
||||
@ -29,12 +31,12 @@ type Controller interface {
|
||||
// Scan the given artifact
|
||||
//
|
||||
// Arguments:
|
||||
// artifact *v1.Artifact : artifact to be scanned
|
||||
// artifact *artifact.Artifact : artifact to be scanned
|
||||
// options ...Option : options for triggering a scan
|
||||
//
|
||||
// Returns:
|
||||
// error : non nil error if any errors occurred
|
||||
Scan(artifact *v1.Artifact, options ...Option) error
|
||||
Scan(ctx context.Context, artifact *artifact.Artifact, options ...Option) error
|
||||
|
||||
// GetReport gets the reports for the given artifact identified by the digest
|
||||
//
|
||||
@ -45,19 +47,19 @@ type Controller interface {
|
||||
// Returns:
|
||||
// []*scan.Report : scan results by different scanner vendors
|
||||
// error : non nil error if any errors occurred
|
||||
GetReport(artifact *v1.Artifact, mimeTypes []string) ([]*scan.Report, error)
|
||||
GetReport(ctx context.Context, artifact *artifact.Artifact, mimeTypes []string) ([]*scan.Report, error)
|
||||
|
||||
// GetSummary gets the summaries of the reports with given types.
|
||||
//
|
||||
// Arguments:
|
||||
// artifact *v1.Artifact : the scanned artifact
|
||||
// artifact *artifact.Artifact : the scanned artifact
|
||||
// mimeTypes []string : the mime types of the reports
|
||||
// options ...report.Option : optional report options, specify if needed
|
||||
//
|
||||
// Returns:
|
||||
// map[string]interface{} : report summaries indexed by mime types
|
||||
// error : non nil error if any errors occurred
|
||||
GetSummary(artifact *v1.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error)
|
||||
GetSummary(ctx context.Context, artifact *artifact.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error)
|
||||
|
||||
// Get the scan log for the specified artifact with the given digest
|
||||
//
|
||||
|
@ -237,6 +237,8 @@ func (bc *basicController) GetRegistrationByProject(projectID int64, options ...
|
||||
registration.Adapter = meta.Scanner.Name
|
||||
registration.Vendor = meta.Scanner.Vendor
|
||||
registration.Version = meta.Scanner.Version
|
||||
|
||||
registration.Metadata = meta
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +251,7 @@ func (bc *basicController) Ping(registration *scanner.Registration) (*v1.Scanner
|
||||
return nil, errors.New("nil registration to ping")
|
||||
}
|
||||
|
||||
client, err := bc.clientPool.Get(registration)
|
||||
client, err := registration.Client(bc.clientPool)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scanner controller: ping")
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
mocktesting "github.com/goharbor/harbor/src/testing/mock"
|
||||
v1testing "github.com/goharbor/harbor/src/testing/pkg/scan/rest/v1"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/pkg/scan/scanner"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -32,7 +35,7 @@ type ControllerTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
c *basicController
|
||||
mMgr *MockScannerManager
|
||||
mMgr *scannertesting.Manager
|
||||
mMeta *MockProMetaManager
|
||||
|
||||
sample *scanner.Registration
|
||||
@ -45,7 +48,7 @@ func TestController(t *testing.T) {
|
||||
|
||||
// SetupSuite prepares env for the controller test suite
|
||||
func (suite *ControllerTestSuite) SetupSuite() {
|
||||
suite.mMgr = new(MockScannerManager)
|
||||
suite.mMgr = &scannertesting.Manager{}
|
||||
suite.mMeta = new(MockProMetaManager)
|
||||
|
||||
m := &v1.ScannerAdapterMetadata{
|
||||
@ -75,11 +78,11 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
URL: "https://sample.scanner.com",
|
||||
}
|
||||
|
||||
mc := &MockClient{}
|
||||
mc := &v1testing.Client{}
|
||||
mc.On("GetMetadata").Return(m, nil)
|
||||
|
||||
mcp := &MockClientPool{}
|
||||
mcp.On("Get", suite.sample).Return(mc, nil)
|
||||
mcp := &v1testing.ClientPool{}
|
||||
mocktesting.OnAnything(mcp, "Get").Return(mc, nil)
|
||||
suite.c = &basicController{
|
||||
manager: suite.mMgr,
|
||||
proMetaMgr: suite.mMeta,
|
||||
@ -242,58 +245,6 @@ func (suite *ControllerTestSuite) TestGetMetadata() {
|
||||
suite.Equal(1, len(meta.Capabilities))
|
||||
}
|
||||
|
||||
// MockScannerManager is mock of the scanner manager
|
||||
type MockScannerManager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (m *MockScannerManager) List(query *q.Query) ([]*scanner.Registration, error) {
|
||||
args := m.Called(query)
|
||||
return args.Get(0).([]*scanner.Registration), args.Error(1)
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (m *MockScannerManager) Create(registration *scanner.Registration) (string, error) {
|
||||
args := m.Called(registration)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (m *MockScannerManager) Get(registrationUUID string) (*scanner.Registration, error) {
|
||||
args := m.Called(registrationUUID)
|
||||
r := args.Get(0)
|
||||
if r == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return r.(*scanner.Registration), args.Error(1)
|
||||
}
|
||||
|
||||
// Update ...
|
||||
func (m *MockScannerManager) Update(registration *scanner.Registration) error {
|
||||
args := m.Called(registration)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (m *MockScannerManager) Delete(registrationUUID string) error {
|
||||
args := m.Called(registrationUUID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// SetAsDefault ...
|
||||
func (m *MockScannerManager) SetAsDefault(registrationUUID string) error {
|
||||
args := m.Called(registrationUUID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// GetDefault ...
|
||||
func (m *MockScannerManager) GetDefault() (*scanner.Registration, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).(*scanner.Registration), args.Error(1)
|
||||
}
|
||||
|
||||
// MockProMetaManager is the mock of the ProjectMetadataManager
|
||||
type MockProMetaManager struct {
|
||||
mock.Mock
|
||||
@ -328,50 +279,3 @@ func (m *MockProMetaManager) List(name, value string) ([]*models.ProjectMetadata
|
||||
args := m.Called(name, value)
|
||||
return args.Get(0).([]*models.ProjectMetadata), args.Error(1)
|
||||
}
|
||||
|
||||
// MockClientPool is defined and referred by other UT cases.
|
||||
type MockClientPool struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get client
|
||||
func (mcp *MockClientPool) Get(r *scanner.Registration) (v1.Client, error) {
|
||||
args := mcp.Called(r)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(v1.Client), args.Error(1)
|
||||
}
|
||||
|
||||
// MockClient is defined and referred in other UT cases.
|
||||
type MockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetMetadata ...
|
||||
func (mc *MockClient) GetMetadata() (*v1.ScannerAdapterMetadata, error) {
|
||||
args := mc.Called()
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*v1.ScannerAdapterMetadata), args.Error(1)
|
||||
}
|
||||
|
||||
// SubmitScan ...
|
||||
func (mc *MockClient) SubmitScan(req *v1.ScanRequest) (*v1.ScanResponse, error) {
|
||||
args := mc.Called(req)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*v1.ScanResponse), args.Error(1)
|
||||
}
|
||||
|
||||
// GetScanReport ...
|
||||
func (mc *MockClient) GetScanReport(scanRequestID, reportMIMEType string) (string, error) {
|
||||
args := mc.Called(scanRequestID, reportMIMEType)
|
||||
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
@ -211,11 +211,6 @@ func init() {
|
||||
beego.Router("/api/projects/:pid([0-9]+)/scanner", proScannerAPI, "get:GetProjectScanner;put:SetProjectScanner")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/scanner/candidates", proScannerAPI, "get:GetProScannerCandidates")
|
||||
|
||||
// Add routes for scan
|
||||
scanAPI := &ScanAPI{}
|
||||
beego.Router("/api/repositories/*/tags/:tag/scan", scanAPI, "post:Scan;get:Report")
|
||||
beego.Router("/api/repositories/*/tags/:tag/scan/:uuid/log", scanAPI, "get:Log")
|
||||
|
||||
if err := quota.Sync(config.GlobalProjectMgr, false); err != nil {
|
||||
log.Fatalf("failed to sync quota from backend: %v", err)
|
||||
}
|
||||
|
@ -1,206 +0,0 @@
|
||||
// Copyright Project Harbor 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 api
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/registry"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/scan"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/errs"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var digestFunc digestGetter = getDigest
|
||||
|
||||
// ScanAPI handles the scan related actions
|
||||
type ScanAPI struct {
|
||||
BaseController
|
||||
|
||||
// Target artifact
|
||||
artifact *v1.Artifact
|
||||
// Project reference
|
||||
pro *models.Project
|
||||
}
|
||||
|
||||
// Prepare sth. for the subsequent actions
|
||||
func (sa *ScanAPI) Prepare() {
|
||||
// Call super prepare method
|
||||
sa.BaseController.Prepare()
|
||||
|
||||
// Parse parameters
|
||||
repoName := sa.GetString(":splat")
|
||||
tag := sa.GetString(":tag")
|
||||
projectName, _ := utils.ParseRepository(repoName)
|
||||
|
||||
pro, err := sa.ProjectMgr.Get(projectName)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scan API: prepare"))
|
||||
return
|
||||
}
|
||||
if pro == nil {
|
||||
sa.SendNotFoundError(errors.Errorf("project %s not found", projectName))
|
||||
return
|
||||
}
|
||||
sa.pro = pro
|
||||
|
||||
// Check authentication
|
||||
if !sa.RequireAuthenticated() {
|
||||
return
|
||||
}
|
||||
|
||||
// Assemble artifact object
|
||||
digest, err := digestFunc(repoName, tag, sa.SecurityCtx.GetUsername())
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scan API: prepare"))
|
||||
return
|
||||
}
|
||||
|
||||
sa.artifact = &v1.Artifact{
|
||||
NamespaceID: pro.ProjectID,
|
||||
Repository: repoName,
|
||||
Tag: tag,
|
||||
Digest: digest,
|
||||
MimeType: v1.MimeTypeDockerArtifact,
|
||||
}
|
||||
|
||||
logger.Debugf("Scan API receives artifact: %#v", sa.artifact)
|
||||
}
|
||||
|
||||
// Scan artifact
|
||||
func (sa *ScanAPI) Scan() {
|
||||
// Check access permissions
|
||||
if !sa.RequireProjectAccess(sa.pro.ProjectID, rbac.ActionCreate, rbac.ResourceScan) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := scan.DefaultController.Scan(sa.artifact); err != nil {
|
||||
e := errors.Wrap(err, "scan API: scan")
|
||||
|
||||
if errs.AsError(err, errs.PreconditionFailed) {
|
||||
sa.SendPreconditionFailedError(e)
|
||||
return
|
||||
}
|
||||
|
||||
if errs.AsError(err, errs.Conflict) {
|
||||
sa.SendConflictError(e)
|
||||
return
|
||||
}
|
||||
|
||||
sa.SendInternalServerError(e)
|
||||
return
|
||||
}
|
||||
|
||||
sa.Ctx.ResponseWriter.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// Report returns the required reports with the given mime types.
|
||||
func (sa *ScanAPI) Report() {
|
||||
// Check access permissions
|
||||
if !sa.RequireProjectAccess(sa.pro.ProjectID, rbac.ActionRead, rbac.ResourceScan) {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract mime types
|
||||
producesMimes := make([]string, 0)
|
||||
if hl, ok := sa.Ctx.Request.Header[v1.HTTPAcceptHeader]; ok && len(hl) > 0 {
|
||||
producesMimes = append(producesMimes, hl...)
|
||||
}
|
||||
|
||||
// Get the reports
|
||||
reports, err := scan.DefaultController.GetReport(sa.artifact, producesMimes)
|
||||
if err != nil {
|
||||
e := errors.Wrap(err, "scan API: get report")
|
||||
|
||||
if errs.AsError(err, errs.PreconditionFailed) {
|
||||
sa.SendPreconditionFailedError(e)
|
||||
return
|
||||
}
|
||||
|
||||
sa.SendInternalServerError(e)
|
||||
return
|
||||
}
|
||||
|
||||
vulItems := make(map[string]interface{})
|
||||
for _, rp := range reports {
|
||||
// Resolve scan report data only when it is ready
|
||||
if len(rp.Report) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
vrp, err := report.ResolveData(rp.MimeType, []byte(rp.Report))
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scan API: get report"))
|
||||
return
|
||||
}
|
||||
|
||||
vulItems[rp.MimeType] = vrp
|
||||
}
|
||||
|
||||
sa.Data["json"] = vulItems
|
||||
sa.ServeJSON()
|
||||
}
|
||||
|
||||
// Log returns the log stream
|
||||
func (sa *ScanAPI) Log() {
|
||||
// Check access permissions
|
||||
if !sa.RequireProjectAccess(sa.pro.ProjectID, rbac.ActionRead, rbac.ResourceScan) {
|
||||
return
|
||||
}
|
||||
|
||||
uuid := sa.GetString(":uuid")
|
||||
bytes, err := scan.DefaultController.GetScanLog(uuid)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scan API: log"))
|
||||
return
|
||||
}
|
||||
|
||||
if bytes == nil {
|
||||
// Not found
|
||||
sa.SendNotFoundError(errors.Errorf("report with uuid %s does not exist", uuid))
|
||||
return
|
||||
}
|
||||
|
||||
sa.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(bytes)))
|
||||
sa.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Type"), "text/plain")
|
||||
_, err = sa.Ctx.ResponseWriter.Write(bytes)
|
||||
if err != nil {
|
||||
sa.SendInternalServerError(errors.Wrap(err, "scan API: log"))
|
||||
}
|
||||
}
|
||||
|
||||
// digestGetter is a function template for getting digest.
|
||||
// TODO: This can be removed if the registry access interface is ready.
|
||||
type digestGetter func(repo, tag string, username string) (string, error)
|
||||
|
||||
// TODO this method should be reconsidered as the tags are stored in database
|
||||
// TODO rather than in registry
|
||||
func getDigest(repo, tag string, username string) (string, error) {
|
||||
exist, digest, err := registry.Cli.ManifestExist(repo, tag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !exist {
|
||||
return "", errors.Errorf("tag %s does exist", tag)
|
||||
}
|
||||
return digest, nil
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
// Copyright Project Harbor 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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/scan"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
dscan "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var scanBaseURL = "/api/repositories/library/hello-world/tags/latest/scan"
|
||||
|
||||
// ScanAPITestSuite is the test suite for scan API.
|
||||
type ScanAPITestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
originalC scan.Controller
|
||||
c *MockScanAPIController
|
||||
|
||||
originalDigestGetter digestGetter
|
||||
|
||||
artifact *v1.Artifact
|
||||
}
|
||||
|
||||
// TestScanAPI is the entry point of ScanAPITestSuite.
|
||||
func TestScanAPI(t *testing.T) {
|
||||
suite.Run(t, new(ScanAPITestSuite))
|
||||
}
|
||||
|
||||
// SetupSuite prepares test env for suite.
|
||||
func (suite *ScanAPITestSuite) SetupSuite() {
|
||||
suite.artifact = &v1.Artifact{
|
||||
NamespaceID: (int64)(1),
|
||||
Repository: "library/hello-world",
|
||||
Tag: "latest",
|
||||
Digest: "digest-code-001",
|
||||
MimeType: v1.MimeTypeDockerArtifact,
|
||||
}
|
||||
}
|
||||
|
||||
// SetupTest prepares test env for test cases.
|
||||
func (suite *ScanAPITestSuite) SetupTest() {
|
||||
suite.originalC = scan.DefaultController
|
||||
suite.c = &MockScanAPIController{}
|
||||
|
||||
scan.DefaultController = suite.c
|
||||
|
||||
suite.originalDigestGetter = digestFunc
|
||||
digestFunc = func(repo, tag string, username string) (s string, e error) {
|
||||
return "digest-code-001", nil
|
||||
}
|
||||
}
|
||||
|
||||
// TearDownTest ...
|
||||
func (suite *ScanAPITestSuite) TearDownTest() {
|
||||
scan.DefaultController = suite.originalC
|
||||
digestFunc = suite.originalDigestGetter
|
||||
}
|
||||
|
||||
// TestScanAPIBase ...
|
||||
func (suite *ScanAPITestSuite) TestScanAPIBase() {
|
||||
suite.c.On("Scan", &v1.Artifact{}).Return(nil)
|
||||
// Including general cases
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: scanBaseURL,
|
||||
method: http.MethodGet,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: scanBaseURL,
|
||||
method: http.MethodPost,
|
||||
credential: projGuest,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
runCodeCheckingCases(suite.T(), cases...)
|
||||
}
|
||||
|
||||
// TestScanAPIScan ...
|
||||
func (suite *ScanAPITestSuite) TestScanAPIScan() {
|
||||
suite.c.On("Scan", suite.artifact).Return(nil)
|
||||
|
||||
// Including general cases
|
||||
cases := []*codeCheckingCase{
|
||||
// 202
|
||||
{
|
||||
request: &testingRequest{
|
||||
url: scanBaseURL,
|
||||
method: http.MethodPost,
|
||||
credential: projAdmin,
|
||||
},
|
||||
code: http.StatusAccepted,
|
||||
},
|
||||
}
|
||||
|
||||
runCodeCheckingCases(suite.T(), cases...)
|
||||
}
|
||||
|
||||
// TestScanAPIReport ...
|
||||
func (suite *ScanAPITestSuite) TestScanAPIReport() {
|
||||
suite.c.On("GetReport", suite.artifact, []string{v1.MimeTypeNativeReport}).Return([]*dscan.Report{}, nil)
|
||||
|
||||
vulItems := make(map[string]interface{})
|
||||
|
||||
header := make(http.Header)
|
||||
header.Add("Accept", v1.MimeTypeNativeReport)
|
||||
err := handleAndParse(
|
||||
&testingRequest{
|
||||
url: scanBaseURL,
|
||||
method: http.MethodGet,
|
||||
credential: projDeveloper,
|
||||
header: header,
|
||||
}, &vulItems)
|
||||
require.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
// TestScanAPILog ...
|
||||
func (suite *ScanAPITestSuite) TestScanAPILog() {
|
||||
suite.c.On("GetScanLog", "the-uuid-001").Return([]byte(`{"log": "this is my log"}`), nil)
|
||||
|
||||
logs := make(map[string]string)
|
||||
err := handleAndParse(
|
||||
&testingRequest{
|
||||
url: fmt.Sprintf("%s/%s", scanBaseURL, "the-uuid-001/log"),
|
||||
method: http.MethodGet,
|
||||
credential: projDeveloper,
|
||||
}, &logs)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Condition(suite.T(), func() (success bool) {
|
||||
success = len(logs) > 0
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Mock things
|
||||
|
||||
// MockScanAPIController ...
|
||||
type MockScanAPIController struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Scan ...
|
||||
func (msc *MockScanAPIController) Scan(artifact *v1.Artifact, option ...scan.Option) error {
|
||||
args := msc.Called(artifact)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetReport(artifact *v1.Artifact, mimeTypes []string) ([]*dscan.Report, error) {
|
||||
args := msc.Called(artifact, mimeTypes)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).([]*dscan.Report), args.Error(1)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetSummary(artifact *v1.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
args := msc.Called(artifact, mimeTypes, options)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(map[string]interface{}), args.Error(1)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetScanLog(uuid string) ([]byte, error) {
|
||||
args := msc.Called(uuid)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) HandleJobHooks(trackID string, change *job.StatusChange) error {
|
||||
args := msc.Called(trackID, change)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) DeleteReports(digests ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetStats(requester string) (*all.Stats, error) {
|
||||
args := msc.Called(requester)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*all.Stats), args.Error(1)
|
||||
}
|
@ -56,7 +56,7 @@ const (
|
||||
// both tagged and untagged artifacts
|
||||
both = `IN (
|
||||
SELECT DISTINCT art.id FROM artifact art
|
||||
LEFT JOIN tag ON art.id=tag.artifact_id
|
||||
LEFT JOIN tag ON art.id=tag.artifact_id
|
||||
LEFT JOIN artifact_reference ref ON art.id=ref.child_id
|
||||
WHERE tag.id IS NOT NULL OR ref.id IS NULL)`
|
||||
// only untagged artifacts
|
||||
|
@ -1,12 +1,16 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
o "github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"github.com/goharbor/harbor/src/api/scan"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/internal/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
@ -101,12 +105,24 @@ func constructScanImagePayload(event *model.ScanImageEvent, project *models.Proj
|
||||
return nil, errors.Wrap(err, "construct scan payload")
|
||||
}
|
||||
|
||||
ctx := orm.NewContext(context.TODO(), o.NewOrm())
|
||||
|
||||
reference := event.Artifact.Digest
|
||||
if reference == "" {
|
||||
reference = event.Artifact.Tag
|
||||
}
|
||||
|
||||
art, err := artifact.Ctl.GetByReference(ctx, event.Artifact.Repository, event.Artifact.Digest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for reasonable time to make sure the report is ready
|
||||
// Interval=500ms and total time = 5s
|
||||
// If the report is still not ready in the total time, then failed at then
|
||||
for i := 0; i < 10; i++ {
|
||||
// First check in case it is ready
|
||||
if re, err := scan.DefaultController.GetReport(event.Artifact, []string{v1.MimeTypeNativeReport}); err == nil {
|
||||
if re, err := scan.DefaultController.GetReport(ctx, art, []string{v1.MimeTypeNativeReport}); err == nil {
|
||||
if len(re) > 0 && len(re[0].Report) > 0 {
|
||||
break
|
||||
}
|
||||
@ -118,7 +134,7 @@ func constructScanImagePayload(event *model.ScanImageEvent, project *models.Proj
|
||||
}
|
||||
|
||||
// Add scan overview
|
||||
summaries, err := scan.DefaultController.GetSummary(event.Artifact, []string{v1.MimeTypeNativeReport})
|
||||
summaries, err := scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "construct scan payload")
|
||||
}
|
||||
|
@ -1,23 +1,37 @@
|
||||
// Copyright Project Harbor 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 notification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
sc "github.com/goharbor/harbor/src/api/scan"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/notification/policy"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/stretchr/testify/mock"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/api/artifact"
|
||||
scantesting "github.com/goharbor/harbor/src/testing/api/scan"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@ -26,10 +40,11 @@ import (
|
||||
type ScanImagePreprocessHandlerSuite struct {
|
||||
suite.Suite
|
||||
|
||||
om policy.Manager
|
||||
pid int64
|
||||
evt *model.ScanImageEvent
|
||||
c sc.Controller
|
||||
om policy.Manager
|
||||
pid int64
|
||||
evt *model.ScanImageEvent
|
||||
c sc.Controller
|
||||
artifactCtl artifact.Controller
|
||||
}
|
||||
|
||||
// TestScanImagePreprocessHandler is the entry point of ScanImagePreprocessHandlerSuite.
|
||||
@ -65,15 +80,29 @@ func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
|
||||
}
|
||||
|
||||
suite.c = sc.DefaultController
|
||||
mc := &MockScanAPIController{}
|
||||
mc := &scantesting.Controller{}
|
||||
|
||||
var options []report.Option
|
||||
s := make(map[string]interface{})
|
||||
mc.On("GetSummary", a, []string{v1.MimeTypeNativeReport}, options).Return(s, nil)
|
||||
mc.On("GetReport", a, []string{v1.MimeTypeNativeReport}).Return(reports, nil)
|
||||
mock.OnAnything(mc, "GetSummary").Return(s, nil)
|
||||
mock.OnAnything(mc, "GetReport").Return(reports, nil)
|
||||
|
||||
sc.DefaultController = mc
|
||||
|
||||
suite.artifactCtl = artifact.Ctl
|
||||
|
||||
artifactCtl := &artifacttesting.Controller{}
|
||||
|
||||
art := &artifact.Artifact{}
|
||||
art.ProjectID = a.NamespaceID
|
||||
art.RepositoryName = a.Repository
|
||||
art.Digest = a.Digest
|
||||
|
||||
mock.OnAnything(artifactCtl, "GetByReference").Return(art, nil)
|
||||
|
||||
artifact.Ctl = artifactCtl
|
||||
|
||||
suite.om = notification.PolicyMgr
|
||||
mp := &fakedPolicyMgr{}
|
||||
notification.PolicyMgr = mp
|
||||
@ -88,6 +117,7 @@ func (suite *ScanImagePreprocessHandlerSuite) SetupSuite() {
|
||||
func (suite *ScanImagePreprocessHandlerSuite) TearDownSuite() {
|
||||
notification.PolicyMgr = suite.om
|
||||
sc.DefaultController = suite.c
|
||||
artifact.Ctl = suite.artifactCtl
|
||||
}
|
||||
|
||||
// TestHandle ...
|
||||
@ -100,73 +130,6 @@ func (suite *ScanImagePreprocessHandlerSuite) TestHandle() {
|
||||
|
||||
// Mock things
|
||||
|
||||
// MockScanAPIController ...
|
||||
type MockScanAPIController struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Scan ...
|
||||
func (msc *MockScanAPIController) Scan(artifact *v1.Artifact, option ...sc.Option) error {
|
||||
args := msc.Called(artifact)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetReport(artifact *v1.Artifact, mimeTypes []string) ([]*scan.Report, error) {
|
||||
args := msc.Called(artifact, mimeTypes)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).([]*scan.Report), args.Error(1)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetSummary(artifact *v1.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
args := msc.Called(artifact, mimeTypes, options)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(map[string]interface{}), args.Error(1)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetScanLog(uuid string) ([]byte, error) {
|
||||
args := msc.Called(uuid)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) HandleJobHooks(trackID string, change *job.StatusChange) error {
|
||||
args := msc.Called(trackID, change)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) DeleteReports(digests ...string) error {
|
||||
pl := make([]interface{}, 0)
|
||||
for _, d := range digests {
|
||||
pl = append(pl, d)
|
||||
}
|
||||
args := msc.Called(pl...)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (msc *MockScanAPIController) GetStats(requester string) (*all.Stats, error) {
|
||||
args := msc.Called(requester)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*all.Stats), args.Error(1)
|
||||
}
|
||||
|
||||
// MockHTTPHandler ...
|
||||
type MockHTTPHandler struct{}
|
||||
|
||||
|
@ -126,17 +126,14 @@ func UpdateReportStatus(trackID string, status string, statusCode int, statusRev
|
||||
|
||||
// qt generates sql statements:
|
||||
// UPDATE "scan_report" SET "end_time" = $1, "status" = $2, "status_code" = $3, "status_rev" = $4
|
||||
// WHERE "id" IN ( SELECT T0."id" FROM "scan_report" T0 WHERE ( T0."status_rev" = $5 AND T0."status_code" < $6 )
|
||||
// OR ( T0."status_rev" < $7 ) AND T0."track_id" = $8 )
|
||||
cond := orm.NewCondition()
|
||||
c1 := cond.And("status_rev", statusRev).And("status_code__lt", statusCode)
|
||||
c2 := cond.And("status_rev__lt", statusRev)
|
||||
c := cond.AndCond(c1).OrCond(c2)
|
||||
|
||||
count, err := qt.SetCond(c).
|
||||
Filter("track_id", trackID).
|
||||
Update(data)
|
||||
// WHERE "id" IN ( SELECT T0."id" FROM "scan_report" T0 WHERE ( T0."status_rev" = $5 AND T0."status_code" < $6
|
||||
// OR ( T0."status_rev" < $7 ) ) AND ( T0."track_id" = $8 )
|
||||
c1 := orm.NewCondition().And("status_rev", statusRev).And("status_code__lt", statusCode)
|
||||
c2 := orm.NewCondition().And("status_rev__lt", statusRev)
|
||||
c3 := orm.NewCondition().And("track_id", trackID)
|
||||
c := orm.NewCondition().AndCond(c1.OrCond(c2)).AndCond(c3)
|
||||
|
||||
count, err := qt.SetCond(c).Update(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/rest/auth"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -57,6 +58,8 @@ type Registration struct {
|
||||
Vendor string `orm:"-" json:"vendor,omitempty"`
|
||||
Version string `orm:"-" json:"version,omitempty"`
|
||||
|
||||
Metadata *v1.ScannerAdapterMetadata `orm:"-" json:"-"`
|
||||
|
||||
// Timestamps
|
||||
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
|
||||
@ -115,6 +118,66 @@ func (r *Registration) Validate(checkUUID bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Client returns client of registration
|
||||
func (r *Registration) Client(pool v1.ClientPool) (v1.Client, error) {
|
||||
if err := r.Validate(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pool.Get(r.URL, r.Auth, r.AccessCredential, r.SkipCertVerify)
|
||||
}
|
||||
|
||||
// HasCapability returns true when mime type of the artifact support by the scanner
|
||||
func (r *Registration) HasCapability(manifestMimeType string) bool {
|
||||
if r.Metadata == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, capability := range r.Metadata.Capabilities {
|
||||
for _, mt := range capability.ConsumesMimeTypes {
|
||||
if mt == manifestMimeType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetProducesMimeTypes returns produces mime types for the artifact
|
||||
func (r *Registration) GetProducesMimeTypes(mimeType string) []string {
|
||||
if r.Metadata == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, capability := range r.Metadata.Capabilities {
|
||||
for _, mt := range capability.ConsumesMimeTypes {
|
||||
if mt == mimeType {
|
||||
return capability.ProducesMimeTypes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCapability returns capability for the mime type
|
||||
func (r *Registration) GetCapability(mimeType string) *v1.ScannerCapability {
|
||||
if r.Metadata == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, capability := range r.Metadata.Capabilities {
|
||||
for _, mt := range capability.ConsumesMimeTypes {
|
||||
if mt == mimeType {
|
||||
return capability
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check the registration URL with url package
|
||||
func checkURL(u string) error {
|
||||
if len(strings.TrimSpace(u)) == 0 {
|
||||
|
@ -126,7 +126,7 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
|
||||
myLogger.Infof("Report mime types: %v\n", mimes)
|
||||
|
||||
// Submit scan request to the scanner adapter
|
||||
client, err := v1.DefaultClientPool.Get(r)
|
||||
client, err := r.Client(v1.DefaultClientPool)
|
||||
if err != nil {
|
||||
return logAndWrapError(myLogger, err, "scan job: get client")
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
mocktesting "github.com/goharbor/harbor/src/testing/mock"
|
||||
v1testing "github.com/goharbor/harbor/src/testing/pkg/scan/rest/v1"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -35,7 +37,7 @@ type JobTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
defaultClientPool v1.ClientPool
|
||||
mcp *MockClientPool
|
||||
mcp *v1testing.ClientPool
|
||||
}
|
||||
|
||||
// TestJob is the entry of JobTestSuite.
|
||||
@ -45,7 +47,7 @@ func TestJob(t *testing.T) {
|
||||
|
||||
// SetupSuite sets up test env for JobTestSuite.
|
||||
func (suite *JobTestSuite) SetupSuite() {
|
||||
mcp := &MockClientPool{}
|
||||
mcp := &v1testing.ClientPool{}
|
||||
suite.defaultClientPool = v1.DefaultClientPool
|
||||
v1.DefaultClientPool = mcp
|
||||
|
||||
@ -96,7 +98,7 @@ func (suite *JobTestSuite) TestJob() {
|
||||
jp[JobParameterRequest] = sData
|
||||
jp[JobParameterMimes] = mimeTypes
|
||||
|
||||
mc := &MockClient{}
|
||||
mc := &v1testing.Client{}
|
||||
sre := &v1.ScanResponse{
|
||||
ID: "scan_id",
|
||||
}
|
||||
@ -127,7 +129,7 @@ func (suite *JobTestSuite) TestJob() {
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
mc.On("GetScanReport", "scan_id", v1.MimeTypeNativeReport).Return(string(jRep), nil)
|
||||
suite.mcp.On("Get", r).Return(mc, nil)
|
||||
mocktesting.OnAnything(suite.mcp, "Get").Return(mc, nil)
|
||||
|
||||
crp := &CheckInReport{
|
||||
Digest: sr.Artifact.Digest,
|
||||
@ -255,52 +257,3 @@ func (mjl *MockJobLogger) Fatal(v ...interface{}) {
|
||||
func (mjl *MockJobLogger) Fatalf(format string, v ...interface{}) {
|
||||
logger.Fatalf(format, v...)
|
||||
}
|
||||
|
||||
// MockClientPool mocks the client pool
|
||||
type MockClientPool struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get v1 client
|
||||
func (mcp *MockClientPool) Get(r *scanner.Registration) (v1.Client, error) {
|
||||
args := mcp.Called(r)
|
||||
c := args.Get(0)
|
||||
if c != nil {
|
||||
return c.(v1.Client), nil
|
||||
}
|
||||
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
// MockClient mocks the v1 client
|
||||
type MockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetMetadata ...
|
||||
func (mc *MockClient) GetMetadata() (*v1.ScannerAdapterMetadata, error) {
|
||||
args := mc.Called()
|
||||
s := args.Get(0)
|
||||
if s != nil {
|
||||
return s.(*v1.ScannerAdapterMetadata), nil
|
||||
}
|
||||
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
// SubmitScan ...
|
||||
func (mc *MockClient) SubmitScan(req *v1.ScanRequest) (*v1.ScanResponse, error) {
|
||||
args := mc.Called(req)
|
||||
sr := args.Get(0)
|
||||
if sr != nil {
|
||||
return sr.(*v1.ScanResponse), nil
|
||||
}
|
||||
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
// GetScanReport ...
|
||||
func (mc *MockClient) GetScanReport(scanRequestID, reportMIMEType string) (string, error) {
|
||||
args := mc.Called(scanRequestID, reportMIMEType)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
@ -17,12 +17,11 @@ package report
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/errs"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -81,7 +80,7 @@ func (bm *basicManager) Create(r *scan.Report) (string, error) {
|
||||
// Status conflict
|
||||
if theCopy.StartTime.Add(reportTimeout).After(time.Now()) {
|
||||
if theStatus.Compare(job.RunningStatus) <= 0 {
|
||||
return "", errs.WithCode(errs.Conflict, errs.Errorf("a previous scan process is %s", theCopy.Status))
|
||||
return "", ierror.ConflictError(nil).WithMessage("a previous scan process is %s", theCopy.Status)
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,17 +91,14 @@ func (bm *basicManager) Create(r *scan.Report) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Assign uuid
|
||||
UUID, err := uuid.NewUUID()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "create report: new UUID")
|
||||
}
|
||||
r.UUID = UUID.String()
|
||||
r.UUID = uuid.New().String()
|
||||
|
||||
// Fill in / override the related properties
|
||||
r.StartTime = time.Now().UTC()
|
||||
r.Status = job.PendingStatus.String()
|
||||
r.StatusCode = job.PendingStatus.Code()
|
||||
if r.Status == "" {
|
||||
r.Status = job.PendingStatus.String()
|
||||
r.StatusCode = job.PendingStatus.Code()
|
||||
}
|
||||
|
||||
// Insert
|
||||
if _, err = scan.CreateReport(r); err != nil {
|
||||
|
@ -50,6 +50,39 @@ func WithCVEWhitelist(set *CVESet) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// SummaryMerger is a helper function to merge summary together
|
||||
type SummaryMerger func(s1, s2 interface{}) (interface{}, error)
|
||||
|
||||
// SupportedMergers declares mappings between mime type and summary merger func.
|
||||
var SupportedMergers = map[string]SummaryMerger{
|
||||
v1.MimeTypeNativeReport: MergeNativeSummary,
|
||||
}
|
||||
|
||||
// MergeSummary merge summary s1 and s2
|
||||
func MergeSummary(mimeType string, s1, s2 interface{}) (interface{}, error) {
|
||||
m, ok := SupportedMergers[mimeType]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no mreger bound with mime type %s", mimeType)
|
||||
}
|
||||
|
||||
return m(s1, s2)
|
||||
}
|
||||
|
||||
// MergeNativeSummary merge vuln.NativeReportSummary together
|
||||
func MergeNativeSummary(s1, s2 interface{}) (interface{}, error) {
|
||||
nrs1, ok := s1.(*vuln.NativeReportSummary)
|
||||
if !ok {
|
||||
return nil, errors.New("native report summary required")
|
||||
}
|
||||
|
||||
nrs2, ok := s2.(*vuln.NativeReportSummary)
|
||||
if !ok {
|
||||
return nil, errors.New("native report summary required")
|
||||
}
|
||||
|
||||
return nrs1.Merge(nrs2), nil
|
||||
}
|
||||
|
||||
// SupportedGenerators declares mappings between mime type and summary generator func.
|
||||
var SupportedGenerators = map[string]SummaryGenerator{
|
||||
v1.MimeTypeNativeReport: GenerateNativeSummary,
|
||||
@ -94,6 +127,8 @@ func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, erro
|
||||
sum.ScanStatus = r.Status
|
||||
}
|
||||
|
||||
sum.TotalCount = 1
|
||||
|
||||
// If the status is not success/stopped, there will not be any report.
|
||||
if r.Status != job.SuccessStatus.String() &&
|
||||
r.Status != job.StoppedStatus.String() {
|
||||
@ -115,6 +150,9 @@ func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, erro
|
||||
return nil, errors.Errorf("type mismatch: expect *vuln.Report but got %s", reflect.TypeOf(raw).String())
|
||||
}
|
||||
|
||||
sum.CompleteCount = 1
|
||||
sum.CompletePercent = 100
|
||||
|
||||
sum.Severity = rp.Severity
|
||||
vsum := &vuln.VulnerabilitySummary{
|
||||
Total: len(rp.Vulnerabilities),
|
||||
|
@ -26,7 +26,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/rest/auth"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -81,7 +80,7 @@ type basicClient struct {
|
||||
}
|
||||
|
||||
// NewClient news a basic client
|
||||
func NewClient(r *scanner.Registration) (Client, error) {
|
||||
func NewClient(url, authType, accessCredential string, skipCertVerify bool) (Client, error) {
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
@ -93,11 +92,11 @@ func NewClient(r *scanner.Registration) (Client, error) {
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: r.SkipCertVerify,
|
||||
InsecureSkipVerify: skipCertVerify,
|
||||
},
|
||||
}
|
||||
|
||||
authorizer, err := auth.GetAuthorizer(r.Auth, r.AccessCredential)
|
||||
authorizer, err := auth.GetAuthorizer(authType, accessCredential)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "new v1 client")
|
||||
}
|
||||
@ -109,7 +108,7 @@ func NewClient(r *scanner.Registration) (Client, error) {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
},
|
||||
spec: NewSpec(r.URL),
|
||||
spec: NewSpec(url),
|
||||
authorizer: authorizer,
|
||||
}, nil
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -44,7 +43,7 @@ type ClientPool interface {
|
||||
// Returns:
|
||||
// Client : v1 client
|
||||
// error : non nil error if any errors occurred
|
||||
Get(r *scanner.Registration) (Client, error)
|
||||
Get(url, authType, accessCredential string, skipCertVerify bool) (Client, error)
|
||||
}
|
||||
|
||||
// PoolConfig provides configurations for the client pool.
|
||||
@ -97,20 +96,12 @@ func NewClientPool(config *PoolConfig) ClientPool {
|
||||
// add the following func after the first time initializing the client.
|
||||
// pool item represents the client with a timestamp of last accessed.
|
||||
|
||||
func (bcp *basicClientPool) Get(r *scanner.Registration) (Client, error) {
|
||||
if r == nil {
|
||||
return nil, errors.New("nil scanner registration")
|
||||
}
|
||||
|
||||
if err := r.Validate(false); err != nil {
|
||||
return nil, errors.Wrap(err, "client pool: get")
|
||||
}
|
||||
|
||||
k := key(r)
|
||||
func (bcp *basicClientPool) Get(url, authType, accessCredential string, skipCertVerify bool) (Client, error) {
|
||||
k := fmt.Sprintf("%s:%s:%s:%v", url, authType, accessCredential, skipCertVerify)
|
||||
|
||||
item, ok := bcp.pool.Load(k)
|
||||
if !ok {
|
||||
nc, err := NewClient(r)
|
||||
nc, err := NewClient(url, authType, accessCredential, skipCertVerify)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "client pool: get")
|
||||
}
|
||||
@ -157,12 +148,3 @@ func (bcp *basicClientPool) deadCheck(key string, item *poolItem) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func key(r *scanner.Registration) string {
|
||||
return fmt.Sprintf("%s:%s:%s:%v",
|
||||
r.URL,
|
||||
r.Auth,
|
||||
r.AccessCredential,
|
||||
r.SkipCertVerify,
|
||||
)
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/rest/auth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -49,23 +48,13 @@ func (suite *ClientPoolTestSuite) SetupSuite() {
|
||||
|
||||
// TestClientPoolGet tests the get method of client pool.
|
||||
func (suite *ClientPoolTestSuite) TestClientPoolGet() {
|
||||
r := &scanner.Registration{
|
||||
ID: 1,
|
||||
Name: "TestClientPoolGet",
|
||||
UUID: "uuid",
|
||||
URL: "http://a.b.c",
|
||||
Auth: auth.Basic,
|
||||
AccessCredential: "u:p",
|
||||
SkipCertVerify: false,
|
||||
}
|
||||
|
||||
client1, err := suite.pool.Get(r)
|
||||
client1, err := suite.pool.Get("http://a.b.c", auth.Basic, "u:p", false)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), client1)
|
||||
|
||||
p1 := fmt.Sprintf("%p", client1.(*basicClient))
|
||||
|
||||
client2, err := suite.pool.Get(r)
|
||||
client2, err := suite.pool.Get("http://a.b.c", auth.Basic, "u:p", false)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), client2)
|
||||
|
||||
@ -73,7 +62,7 @@ func (suite *ClientPoolTestSuite) TestClientPoolGet() {
|
||||
assert.Equal(suite.T(), p1, p2)
|
||||
|
||||
<-time.After(400 * time.Millisecond)
|
||||
client3, err := suite.pool.Get(r)
|
||||
client3, err := suite.pool.Get("http://a.b.c", auth.Basic, "u:p", false)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), client3)
|
||||
|
||||
|
@ -22,7 +22,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -44,15 +43,8 @@ func TestClient(t *testing.T) {
|
||||
// SetupSuite prepares the test suite env
|
||||
func (suite *ClientTestSuite) SetupSuite() {
|
||||
suite.testServer = httptest.NewServer(&mockHandler{})
|
||||
r := &scanner.Registration{
|
||||
ID: 1000,
|
||||
UUID: "uuid",
|
||||
Name: "TestClient",
|
||||
URL: suite.testServer.URL,
|
||||
SkipCertVerify: true,
|
||||
}
|
||||
|
||||
c, err := NewClient(r)
|
||||
c, err := NewClient(suite.testServer.URL, "", "", true)
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), c)
|
||||
|
||||
|
@ -78,6 +78,19 @@ func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetCapability returns capability for the mime type
|
||||
func (md *ScannerAdapterMetadata) GetCapability(mimeType string) *ScannerCapability {
|
||||
for _, capability := range md.Capabilities {
|
||||
for _, mt := range capability.ConsumesMimeTypes {
|
||||
if mt == mimeType {
|
||||
return capability
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Artifact represents an artifact stored in Registry.
|
||||
type Artifact struct {
|
||||
// ID of the namespace (project). It will not be sent to scanner adapter.
|
||||
|
@ -17,21 +17,76 @@ package vuln
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
// NativeReportSummary is the default supported scan report summary model.
|
||||
// Generated based on the report with v1.MimeTypeNativeReport mime type.
|
||||
type NativeReportSummary struct {
|
||||
ReportID string `json:"report_id"`
|
||||
ScanStatus string `json:"scan_status"`
|
||||
Severity Severity `json:"severity"`
|
||||
Duration int64 `json:"duration"`
|
||||
Summary *VulnerabilitySummary `json:"summary"`
|
||||
CVEBypassed []string `json:"-"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime time.Time `json:"end_time"`
|
||||
Scanner *v1.Scanner `json:"scanner,omitempty"`
|
||||
ReportID string `json:"report_id"`
|
||||
ScanStatus string `json:"scan_status"`
|
||||
Severity Severity `json:"severity"`
|
||||
Duration int64 `json:"duration"`
|
||||
Summary *VulnerabilitySummary `json:"summary"`
|
||||
CVEBypassed []string `json:"-"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime time.Time `json:"end_time"`
|
||||
Scanner *v1.Scanner `json:"scanner,omitempty"`
|
||||
CompletePercent int `json:"complete_percent"`
|
||||
|
||||
TotalCount int `json:"-"`
|
||||
CompleteCount int `json:"-"`
|
||||
}
|
||||
|
||||
// Merge ...
|
||||
func (sum *NativeReportSummary) Merge(another *NativeReportSummary) *NativeReportSummary {
|
||||
r := &NativeReportSummary{}
|
||||
|
||||
r.ReportID = sum.ReportID
|
||||
r.StartTime = minTime(sum.StartTime, another.StartTime)
|
||||
r.EndTime = maxTime(sum.EndTime, another.EndTime)
|
||||
r.Duration = r.EndTime.Unix() - r.StartTime.Unix()
|
||||
r.Scanner = sum.Scanner
|
||||
r.TotalCount = sum.TotalCount + another.TotalCount
|
||||
r.CompleteCount = sum.CompleteCount + another.CompleteCount
|
||||
r.CompletePercent = r.CompleteCount * 100 / r.TotalCount
|
||||
|
||||
if sum.Severity.String() != "" && another.Severity.String() != "" {
|
||||
if sum.Severity.Code() > another.Severity.Code() {
|
||||
r.Severity = sum.Severity
|
||||
} else {
|
||||
r.Severity = another.Severity
|
||||
}
|
||||
} else if sum.Severity.String() != "" {
|
||||
r.Severity = sum.Severity
|
||||
} else {
|
||||
r.Severity = another.Severity
|
||||
}
|
||||
|
||||
if isRunningStatus(sum.ScanStatus) || isRunningStatus(another.ScanStatus) {
|
||||
r.ScanStatus = job.RunningStatus.String()
|
||||
} else {
|
||||
diff := job.Status(sum.ScanStatus).Compare(job.Status(another.ScanStatus))
|
||||
if diff < 0 {
|
||||
r.ScanStatus = another.ScanStatus
|
||||
} else if diff == 0 {
|
||||
if job.Status(sum.ScanStatus) == job.SuccessStatus ||
|
||||
job.Status(another.ScanStatus) == job.SuccessStatus {
|
||||
r.ScanStatus = job.SuccessStatus.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sum.Summary != nil && another.Summary != nil {
|
||||
r.Summary = sum.Summary.Merge(another.Summary)
|
||||
} else if sum.Summary != nil {
|
||||
r.Summary = sum.Summary
|
||||
} else {
|
||||
r.Summary = another.Summary
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// VulnerabilitySummary contains the total number of the found vulnerabilities number
|
||||
@ -42,5 +97,48 @@ type VulnerabilitySummary struct {
|
||||
Summary SeveritySummary `json:"summary"`
|
||||
}
|
||||
|
||||
// Merge ...
|
||||
func (v *VulnerabilitySummary) Merge(a *VulnerabilitySummary) *VulnerabilitySummary {
|
||||
r := &VulnerabilitySummary{
|
||||
Total: v.Total + a.Total,
|
||||
Fixable: v.Fixable + a.Fixable,
|
||||
Summary: SeveritySummary{},
|
||||
}
|
||||
|
||||
for k, v := range v.Summary {
|
||||
r.Summary[k] = v
|
||||
}
|
||||
|
||||
for k, v := range a.Summary {
|
||||
if _, ok := r.Summary[k]; ok {
|
||||
r.Summary[k] += v
|
||||
} else {
|
||||
r.Summary[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// SeveritySummary ...
|
||||
type SeveritySummary map[Severity]int
|
||||
|
||||
func minTime(t1, t2 time.Time) time.Time {
|
||||
if t1.Before(t2) {
|
||||
return t1
|
||||
}
|
||||
|
||||
return t2
|
||||
}
|
||||
|
||||
func maxTime(t1, t2 time.Time) time.Time {
|
||||
if t1.Before(t2) {
|
||||
return t2
|
||||
}
|
||||
|
||||
return t1
|
||||
}
|
||||
|
||||
func isRunningStatus(status string) bool {
|
||||
return job.Status(status) == job.RunningStatus
|
||||
}
|
||||
|
@ -55,9 +55,9 @@
|
||||
<div *ngFor="let filter of mutipleFilter" >
|
||||
<ul class="list-unstyled" *ngIf="filterByType ===filter.filterBy">
|
||||
<li class="cursor-pointer" (click)="selectFilter(item.showItem, item.filterText)" *ngFor="let item of filter.listItem">{{item.showItem | translate}}</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="label-filter-panel" *ngIf="!withAdmiral" [hidden]="!(openLabelFilterPanel&&filterByType==='label.id')">
|
||||
<a class="filterClose" (click)="closeFilter()">×</a>
|
||||
|
@ -75,6 +75,10 @@
|
||||
<span class="bar-scanning-time">{{'VULNERABILITY.CHART.SCANNING_TIME' | translate}} </span>
|
||||
<span>{{completeTimestamp | date:'short'}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="bar-scanning-time">{{'VULNERABILITY.CHART.SCANNING_PERCENT' | translate}} </span>
|
||||
<span>{{completePercent}}</span>
|
||||
</div>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
|
@ -149,7 +149,9 @@ export class ResultTipHistogramComponent implements OnInit {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public get completePercent(): string {
|
||||
return this.vulnerabilitySummary ? `${this.vulnerabilitySummary.complete_percent}%` : '0%'
|
||||
}
|
||||
get completeTimestamp(): Date {
|
||||
return this.vulnerabilitySummary && this.vulnerabilitySummary.end_time ? this.vulnerabilitySummary.end_time : new Date();
|
||||
}
|
||||
|
@ -987,6 +987,7 @@
|
||||
},
|
||||
"CHART": {
|
||||
"SCANNING_TIME": "Scan completed time:",
|
||||
"SCANNING_PERCENT": "Scan completed percent:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} of {{totalPackages}} {{package}} have known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} of {{totalPackages}} {{package}} has known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
@ -1028,7 +1029,7 @@
|
||||
"UNTAGGED": "Untagged",
|
||||
"ALL": "All",
|
||||
"PLACEHOLDER": "We couldn't find any artifactds!",
|
||||
"SCAN_UNSUPPORTED": "Scan Unsupported"
|
||||
"SCAN_UNSUPPORTED": "Unsupported"
|
||||
},
|
||||
"TAG": {
|
||||
"CREATION_TIME_PREFIX": "Create on",
|
||||
|
@ -986,6 +986,7 @@
|
||||
},
|
||||
"CHART": {
|
||||
"SCANNING_TIME": "Scan completed time:",
|
||||
"SCANNING_PERCENT": "Scan completed percent:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} of {{totalPackages}} {{package}} have known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} of {{totalPackages}} {{package}} has known {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
@ -1027,7 +1028,7 @@
|
||||
"UNTAGGED": "Untagged",
|
||||
"ALL": "All",
|
||||
"PLACEHOLDER": "We couldn't find any artifacts!",
|
||||
"SCAN_UNSUPPORTED": "Scan Unsupported"
|
||||
"SCAN_UNSUPPORTED": "Unsupported"
|
||||
},
|
||||
"TAG": {
|
||||
"CREATION_TIME_PREFIX": "Create on",
|
||||
|
@ -959,6 +959,7 @@
|
||||
},
|
||||
"CHART": {
|
||||
"SCANNING_TIME": "Temps d'analyse complète :",
|
||||
"SCANNING_PERCENT": "Scan completed percent:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} de {{totalPackages}} {{package}} ont des {{vulnerability}} connues.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} de {{totalPackages}} {{package}} a des {{vulnerability}} connues.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
@ -1000,7 +1001,7 @@
|
||||
"UNTAGGED": "Untagged",
|
||||
"ALL": "All",
|
||||
"PLACEHOLDER": "We couldn't find any artifacts!",
|
||||
"SCAN_UNSUPPORTED": "Scan Unsupported"
|
||||
"SCAN_UNSUPPORTED": "Unsupported"
|
||||
},
|
||||
"TAG": {
|
||||
"CREATION_TIME_PREFIX": "Créer le",
|
||||
|
@ -982,6 +982,7 @@
|
||||
},
|
||||
"CHART": {
|
||||
"SCANNING_TIME": "Tempo de conclusão da análise:",
|
||||
"SCANNING_PERCENT": "Scan completed percent:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} de {{totalPackages}} {{package}} possuem {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} de {{totalPackages}} {{package}} possuem {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
@ -1023,7 +1024,7 @@
|
||||
"UNTAGGED": "Untagged",
|
||||
"ALL": "All",
|
||||
"PLACEHOLDER": "We couldn't find any artifacts!",
|
||||
"SCAN_UNSUPPORTED": "Scan Unsupported"
|
||||
"SCAN_UNSUPPORTED": "Unsupported"
|
||||
},
|
||||
"TAG": {
|
||||
"CREATION_TIME_PREFIX": "Criado em",
|
||||
|
@ -986,6 +986,7 @@
|
||||
},
|
||||
"CHART": {
|
||||
"SCANNING_TIME": "Tarama tamamlanma zamanı:",
|
||||
"SCANNING_PERCENT": "Scan completed percent:",
|
||||
"TOOLTIPS_TITLE": "{{totalVulnerability}} 'nın {{totalPackages}} {{package}} bilinen {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} 'nın {{totalPackages}} {{package}} bilinen {{vulnerability}}.",
|
||||
"TOOLTIPS_TITLE_ZERO": "No recognizable vulnerability found"
|
||||
@ -1027,7 +1028,7 @@
|
||||
"UNTAGGED": "Untagged",
|
||||
"ALL": "All",
|
||||
"PLACEHOLDER": "We couldn't find any artifacts!",
|
||||
"SCAN_UNSUPPORTED": "Scan Unsupported"
|
||||
"SCAN_UNSUPPORTED": "Unsupported"
|
||||
},
|
||||
"TAG": {
|
||||
"CREATION_TIME_PREFIX": "Oluştur",
|
||||
|
@ -986,6 +986,7 @@
|
||||
},
|
||||
"CHART": {
|
||||
"SCANNING_TIME": "扫描完成时间:",
|
||||
"SCANNING_PERCENT": "扫描完成度:",
|
||||
"TOOLTIPS_TITLE": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}。",
|
||||
"TOOLTIPS_TITLE_SINGULAR": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}。",
|
||||
"TOOLTIPS_TITLE_ZERO": "没有发现可识别的漏洞"
|
||||
|
@ -305,6 +305,7 @@ export interface VulnerabilitySummary {
|
||||
start_time?: Date;
|
||||
end_time?: Date;
|
||||
scanner?: ScannerVo;
|
||||
complete_percent?: number;
|
||||
}
|
||||
export interface ScannerVo {
|
||||
name?: string;
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
sc "github.com/goharbor/harbor/src/api/scan"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
@ -40,21 +41,15 @@ func Middleware() func(http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the vulnerability summary
|
||||
artifact := &v1.Artifact{
|
||||
NamespaceID: wl.ProjectID,
|
||||
Repository: img.Repository,
|
||||
Tag: img.Tag,
|
||||
Digest: img.Digest,
|
||||
MimeType: v1.MimeTypeDockerArtifact,
|
||||
ctx := req.Context()
|
||||
art, err := artifact.Ctl.GetByReference(ctx, img.Repository, img.Digest, nil)
|
||||
if err != nil {
|
||||
// TODO: error handle
|
||||
return
|
||||
}
|
||||
|
||||
cve := report.CVESet(wl.CVESet())
|
||||
summaries, err := sc.DefaultController.GetSummary(
|
||||
artifact,
|
||||
[]string{v1.MimeTypeNativeReport},
|
||||
report.WithCVEWhitelist(&cve),
|
||||
)
|
||||
summaries, err := sc.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport}, report.WithCVEWhitelist(&cve))
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "middleware: vulnerable handler")
|
||||
|
@ -34,7 +34,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
evt "github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/assembler"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
@ -178,30 +177,6 @@ func (a *artifactAPI) CopyArtifact(ctx context.Context, params operation.CopyArt
|
||||
return operation.NewCopyArtifactCreated().WithLocation(location)
|
||||
}
|
||||
|
||||
func (a *artifactAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifactParams) middleware.Responder {
|
||||
if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, rbac.ResourceScan); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName)
|
||||
artifact, err := a.artCtl.GetByReference(ctx, repository, params.Reference, nil)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
art := &v1.Artifact{
|
||||
NamespaceID: artifact.ProjectID,
|
||||
Repository: repository,
|
||||
Digest: artifact.Digest,
|
||||
MimeType: artifact.ManifestMediaType,
|
||||
}
|
||||
if err := a.scanCtl.Scan(art); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewScanArtifactAccepted()
|
||||
}
|
||||
|
||||
// parse "repository:tag" or "repository@digest" into repository and reference parts
|
||||
func parse(s string) (string, string, error) {
|
||||
matches := reference.ReferenceRegexp.FindStringSubmatch(s)
|
||||
|
@ -72,16 +72,9 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
|
||||
artifact.SetAdditionLink(vulnerabilitiesAddition, version)
|
||||
|
||||
if assembler.withScanOverview {
|
||||
art := &v1.Artifact{
|
||||
NamespaceID: artifact.ProjectID,
|
||||
Repository: artifact.RepositoryName,
|
||||
Digest: artifact.Digest,
|
||||
MimeType: artifact.ManifestMediaType,
|
||||
}
|
||||
|
||||
overview, err := assembler.scanCtl.GetSummary(art, []string{v1.MimeTypeNativeReport})
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeNativeReport})
|
||||
if err != nil {
|
||||
log.Warningf("get scan summary of artifact %s failed, error:%v", artifact.Digest, err)
|
||||
log.Warningf("get scan summary of artifact %s@%s failed, error:%v", artifact.RepositoryName, artifact.Digest, err)
|
||||
} else if len(overview) > 0 {
|
||||
artifact.ScanOverview = overview
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ func New() http.Handler {
|
||||
ArtifactAPI: newArtifactAPI(),
|
||||
RepositoryAPI: newRepositoryAPI(),
|
||||
AuditlogAPI: newAuditLogAPI(),
|
||||
ScanAPI: newScanAPI(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -15,8 +15,11 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
@ -57,5 +60,22 @@ func (a *Artifact) ToSwagger() *models.Artifact {
|
||||
for _, label := range a.Labels {
|
||||
art.Labels = append(art.Labels, label.ToSwagger())
|
||||
}
|
||||
if len(a.ScanOverview) > 0 {
|
||||
art.ScanOverview = models.ScanOverview{}
|
||||
for key, value := range a.ScanOverview {
|
||||
js, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
log.Warningf("convert summary of %s failed, error: %v", key, err)
|
||||
continue
|
||||
}
|
||||
var summary models.NativeReportSummary
|
||||
if err := summary.UnmarshalBinary(js); err != nil {
|
||||
log.Warningf("convert summary of %s failed, error: %v", key, err)
|
||||
continue
|
||||
}
|
||||
|
||||
art.ScanOverview[key] = summary
|
||||
}
|
||||
}
|
||||
return art
|
||||
}
|
||||
|
90
src/server/v2.0/handler/scan.go
Normal file
90
src/server/v2.0/handler/scan.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright Project Harbor 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 handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/api/artifact"
|
||||
"github.com/goharbor/harbor/src/api/scan"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/scan"
|
||||
)
|
||||
|
||||
func newScanAPI() *scanAPI {
|
||||
return &scanAPI{
|
||||
artCtl: artifact.Ctl,
|
||||
scanCtl: scan.DefaultController,
|
||||
}
|
||||
}
|
||||
|
||||
type scanAPI struct {
|
||||
BaseAPI
|
||||
artCtl artifact.Controller
|
||||
scanCtl scan.Controller
|
||||
}
|
||||
|
||||
func (s *scanAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
|
||||
if err := unescapePathParams(params, "RepositoryName"); err != nil {
|
||||
s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scanAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifactParams) middleware.Responder {
|
||||
if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceScan); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName)
|
||||
artifact, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := s.scanCtl.Scan(ctx, artifact); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return operation.NewScanArtifactAccepted()
|
||||
}
|
||||
|
||||
func (s *scanAPI) GetReportLog(ctx context.Context, params operation.GetReportLogParams) middleware.Responder {
|
||||
if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceScan); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName)
|
||||
_, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
bytes, err := s.scanCtl.GetScanLog(params.ReportID)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if bytes == nil {
|
||||
// Not found
|
||||
return s.SendError(ctx, ierror.NotFoundError(nil).WithMessage("report with uuid %s does not exist", params.ReportID))
|
||||
}
|
||||
|
||||
return operation.NewGetReportLogOK().WithPayload(string(bytes))
|
||||
}
|
@ -38,14 +38,7 @@ func boolValue(v *bool) bool {
|
||||
}
|
||||
|
||||
func resolveVulnerabilitiesAddition(ctx context.Context, artifact *artifact.Artifact) (*processor.Addition, error) {
|
||||
art := &v1.Artifact{
|
||||
NamespaceID: artifact.ProjectID,
|
||||
Repository: artifact.RepositoryName,
|
||||
Digest: artifact.Digest,
|
||||
MimeType: artifact.ManifestMediaType,
|
||||
}
|
||||
|
||||
reports, err := scan.DefaultController.GetReport(art, []string{v1.MimeTypeNativeReport})
|
||||
reports, err := scan.DefaultController.GetReport(ctx, artifact, []string{v1.MimeTypeNativeReport})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3,7 +3,11 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
artifact "github.com/goharbor/harbor/src/api/artifact"
|
||||
all "github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
|
||||
context "context"
|
||||
|
||||
daoscan "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
|
||||
job "github.com/goharbor/harbor/src/jobservice/job"
|
||||
@ -13,8 +17,6 @@ import (
|
||||
report "github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
|
||||
scan "github.com/goharbor/harbor/src/api/scan"
|
||||
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
// Controller is an autogenerated mock type for the Controller type
|
||||
@ -42,13 +44,13 @@ func (_m *Controller) DeleteReports(digests ...string) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetReport provides a mock function with given fields: artifact, mimeTypes
|
||||
func (_m *Controller) GetReport(artifact *v1.Artifact, mimeTypes []string) ([]*daoscan.Report, error) {
|
||||
ret := _m.Called(artifact, mimeTypes)
|
||||
// GetReport provides a mock function with given fields: ctx, _a1, mimeTypes
|
||||
func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) ([]*daoscan.Report, error) {
|
||||
ret := _m.Called(ctx, _a1, mimeTypes)
|
||||
|
||||
var r0 []*daoscan.Report
|
||||
if rf, ok := ret.Get(0).(func(*v1.Artifact, []string) []*daoscan.Report); ok {
|
||||
r0 = rf(artifact, mimeTypes)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) []*daoscan.Report); ok {
|
||||
r0 = rf(ctx, _a1, mimeTypes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*daoscan.Report)
|
||||
@ -56,8 +58,8 @@ func (_m *Controller) GetReport(artifact *v1.Artifact, mimeTypes []string) ([]*d
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*v1.Artifact, []string) error); ok {
|
||||
r1 = rf(artifact, mimeTypes)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string) error); ok {
|
||||
r1 = rf(ctx, _a1, mimeTypes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@ -111,20 +113,20 @@ func (_m *Controller) GetStats(requester string) (*all.Stats, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSummary provides a mock function with given fields: artifact, mimeTypes, options
|
||||
func (_m *Controller) GetSummary(artifact *v1.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
// GetSummary provides a mock function with given fields: ctx, _a1, mimeTypes, options
|
||||
func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, artifact, mimeTypes)
|
||||
_ca = append(_ca, ctx, _a1, mimeTypes)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 map[string]interface{}
|
||||
if rf, ok := ret.Get(0).(func(*v1.Artifact, []string, ...report.Option) map[string]interface{}); ok {
|
||||
r0 = rf(artifact, mimeTypes, options...)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string, ...report.Option) map[string]interface{}); ok {
|
||||
r0 = rf(ctx, _a1, mimeTypes, options...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]interface{})
|
||||
@ -132,8 +134,8 @@ func (_m *Controller) GetSummary(artifact *v1.Artifact, mimeTypes []string, opti
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*v1.Artifact, []string, ...report.Option) error); ok {
|
||||
r1 = rf(artifact, mimeTypes, options...)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string, ...report.Option) error); ok {
|
||||
r1 = rf(ctx, _a1, mimeTypes, options...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@ -155,20 +157,20 @@ func (_m *Controller) HandleJobHooks(trackID string, change *job.StatusChange) e
|
||||
return r0
|
||||
}
|
||||
|
||||
// Scan provides a mock function with given fields: artifact, options
|
||||
func (_m *Controller) Scan(artifact *v1.Artifact, options ...scan.Option) error {
|
||||
// Scan provides a mock function with given fields: ctx, _a1, options
|
||||
func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options ...scan.Option) error {
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, artifact)
|
||||
_ca = append(_ca, ctx, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*v1.Artifact, ...scan.Option) error); ok {
|
||||
r0 = rf(artifact, options...)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...scan.Option) error); ok {
|
||||
r0 = rf(ctx, _a1, options...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
@ -17,3 +17,6 @@ package pkg
|
||||
//go:generate mockery -case snake -dir ../../pkg/blob -name Manager -output ./blob -outpkg blob
|
||||
//go:generate mockery -case snake -dir ../../pkg/quota -name Manager -output ./quota -outpkg quota
|
||||
//go:generate mockery -case snake -dir ../../pkg/quota/driver -name Driver -output ./quota/driver -outpkg driver
|
||||
//go:generate mockery -case snake -dir ../../pkg/scan/report -name Manager -output ./scan/report -outpkg report
|
||||
//go:generate mockery -case snake -dir ../../pkg/scan/rest/v1 -all -output ./scan/rest/v1 -outpkg v1
|
||||
//go:generate mockery -case snake -dir ../../pkg/scan/scanner -all -output ./scan/scanner -outpkg scanner
|
||||
|
167
src/testing/pkg/scan/report/manager.go
Normal file
167
src/testing/pkg/scan/report/manager.go
Normal file
@ -0,0 +1,167 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
all "github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
scan "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: r
|
||||
func (_m *Manager) Create(r *scan.Report) (string, error) {
|
||||
ret := _m.Called(r)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(*scan.Report) string); ok {
|
||||
r0 = rf(r)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*scan.Report) error); ok {
|
||||
r1 = rf(r)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteByDigests provides a mock function with given fields: digests
|
||||
func (_m *Manager) DeleteByDigests(digests ...string) error {
|
||||
_va := make([]interface{}, len(digests))
|
||||
for _i := range digests {
|
||||
_va[_i] = digests[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(...string) error); ok {
|
||||
r0 = rf(digests...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: uuid
|
||||
func (_m *Manager) Get(uuid string) (*scan.Report, error) {
|
||||
ret := _m.Called(uuid)
|
||||
|
||||
var r0 *scan.Report
|
||||
if rf, ok := ret.Get(0).(func(string) *scan.Report); ok {
|
||||
r0 = rf(uuid)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scan.Report)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(uuid)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBy provides a mock function with given fields: digest, registrationUUID, mimeTypes
|
||||
func (_m *Manager) GetBy(digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) {
|
||||
ret := _m.Called(digest, registrationUUID, mimeTypes)
|
||||
|
||||
var r0 []*scan.Report
|
||||
if rf, ok := ret.Get(0).(func(string, string, []string) []*scan.Report); ok {
|
||||
r0 = rf(digest, registrationUUID, mimeTypes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*scan.Report)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, []string) error); ok {
|
||||
r1 = rf(digest, registrationUUID, mimeTypes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetStats provides a mock function with given fields: requester
|
||||
func (_m *Manager) GetStats(requester string) (*all.Stats, error) {
|
||||
ret := _m.Called(requester)
|
||||
|
||||
var r0 *all.Stats
|
||||
if rf, ok := ret.Get(0).(func(string) *all.Stats); ok {
|
||||
r0 = rf(requester)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*all.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(requester)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateReportData provides a mock function with given fields: uuid, _a1, rev
|
||||
func (_m *Manager) UpdateReportData(uuid string, _a1 string, rev int64) error {
|
||||
ret := _m.Called(uuid, _a1, rev)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, int64) error); ok {
|
||||
r0 = rf(uuid, _a1, rev)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateScanJobID provides a mock function with given fields: trackID, jobID
|
||||
func (_m *Manager) UpdateScanJobID(trackID string, jobID string) error {
|
||||
ret := _m.Called(trackID, jobID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(trackID, jobID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateStatus provides a mock function with given fields: trackID, status, rev
|
||||
func (_m *Manager) UpdateStatus(trackID string, status string, rev int64) error {
|
||||
ret := _m.Called(trackID, status, rev)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, int64) error); ok {
|
||||
r0 = rf(trackID, status, rev)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
80
src/testing/pkg/scan/rest/v1/client.go
Normal file
80
src/testing/pkg/scan/rest/v1/client.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Client is an autogenerated mock type for the Client type
|
||||
type Client struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetMetadata provides a mock function with given fields:
|
||||
func (_m *Client) GetMetadata() (*v1.ScannerAdapterMetadata, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *v1.ScannerAdapterMetadata
|
||||
if rf, ok := ret.Get(0).(func() *v1.ScannerAdapterMetadata); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v1.ScannerAdapterMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetScanReport provides a mock function with given fields: scanRequestID, reportMIMEType
|
||||
func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (string, error) {
|
||||
ret := _m.Called(scanRequestID, reportMIMEType)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string, string) string); ok {
|
||||
r0 = rf(scanRequestID, reportMIMEType)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(scanRequestID, reportMIMEType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SubmitScan provides a mock function with given fields: req
|
||||
func (_m *Client) SubmitScan(req *v1.ScanRequest) (*v1.ScanResponse, error) {
|
||||
ret := _m.Called(req)
|
||||
|
||||
var r0 *v1.ScanResponse
|
||||
if rf, ok := ret.Get(0).(func(*v1.ScanRequest) *v1.ScanResponse); ok {
|
||||
r0 = rf(req)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v1.ScanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*v1.ScanRequest) error); ok {
|
||||
r1 = rf(req)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
36
src/testing/pkg/scan/rest/v1/client_pool.go
Normal file
36
src/testing/pkg/scan/rest/v1/client_pool.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ClientPool is an autogenerated mock type for the ClientPool type
|
||||
type ClientPool struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: url, authType, accessCredential, skipCertVerify
|
||||
func (_m *ClientPool) Get(url string, authType string, accessCredential string, skipCertVerify bool) (v1.Client, error) {
|
||||
ret := _m.Called(url, authType, accessCredential, skipCertVerify)
|
||||
|
||||
var r0 v1.Client
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, bool) v1.Client); ok {
|
||||
r0 = rf(url, authType, accessCredential, skipCertVerify)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(v1.Client)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, bool) error); ok {
|
||||
r1 = rf(url, authType, accessCredential, skipCertVerify)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
147
src/testing/pkg/scan/scanner/manager.go
Normal file
147
src/testing/pkg/scan/scanner/manager.go
Normal file
@ -0,0 +1,147 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
package scanner
|
||||
|
||||
import (
|
||||
q "github.com/goharbor/harbor/src/pkg/q"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
scanner "github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
)
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: registration
|
||||
func (_m *Manager) Create(registration *scanner.Registration) (string, error) {
|
||||
ret := _m.Called(registration)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(*scanner.Registration) string); ok {
|
||||
r0 = rf(registration)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*scanner.Registration) error); ok {
|
||||
r1 = rf(registration)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: registrationUUID
|
||||
func (_m *Manager) Delete(registrationUUID string) error {
|
||||
ret := _m.Called(registrationUUID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(registrationUUID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: registrationUUID
|
||||
func (_m *Manager) Get(registrationUUID string) (*scanner.Registration, error) {
|
||||
ret := _m.Called(registrationUUID)
|
||||
|
||||
var r0 *scanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(string) *scanner.Registration); ok {
|
||||
r0 = rf(registrationUUID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scanner.Registration)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(registrationUUID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetDefault provides a mock function with given fields:
|
||||
func (_m *Manager) GetDefault() (*scanner.Registration, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *scanner.Registration
|
||||
if rf, ok := ret.Get(0).(func() *scanner.Registration); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scanner.Registration)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: query
|
||||
func (_m *Manager) List(query *q.Query) ([]*scanner.Registration, error) {
|
||||
ret := _m.Called(query)
|
||||
|
||||
var r0 []*scanner.Registration
|
||||
if rf, ok := ret.Get(0).(func(*q.Query) []*scanner.Registration); ok {
|
||||
r0 = rf(query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*scanner.Registration)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*q.Query) error); ok {
|
||||
r1 = rf(query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetAsDefault provides a mock function with given fields: registrationUUID
|
||||
func (_m *Manager) SetAsDefault(registrationUUID string) error {
|
||||
ret := _m.Called(registrationUUID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(registrationUUID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: registration
|
||||
func (_m *Manager) Update(registration *scanner.Registration) error {
|
||||
ret := _m.Called(registration)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*scanner.Registration) error); ok {
|
||||
r0 = rf(registration)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
Loading…
Reference in New Issue
Block a user