mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-25 01:58:35 +01:00
Support list artifact with_sbom_overview option (#20244)
Signed-off-by: stonezdj <stone.zhang@broadcom.com> Co-authored-by: stonezdj <daojunz@vmware.com>
This commit is contained in:
parent
89995075a7
commit
5d7c668028
@ -17,6 +17,7 @@ package scan
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -674,12 +675,23 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact,
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func isSBOMMimeTypes(mimeTypes []string) bool {
|
||||
for _, mimeType := range mimeTypes {
|
||||
if mimeType == v1.MimeTypeSBOMReport {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSummary ...
|
||||
func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
if artifact == nil {
|
||||
return nil, errors.New("no way to get report summaries for nil artifact")
|
||||
}
|
||||
|
||||
if isSBOMMimeTypes(mimeTypes) {
|
||||
return bc.GetSBOMSummary(ctx, artifact, mimeTypes)
|
||||
}
|
||||
// Get reports first
|
||||
rps, err := bc.GetReport(ctx, artifact, mimeTypes)
|
||||
if err != nil {
|
||||
@ -708,6 +720,30 @@ func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact
|
||||
return summaries, nil
|
||||
}
|
||||
|
||||
func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
if art == nil {
|
||||
return nil, errors.New("no way to get report summaries for nil artifact")
|
||||
}
|
||||
r, err := bc.sc.GetRegistrationByProject(ctx, art.ProjectID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scan controller: get sbom summary")
|
||||
}
|
||||
reports, err := bc.manager.GetBy(ctx, art.Digest, r.UUID, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(reports) == 0 {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
reportContent := reports[0].Report
|
||||
if len(reportContent) == 0 {
|
||||
log.Warning("no content for current report")
|
||||
}
|
||||
result := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(reportContent), &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetScanLog ...
|
||||
func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) {
|
||||
if len(uuid) == 0 {
|
||||
|
@ -78,7 +78,7 @@ type ControllerTestSuite struct {
|
||||
taskMgr *tasktesting.Manager
|
||||
reportMgr *reporttesting.Manager
|
||||
ar artifact.Controller
|
||||
c Controller
|
||||
c *basicController
|
||||
reportConverter *postprocessorstesting.ScanReportV1ToV2Converter
|
||||
cache *mockcache.Cache
|
||||
}
|
||||
@ -180,7 +180,19 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
},
|
||||
}
|
||||
|
||||
sbomReport := []*scan.Report{
|
||||
{
|
||||
ID: 12,
|
||||
UUID: "rp-uuid-002",
|
||||
Digest: "digest-code",
|
||||
RegistrationUUID: "uuid001",
|
||||
MimeType: "application/vnd.scanner.adapter.sbom.report.harbor+json; version=1.0",
|
||||
Status: "Success",
|
||||
Report: `{"sbom_digest": "sha256:1234567890", "scan_status": "Success", "duration": 3, "start_time": "2021-09-01T00:00:00Z", "end_time": "2021-09-01T00:00:03Z"}`,
|
||||
},
|
||||
}
|
||||
mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeNativeReport}).Return(reports, nil)
|
||||
mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil)
|
||||
mgr.On("Get", mock.Anything, "rp-uuid-001").Return(reports[0], nil)
|
||||
mgr.On("UpdateReportData", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil)
|
||||
mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil)
|
||||
@ -620,3 +632,26 @@ func (suite *ControllerTestSuite) makeExtraAttrs(artifactID int64, reportUUIDs .
|
||||
|
||||
return extraAttrs
|
||||
}
|
||||
|
||||
func (suite *ControllerTestSuite) TestGenerateSBOMSummary() {
|
||||
sum, err := suite.c.GetSBOMSummary(context.TODO(), suite.artifact, []string{v1.MimeTypeSBOMReport})
|
||||
suite.Nil(err)
|
||||
suite.NotNil(sum)
|
||||
status := sum["scan_status"]
|
||||
suite.NotNil(status)
|
||||
dgst := sum["sbom_digest"]
|
||||
suite.NotNil(dgst)
|
||||
suite.Equal("Success", status)
|
||||
suite.Equal("sha256:1234567890", dgst)
|
||||
}
|
||||
|
||||
func TestIsSBOMMimeTypes(t *testing.T) {
|
||||
// Test with a slice containing the SBOM mime type
|
||||
assert.True(t, isSBOMMimeTypes([]string{v1.MimeTypeSBOMReport}))
|
||||
|
||||
// Test with a slice not containing the SBOM mime type
|
||||
assert.False(t, isSBOMMimeTypes([]string{"application/vnd.oci.image.manifest.v1+json"}))
|
||||
|
||||
// Test with an empty slice
|
||||
assert.False(t, isSBOMMimeTypes([]string{}))
|
||||
}
|
||||
|
@ -21,12 +21,11 @@ import (
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScanTypeVulnerability the scan type for vulnerability
|
||||
ScanTypeVulnerability = "vulnerability"
|
||||
// ScanTypeSbom the scan type for sbom
|
||||
ScanTypeSbom = "sbom"
|
||||
)
|
||||
var supportedMimeTypes = []string{
|
||||
MimeTypeNativeReport,
|
||||
MimeTypeGenericVulnerabilityReport,
|
||||
MimeTypeSBOMReport,
|
||||
}
|
||||
|
||||
// Scanner represents metadata of a Scanner Adapter which allow Harbor to lookup a scanner capable of
|
||||
// scanning a given Artifact stored in its registry and making sure that it can interpret a
|
||||
@ -105,7 +104,7 @@ func (md *ScannerAdapterMetadata) Validate() error {
|
||||
// either of v1.MimeTypeNativeReport OR v1.MimeTypeGenericVulnerabilityReport is required
|
||||
found = false
|
||||
for _, pm := range ca.ProducesMimeTypes {
|
||||
if pm == MimeTypeNativeReport || pm == MimeTypeGenericVulnerabilityReport {
|
||||
if isSupportedMimeType(pm) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@ -119,6 +118,15 @@ func (md *ScannerAdapterMetadata) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSupportedMimeType(mimeType string) bool {
|
||||
for _, mt := range supportedMimeTypes {
|
||||
if mt == mimeType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasCapability returns true when mine type of the artifact support by the scanner
|
||||
func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool {
|
||||
for _, capability := range md.Capabilities {
|
||||
|
15
src/pkg/scan/rest/v1/models_test.go
Normal file
15
src/pkg/scan/rest/v1/models_test.go
Normal file
@ -0,0 +1,15 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsSupportedMimeType(t *testing.T) {
|
||||
// Test with a supported mime type
|
||||
assert.True(t, isSupportedMimeType(MimeTypeSBOMReport), "isSupportedMimeType should return true for supported mime types")
|
||||
|
||||
// Test with an unsupported mime type
|
||||
assert.False(t, isSupportedMimeType("unsupported/mime-type"), "isSupportedMimeType should return false for unsupported mime types")
|
||||
}
|
@ -39,9 +39,14 @@ const (
|
||||
MimeTypeScanRequest = "application/vnd.scanner.adapter.scan.request+json; version=1.0"
|
||||
// MimeTypeScanResponse defines the mime type for scan response
|
||||
MimeTypeScanResponse = "application/vnd.scanner.adapter.scan.response+json; version=1.0"
|
||||
// MimeTypeSBOMReport
|
||||
MimeTypeSBOMReport = "application/vnd.security.sbom.report+json; version=1.0"
|
||||
// MimeTypeGenericVulnerabilityReport defines the MIME type for the generic report with enhanced information
|
||||
MimeTypeGenericVulnerabilityReport = "application/vnd.security.vulnerability.report; version=1.1"
|
||||
|
||||
ScanTypeVulnerability = "vulnerability"
|
||||
ScanTypeSbom = "sbom"
|
||||
|
||||
apiPrefix = "/api/v1"
|
||||
)
|
||||
|
||||
|
@ -107,8 +107,8 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
assembler := assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities))
|
||||
overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview)))
|
||||
assembler := assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities))
|
||||
var artifacts []*models.Artifact
|
||||
for _, art := range arts {
|
||||
artifact := &model.Artifact{}
|
||||
@ -138,8 +138,9 @@ func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtif
|
||||
}
|
||||
art := &model.Artifact{}
|
||||
art.Artifact = *artifact
|
||||
overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview)))
|
||||
|
||||
err = assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx)
|
||||
err = assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx)
|
||||
if err != nil {
|
||||
log.Warningf("failed to assemble vulnerabilities with artifact, error: %v", err)
|
||||
}
|
||||
|
@ -20,43 +20,48 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
)
|
||||
|
||||
const (
|
||||
vulnerabilitiesAddition = "vulnerabilities"
|
||||
startTime = "start_time"
|
||||
endTime = "end_time"
|
||||
scanStatus = "scan_status"
|
||||
sbomDigest = "sbom_digest"
|
||||
duration = "duration"
|
||||
)
|
||||
|
||||
// NewVulAssembler returns vul assembler
|
||||
func NewVulAssembler(withScanOverview bool, mimeTypes []string) *VulAssembler {
|
||||
return &VulAssembler{
|
||||
scanChecker: scan.NewChecker(),
|
||||
scanCtl: scan.DefaultController,
|
||||
|
||||
withScanOverview: withScanOverview,
|
||||
mimeTypes: mimeTypes,
|
||||
// NewScanReportAssembler returns vul assembler
|
||||
func NewScanReportAssembler(option *model.OverviewOptions, mimeTypes []string) *ScanReportAssembler {
|
||||
return &ScanReportAssembler{
|
||||
overviewOption: option,
|
||||
scanChecker: scan.NewChecker(),
|
||||
scanCtl: scan.DefaultController,
|
||||
mimeTypes: mimeTypes,
|
||||
}
|
||||
}
|
||||
|
||||
// VulAssembler vul assembler
|
||||
type VulAssembler struct {
|
||||
// ScanReportAssembler vul assembler
|
||||
type ScanReportAssembler struct {
|
||||
scanChecker scan.Checker
|
||||
scanCtl scan.Controller
|
||||
|
||||
artifacts []*model.Artifact
|
||||
withScanOverview bool
|
||||
mimeTypes []string
|
||||
artifacts []*model.Artifact
|
||||
mimeTypes []string
|
||||
overviewOption *model.OverviewOptions
|
||||
}
|
||||
|
||||
// WithArtifacts set artifacts for the assembler
|
||||
func (assembler *VulAssembler) WithArtifacts(artifacts ...*model.Artifact) *VulAssembler {
|
||||
func (assembler *ScanReportAssembler) WithArtifacts(artifacts ...*model.Artifact) *ScanReportAssembler {
|
||||
assembler.artifacts = artifacts
|
||||
|
||||
return assembler
|
||||
}
|
||||
|
||||
// Assemble assemble vul for the artifacts
|
||||
func (assembler *VulAssembler) Assemble(ctx context.Context) error {
|
||||
func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error {
|
||||
version := lib.GetAPIVersion(ctx)
|
||||
|
||||
for _, artifact := range assembler.artifacts {
|
||||
@ -72,7 +77,7 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
|
||||
|
||||
artifact.SetAdditionLink(vulnerabilitiesAddition, version)
|
||||
|
||||
if assembler.withScanOverview {
|
||||
if assembler.overviewOption.WithVuln {
|
||||
for _, mimeType := range assembler.mimeTypes {
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType})
|
||||
if err != nil {
|
||||
@ -83,6 +88,20 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if assembler.overviewOption.WithSBOM {
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport})
|
||||
if err != nil {
|
||||
log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err)
|
||||
} else if len(overview) > 0 {
|
||||
artifact.SBOMOverView = map[string]interface{}{
|
||||
startTime: overview[startTime],
|
||||
endTime: overview[endTime],
|
||||
scanStatus: overview[scanStatus],
|
||||
sbomDigest: overview[sbomDigest],
|
||||
duration: overview[duration],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
@ -33,11 +34,11 @@ func (suite *VulAssemblerTestSuite) TestScannable() {
|
||||
checker := &scan.Checker{}
|
||||
scanCtl := &scan.Controller{}
|
||||
|
||||
assembler := VulAssembler{
|
||||
scanChecker: checker,
|
||||
scanCtl: scanCtl,
|
||||
withScanOverview: true,
|
||||
mimeTypes: []string{"mimeType"},
|
||||
assembler := ScanReportAssembler{
|
||||
scanChecker: checker,
|
||||
scanCtl: scanCtl,
|
||||
overviewOption: model.NewOverviewOptions(model.WithVuln(true)),
|
||||
mimeTypes: []string{"mimeType"},
|
||||
}
|
||||
|
||||
mock.OnAnything(checker, "IsScannable").Return(true, nil)
|
||||
@ -56,10 +57,10 @@ func (suite *VulAssemblerTestSuite) TestNotScannable() {
|
||||
checker := &scan.Checker{}
|
||||
scanCtl := &scan.Controller{}
|
||||
|
||||
assembler := VulAssembler{
|
||||
scanChecker: checker,
|
||||
scanCtl: scanCtl,
|
||||
withScanOverview: true,
|
||||
assembler := ScanReportAssembler{
|
||||
scanChecker: checker,
|
||||
scanCtl: scanCtl,
|
||||
overviewOption: model.NewOverviewOptions(model.WithVuln(true)),
|
||||
}
|
||||
|
||||
mock.OnAnything(checker, "IsScannable").Return(false, nil)
|
||||
@ -74,6 +75,32 @@ func (suite *VulAssemblerTestSuite) TestNotScannable() {
|
||||
scanCtl.AssertNotCalled(suite.T(), "GetSummary")
|
||||
}
|
||||
|
||||
func (suite *VulAssemblerTestSuite) TestAssembleSBOMOverview() {
|
||||
checker := &scan.Checker{}
|
||||
scanCtl := &scan.Controller{}
|
||||
|
||||
assembler := ScanReportAssembler{
|
||||
scanChecker: checker,
|
||||
scanCtl: scanCtl,
|
||||
overviewOption: model.NewOverviewOptions(model.WithSBOM(true)),
|
||||
mimeTypes: []string{v1sq.MimeTypeSBOMReport},
|
||||
}
|
||||
|
||||
mock.OnAnything(checker, "IsScannable").Return(true, nil)
|
||||
overview := map[string]interface{}{
|
||||
"sbom_digest": "sha256:123456",
|
||||
"scan_status": "Success",
|
||||
}
|
||||
mock.OnAnything(scanCtl, "GetSummary").Return(overview, nil)
|
||||
|
||||
var artifact model.Artifact
|
||||
err := assembler.WithArtifacts(&artifact).Assemble(context.TODO())
|
||||
suite.Nil(err)
|
||||
suite.Equal(artifact.SBOMOverView["sbom_digest"], "sha256:123456")
|
||||
suite.Equal(artifact.SBOMOverView["scan_status"], "Success")
|
||||
|
||||
}
|
||||
|
||||
func TestVulAssemblerTestSuite(t *testing.T) {
|
||||
suite.Run(t, &VulAssemblerTestSuite{})
|
||||
}
|
@ -28,7 +28,9 @@ import (
|
||||
// Artifact model
|
||||
type Artifact struct {
|
||||
artifact.Artifact
|
||||
// TODO: rename to VulOverview
|
||||
ScanOverview map[string]interface{} `json:"scan_overview"`
|
||||
SBOMOverView map[string]interface{} `json:"sbom_overview"`
|
||||
}
|
||||
|
||||
// ToSwagger converts the artifact to the swagger model
|
||||
@ -84,6 +86,18 @@ func (a *Artifact) ToSwagger() *models.Artifact {
|
||||
art.ScanOverview[key] = summary
|
||||
}
|
||||
}
|
||||
if len(a.SBOMOverView) > 0 {
|
||||
js, err := json.Marshal(a.SBOMOverView)
|
||||
if err != nil {
|
||||
log.Warningf("convert sbom summary failed, error: %v", err)
|
||||
}
|
||||
sbomOverview := &models.SBOMOverview{}
|
||||
err = json.Unmarshal(js, sbomOverview)
|
||||
if err != nil {
|
||||
log.Warningf("failed to get sbom summary: error: %v", err)
|
||||
}
|
||||
art.SbomOverview = sbomOverview
|
||||
}
|
||||
return art
|
||||
}
|
||||
|
||||
|
47
src/server/v2.0/handler/model/option.go
Normal file
47
src/server/v2.0/handler/model/option.go
Normal file
@ -0,0 +1,47 @@
|
||||
// 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 model
|
||||
|
||||
// OverviewOptions define the option to query overview info
|
||||
type OverviewOptions struct {
|
||||
WithVuln bool
|
||||
WithSBOM bool
|
||||
}
|
||||
|
||||
// Option define the func to build options
|
||||
type Option func(*OverviewOptions)
|
||||
|
||||
// NewOverviewOptions create a new OverviewOptions
|
||||
func NewOverviewOptions(options ...Option) *OverviewOptions {
|
||||
opts := &OverviewOptions{}
|
||||
for _, f := range options {
|
||||
f(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// WithVuln set the option to query vulnerability info
|
||||
func WithVuln(enable bool) Option {
|
||||
return func(o *OverviewOptions) {
|
||||
o.WithVuln = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithSBOM set the option to query SBOM info
|
||||
func WithSBOM(enable bool) Option {
|
||||
return func(o *OverviewOptions) {
|
||||
o.WithSBOM = enable
|
||||
}
|
||||
}
|
33
src/server/v2.0/handler/model/option_test.go
Normal file
33
src/server/v2.0/handler/model/option_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOverviewOptions(t *testing.T) {
|
||||
// Test NewOverviewOptions with WithVuln and WithSBOM
|
||||
opts := NewOverviewOptions(WithVuln(true), WithSBOM(true))
|
||||
assert.True(t, opts.WithVuln)
|
||||
assert.True(t, opts.WithSBOM)
|
||||
|
||||
// Test NewOverviewOptions with WithVuln and WithSBOM set to false
|
||||
opts = NewOverviewOptions(WithVuln(false), WithSBOM(false))
|
||||
assert.False(t, opts.WithVuln)
|
||||
assert.False(t, opts.WithSBOM)
|
||||
}
|
Loading…
Reference in New Issue
Block a user