mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 04:17:55 +08:00
refactor(cve): improve CVE test time by mocking trivy (#1184)
- refactor(cve): remove the global of type cveinfo.CveInfo from the extensions package Replace it with an attribute on controller level - refactor(controller): extract initialization logic from controller.Run() - test(cve): mock cve scanner in cli tests Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
+160
-6
@@ -5,8 +5,12 @@ package cli //nolint:testpackage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
@@ -14,6 +18,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
regTypes "github.com/google/go-containerregistry/pkg/v1/types"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -21,7 +28,12 @@ import (
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
func TestSearchCVECmd(t *testing.T) {
|
||||
@@ -441,9 +453,23 @@ func TestNegativeServerResponse(t *testing.T) {
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Log.Logger = ctlr.Log.Output(writers)
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
ctx := context.Background()
|
||||
|
||||
if err := ctlr.Init(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctlr.CveInfo = getMockCveInfo(ctlr.RepoDB, ctlr.Log)
|
||||
|
||||
go func() {
|
||||
if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctlr.Shutdown()
|
||||
|
||||
test.WaitTillServerReady(url)
|
||||
|
||||
_, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second)
|
||||
if err != nil {
|
||||
@@ -504,10 +530,23 @@ func TestServerCVEResponse(t *testing.T) {
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Log.Logger = ctlr.Log.Output(writers)
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
ctx := context.Background()
|
||||
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
if err := ctlr.Init(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctlr.CveInfo = getMockCveInfo(ctlr.RepoDB, ctlr.Log)
|
||||
|
||||
go func() {
|
||||
if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctlr.Shutdown()
|
||||
|
||||
test.WaitTillServerReady(url)
|
||||
|
||||
_, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second)
|
||||
if err != nil {
|
||||
@@ -988,3 +1027,118 @@ func MockSearchCve(searchConfig searchConfig) error {
|
||||
|
||||
return zotErrors.ErrInvalidFlagsCombination
|
||||
}
|
||||
|
||||
func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||
// RepoDB loaded with initial data, mock the scanner
|
||||
severities := map[string]int{
|
||||
"UNKNOWN": 0,
|
||||
"LOW": 1,
|
||||
"MEDIUM": 2,
|
||||
"HIGH": 3,
|
||||
"CRITICAL": 4,
|
||||
}
|
||||
|
||||
// Setup test CVE data in mock scanner
|
||||
scanner := mocks.CveScannerMock{
|
||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||
if image == "zot-cve-test:0.0.1" {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE-1": {
|
||||
ID: "CVE-1",
|
||||
Severity: "CRITICAL",
|
||||
Title: "Title for CVE-C1",
|
||||
Description: "Description of CVE-1",
|
||||
},
|
||||
"CVE-2019-9923": {
|
||||
ID: "CVE-2019-9923",
|
||||
Severity: "HIGH",
|
||||
Title: "Title for CVE-2",
|
||||
Description: "Description of CVE-2",
|
||||
},
|
||||
"CVE-3": {
|
||||
ID: "CVE-3",
|
||||
Severity: "MEDIUM",
|
||||
Title: "Title for CVE-3",
|
||||
Description: "Description of CVE-3",
|
||||
},
|
||||
"CVE-4": {
|
||||
ID: "CVE-4",
|
||||
Severity: "LOW",
|
||||
Title: "Title for CVE-4",
|
||||
Description: "Description of CVE-4",
|
||||
},
|
||||
"CVE-5": {
|
||||
ID: "CVE-5",
|
||||
Severity: "UNKNOWN",
|
||||
Title: "Title for CVE-5",
|
||||
Description: "Description of CVE-5",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// By default the image has no vulnerabilities
|
||||
return map[string]cvemodel.CVE{}, nil
|
||||
},
|
||||
CompareSeveritiesFn: func(severity1, severity2 string) int {
|
||||
return severities[severity2] - severities[severity1]
|
||||
},
|
||||
IsImageFormatScannableFn: func(image string) (bool, error) {
|
||||
// Almost same logic compared to actual Trivy specific implementation
|
||||
var imageDir string
|
||||
|
||||
var inputTag string
|
||||
|
||||
if strings.Contains(image, ":") {
|
||||
imageDir, inputTag, _ = strings.Cut(image, ":")
|
||||
} else {
|
||||
imageDir = image
|
||||
}
|
||||
|
||||
repoMeta, err := repoDB.GetRepoMeta(imageDir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zotErrors.ErrTagMetaNotFound
|
||||
}
|
||||
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
manifestData, err := repoDB.GetManifestData(manifestDigest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var manifestContent ispec.Manifest
|
||||
|
||||
err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent)
|
||||
if err != nil {
|
||||
return false, zotErrors.ErrScanNotSupported
|
||||
}
|
||||
|
||||
for _, imageLayer := range manifestContent.Layers {
|
||||
switch imageLayer.MediaType {
|
||||
case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
|
||||
|
||||
return true, nil
|
||||
default:
|
||||
|
||||
return false, zotErrors.ErrScanNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
},
|
||||
}
|
||||
|
||||
return &cveinfo.BaseCveInfo{
|
||||
Log: log,
|
||||
Scanner: scanner,
|
||||
RepoDB: repoDB,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1292,7 +1292,7 @@ func TestServerResponseGQLWithoutPermissions(t *testing.T) {
|
||||
}
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
if err := ctlr.Run(context.Background()); err != nil {
|
||||
if err := ctlr.Init(context.Background()); err != nil {
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -63,6 +63,10 @@ func newServeCmd(conf *config.Config) *cobra.Command {
|
||||
we can change their config on the fly (restart routines with different config) */
|
||||
reloaderCtx := hotReloader.Start()
|
||||
|
||||
if err := ctlr.Init(reloaderCtx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := ctlr.Run(reloaderCtx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+33
-8
@@ -78,17 +78,42 @@ func TestServe(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("bad config", func(c C) {
|
||||
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
||||
rootDir := t.TempDir()
|
||||
|
||||
tmpFile := path.Join(rootDir, "zot-test.json")
|
||||
err := os.WriteFile(tmpFile, []byte(`{"log":{}}`), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
content := []byte(`{"log":{}}`)
|
||||
_, err = tmpfile.Write(content)
|
||||
So(err, ShouldBeNil)
|
||||
err = tmpfile.Close()
|
||||
So(err, ShouldBeNil)
|
||||
os.Args = []string{"cli_test", "serve", tmpfile.Name()}
|
||||
|
||||
os.Args = []string{"cli_test", "serve", tmpFile}
|
||||
|
||||
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("config with missing rootDir", func(c C) {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
// missing storag config should result in an error in Controller.Init()
|
||||
content := []byte(`{
|
||||
"distSpecVersion": "1.1.0-dev",
|
||||
"http": {
|
||||
"address":"127.0.0.1",
|
||||
"port":"8080"
|
||||
}
|
||||
}`)
|
||||
|
||||
tmpFile := path.Join(rootDir, "zot-test.json")
|
||||
err := os.WriteFile(tmpFile, content, 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
os.Args = []string{"cli_test", "serve", tmpFile}
|
||||
|
||||
So(func() { _ = cli.NewServerRootCmd().Execute() }, ShouldPanic)
|
||||
|
||||
// wait for the config reloader goroutine to start watching the config file
|
||||
// if we end the test too fast it will delete the config file
|
||||
// which will cause a panic and mark the test run as a failure
|
||||
time.Sleep(1 * time.Second)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user