zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/cve_cmd_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package client_test
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"os"
    14  	"path"
    15  	"regexp"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	regTypes "github.com/google/go-containerregistry/pkg/v1/types"
    21  	godigest "github.com/opencontainers/go-digest"
    22  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  	. "github.com/smartystreets/goconvey/convey"
    24  
    25  	zerr "zotregistry.dev/zot/errors"
    26  	"zotregistry.dev/zot/pkg/api"
    27  	"zotregistry.dev/zot/pkg/api/config"
    28  	"zotregistry.dev/zot/pkg/cli/client"
    29  	zcommon "zotregistry.dev/zot/pkg/common"
    30  	extconf "zotregistry.dev/zot/pkg/extensions/config"
    31  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    32  	cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
    33  	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
    34  	"zotregistry.dev/zot/pkg/log"
    35  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    36  	"zotregistry.dev/zot/pkg/storage"
    37  	"zotregistry.dev/zot/pkg/storage/local"
    38  	test "zotregistry.dev/zot/pkg/test/common"
    39  	. "zotregistry.dev/zot/pkg/test/image-utils"
    40  	"zotregistry.dev/zot/pkg/test/mocks"
    41  	ociutils "zotregistry.dev/zot/pkg/test/oci-utils"
    42  )
    43  
    44  func TestNegativeServerResponse(t *testing.T) {
    45  	Convey("Test from real server without search endpoint", t, func() {
    46  		port := test.GetFreePort()
    47  		url := test.GetBaseURL(port)
    48  		conf := config.New()
    49  		conf.HTTP.Port = port
    50  
    51  		dir := t.TempDir()
    52  
    53  		srcStorageCtlr := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", ""))
    54  		err := WriteImageToFileSystem(CreateDefaultVulnerableImage(), "zot-cve-test", "0.0.1", srcStorageCtlr)
    55  		So(err, ShouldBeNil)
    56  
    57  		conf.Storage.RootDirectory = dir
    58  		trivyConfig := &extconf.TrivyConfig{
    59  			DBRepository: "ghcr.io/project-zot/trivy-db",
    60  		}
    61  		cveConfig := &extconf.CVEConfig{
    62  			UpdateInterval: 2,
    63  			Trivy:          trivyConfig,
    64  		}
    65  		defaultVal := false
    66  		searchConfig := &extconf.SearchConfig{
    67  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    68  			CVE:        cveConfig,
    69  		}
    70  		conf.Extensions = &extconf.ExtensionConfig{
    71  			Search: searchConfig,
    72  		}
    73  
    74  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
    75  		if err != nil {
    76  			panic(err)
    77  		}
    78  
    79  		logPath := logFile.Name()
    80  		defer os.Remove(logPath)
    81  
    82  		writers := io.MultiWriter(os.Stdout, logFile)
    83  
    84  		ctlr := api.NewController(conf)
    85  		ctlr.Log.Logger = ctlr.Log.Output(writers)
    86  
    87  		cm := test.NewControllerManager(ctlr)
    88  		cm.StartAndWait(conf.HTTP.Port)
    89  		defer cm.StopServer()
    90  
    91  		_, err = test.ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second)
    92  		if err != nil {
    93  			panic(err)
    94  		}
    95  
    96  		Convey("Status Code Not Found", func() {
    97  			args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"}
    98  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
    99  			defer os.Remove(configPath)
   100  			cveCmd := client.NewCVECommand(client.NewSearchService())
   101  			buff := bytes.NewBufferString("")
   102  			cveCmd.SetOut(buff)
   103  			cveCmd.SetErr(buff)
   104  			cveCmd.SetArgs(args)
   105  			err = cveCmd.Execute()
   106  			So(err, ShouldNotBeNil)
   107  			So(err.Error(), ShouldContainSubstring, zerr.ErrExtensionNotEnabled.Error())
   108  		})
   109  	})
   110  
   111  	Convey("Test non-existing manifest blob", t, func() {
   112  		port := test.GetFreePort()
   113  		url := test.GetBaseURL(port)
   114  		conf := config.New()
   115  		conf.HTTP.Port = port
   116  
   117  		dir := t.TempDir()
   118  
   119  		imageStore := local.NewImageStore(dir, false, false,
   120  			log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil)
   121  
   122  		storeController := storage.StoreController{
   123  			DefaultStore: imageStore,
   124  		}
   125  
   126  		image := CreateRandomImage()
   127  
   128  		err := WriteImageToFileSystem(image, "zot-cve-test", "0.0.1", storeController)
   129  		So(err, ShouldBeNil)
   130  
   131  		err = os.RemoveAll(path.Join(dir, "zot-cve-test/blobs"))
   132  		if err != nil {
   133  			panic(err)
   134  		}
   135  
   136  		conf.Storage.RootDirectory = dir
   137  		trivyConfig := &extconf.TrivyConfig{
   138  			DBRepository: "ghcr.io/project-zot/trivy-db",
   139  		}
   140  		cveConfig := &extconf.CVEConfig{
   141  			UpdateInterval: 2,
   142  			Trivy:          trivyConfig,
   143  		}
   144  		defaultVal := true
   145  		searchConfig := &extconf.SearchConfig{
   146  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   147  			CVE:        cveConfig,
   148  		}
   149  		conf.Extensions = &extconf.ExtensionConfig{
   150  			Search: searchConfig,
   151  		}
   152  
   153  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   154  		if err != nil {
   155  			panic(err)
   156  		}
   157  
   158  		logPath := logFile.Name()
   159  		defer os.Remove(logPath)
   160  
   161  		writers := io.MultiWriter(os.Stdout, logFile)
   162  
   163  		ctlr := api.NewController(conf)
   164  		ctlr.Log.Logger = ctlr.Log.Output(writers)
   165  
   166  		if err := ctlr.Init(); err != nil {
   167  			panic(err)
   168  		}
   169  
   170  		ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
   171  
   172  		go func() {
   173  			if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
   174  				panic(err)
   175  			}
   176  		}()
   177  
   178  		defer ctlr.Shutdown()
   179  
   180  		test.WaitTillServerReady(url)
   181  
   182  		_, err = test.ReadLogFileAndSearchString(logPath, "cve-db update completed, next update scheduled after interval",
   183  			90*time.Second)
   184  		if err != nil {
   185  			panic(err)
   186  		}
   187  
   188  		args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"}
   189  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   190  		defer os.Remove(configPath)
   191  		cveCmd := client.NewCVECommand(client.NewSearchService())
   192  		buff := bytes.NewBufferString("")
   193  		cveCmd.SetOut(buff)
   194  		cveCmd.SetErr(buff)
   195  		cveCmd.SetArgs(args)
   196  		err = cveCmd.Execute()
   197  		So(err, ShouldNotBeNil)
   198  	})
   199  }
   200  
   201  func TestCVEDiffList(t *testing.T) {
   202  	port := test.GetFreePort()
   203  	url := test.GetBaseURL(port)
   204  	conf := config.New()
   205  	conf.HTTP.Port = port
   206  
   207  	dir := t.TempDir()
   208  
   209  	conf.Storage.RootDirectory = dir
   210  	trivyConfig := &extconf.TrivyConfig{
   211  		DBRepository: "ghcr.io/project-zot/trivy-db",
   212  	}
   213  	cveConfig := &extconf.CVEConfig{
   214  		UpdateInterval: 2,
   215  		Trivy:          trivyConfig,
   216  	}
   217  	defaultVal := true
   218  	searchConfig := &extconf.SearchConfig{
   219  		BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   220  		CVE:        cveConfig,
   221  	}
   222  	conf.Extensions = &extconf.ExtensionConfig{
   223  		Search: searchConfig,
   224  	}
   225  
   226  	logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   227  	if err != nil {
   228  		panic(err)
   229  	}
   230  
   231  	logPath := logFile.Name()
   232  	defer os.Remove(logPath)
   233  
   234  	writers := io.MultiWriter(os.Stdout, logFile)
   235  
   236  	ctlr := api.NewController(conf)
   237  	ctlr.Log.Logger = ctlr.Log.Output(writers)
   238  
   239  	if err := ctlr.Init(); err != nil {
   240  		panic(err)
   241  	}
   242  
   243  	layer1 := []byte{10, 20, 30}
   244  	layer2 := []byte{11, 21, 31}
   245  	layer3 := []byte{12, 22, 23}
   246  
   247  	otherImage := CreateImageWith().LayerBlobs([][]byte{
   248  		layer1,
   249  	}).DefaultConfig().Build()
   250  
   251  	baseImage := CreateImageWith().LayerBlobs([][]byte{
   252  		layer1,
   253  		layer2,
   254  	}).PlatformConfig("testArch", "testOs").Build()
   255  
   256  	image := CreateImageWith().LayerBlobs([][]byte{
   257  		layer1,
   258  		layer2,
   259  		layer3,
   260  	}).PlatformConfig("testArch", "testOs").Build()
   261  
   262  	multiArchBase := CreateMultiarchWith().Images([]Image{baseImage, CreateRandomImage(), CreateRandomImage()}).
   263  		Build()
   264  	multiArchImage := CreateMultiarchWith().Images([]Image{image, CreateRandomImage(), CreateRandomImage()}).
   265  		Build()
   266  
   267  	getCveResults := func(digestStr string) map[string]cvemodel.CVE {
   268  		switch digestStr {
   269  		case image.DigestStr():
   270  			return map[string]cvemodel.CVE{
   271  				"CVE1": {
   272  					ID:          "CVE1",
   273  					Severity:    "HIGH",
   274  					Title:       "Title CVE1",
   275  					Description: "Description CVE1",
   276  					PackageList: []cvemodel.Package{{}},
   277  				},
   278  				"CVE2": {
   279  					ID:          "CVE2",
   280  					Severity:    "MEDIUM",
   281  					Title:       "Title CVE2",
   282  					Description: "Description CVE2",
   283  					PackageList: []cvemodel.Package{{}},
   284  				},
   285  				"CVE3": {
   286  					ID:          "CVE3",
   287  					Severity:    "LOW",
   288  					Title:       "Title CVE3",
   289  					Description: "Description CVE3",
   290  					PackageList: []cvemodel.Package{{}},
   291  				},
   292  			}
   293  		case baseImage.DigestStr():
   294  			return map[string]cvemodel.CVE{
   295  				"CVE1": {
   296  					ID:          "CVE1",
   297  					Severity:    "HIGH",
   298  					Title:       "Title CVE1",
   299  					Description: "Description CVE1",
   300  					PackageList: []cvemodel.Package{{}},
   301  				},
   302  				"CVE2": {
   303  					ID:          "CVE2",
   304  					Severity:    "MEDIUM",
   305  					Title:       "Title CVE2",
   306  					Description: "Description CVE2",
   307  					PackageList: []cvemodel.Package{{}},
   308  				},
   309  			}
   310  		case otherImage.DigestStr():
   311  			return map[string]cvemodel.CVE{
   312  				"CVE1": {
   313  					ID:          "CVE1",
   314  					Severity:    "HIGH",
   315  					Title:       "Title CVE1",
   316  					Description: "Description CVE1",
   317  					PackageList: []cvemodel.Package{{}},
   318  				},
   319  			}
   320  		}
   321  
   322  		// By default the image has no vulnerabilities
   323  		return map[string]cvemodel.CVE{}
   324  	}
   325  
   326  	// MetaDB loaded with initial data, now mock the scanner
   327  	// Setup test CVE data in mock scanner
   328  	scanner := mocks.CveScannerMock{
   329  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   330  			repo, ref, _, _ := zcommon.GetRepoReference(image)
   331  
   332  			if zcommon.IsDigest(ref) {
   333  				return getCveResults(ref), nil
   334  			}
   335  
   336  			repoMeta, _ := ctlr.MetaDB.GetRepoMeta(ctx, repo)
   337  
   338  			if _, ok := repoMeta.Tags[ref]; !ok {
   339  				panic("unexpected tag '" + ref + "', test might be wrong")
   340  			}
   341  
   342  			return getCveResults(repoMeta.Tags[ref].Digest), nil
   343  		},
   344  		GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE {
   345  			return getCveResults(digestStr)
   346  		},
   347  		IsResultCachedFn: func(digestStr string) bool {
   348  			return true
   349  		},
   350  	}
   351  
   352  	ctlr.CveScanner = scanner
   353  
   354  	go func() {
   355  		if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
   356  			panic(err)
   357  		}
   358  	}()
   359  
   360  	defer ctlr.Shutdown()
   361  
   362  	test.WaitTillServerReady(url)
   363  
   364  	ctx := context.Background()
   365  	_, err = ociutils.InitializeTestMetaDB(ctx, ctlr.MetaDB,
   366  		ociutils.Repo{
   367  			Name: "repo",
   368  			Images: []ociutils.RepoImage{
   369  				{Image: otherImage, Reference: "other-image"},
   370  				{Image: baseImage, Reference: "base-image"},
   371  				{Image: image, Reference: "image"},
   372  			},
   373  		},
   374  		ociutils.Repo{
   375  			Name: "repo-multi",
   376  			MultiArchImages: []ociutils.RepoMultiArchImage{
   377  				{MultiarchImage: CreateRandomMultiarch(), Reference: "multi-rand"},
   378  				{MultiarchImage: multiArchBase, Reference: "multi-base"},
   379  				{MultiarchImage: multiArchImage, Reference: "multi-img"},
   380  			},
   381  		},
   382  	)
   383  
   384  	Convey("Test CVE by image name - GQL - positive", t, func() {
   385  		args := []string{"diff", "repo:image", "repo:base-image", "--config", "cvetest"}
   386  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   387  		defer os.Remove(configPath)
   388  		cveCmd := client.NewCVECommand(client.NewSearchService())
   389  		buff := bytes.NewBufferString("")
   390  		cveCmd.SetOut(buff)
   391  		cveCmd.SetErr(buff)
   392  		cveCmd.SetArgs(args)
   393  		err = cveCmd.Execute()
   394  		fmt.Println(buff.String())
   395  		space := regexp.MustCompile(`\s+`)
   396  		str := space.ReplaceAllString(buff.String(), " ")
   397  		str = strings.TrimSpace(str)
   398  		So(str, ShouldContainSubstring, "CVE3")
   399  		So(str, ShouldNotContainSubstring, "CVE1")
   400  		So(str, ShouldNotContainSubstring, "CVE2")
   401  	})
   402  
   403  	Convey("Errors", t, func() {
   404  		// args := []string{"diff", "repo:image", "repo:base-image", "--config", "cvetest"}
   405  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   406  		defer os.Remove(configPath)
   407  		cveCmd := client.NewCVECommand(client.NewSearchService())
   408  
   409  		Convey("Set wrong number of params", func() {
   410  			args := []string{"diff", "repo:image", "--config", "cvetest"}
   411  			cveCmd.SetArgs(args)
   412  			So(cveCmd.Execute(), ShouldNotBeNil)
   413  		})
   414  		Convey("First input is not a repo:tag", func() {
   415  			args := []string{"diff", "bad-input", "repo:base-image", "--config", "cvetest"}
   416  			cveCmd.SetArgs(args)
   417  			So(cveCmd.Execute(), ShouldNotBeNil)
   418  		})
   419  		Convey("Second input is arch but not enough args", func() {
   420  			args := []string{"diff", "repo:base-image", "linux/amd64", "--config", "cvetest"}
   421  			cveCmd.SetArgs(args)
   422  			So(cveCmd.Execute(), ShouldNotBeNil)
   423  		})
   424  		Convey("Second input is arch 3rd is repo:tag", func() {
   425  			args := []string{"diff", "repo:base-image", "linux/amd64", "repo:base-image", "--config", "cvetest"}
   426  			cveCmd.SetArgs(args)
   427  			So(cveCmd.Execute(), ShouldBeNil)
   428  		})
   429  		Convey("Second input is repo:tag 3rd is repo:tag", func() {
   430  			args := []string{"diff", "repo:base-image", "repo:base-image", "repo:base-image", "--config", "cvetest"}
   431  			cveCmd.SetArgs(args)
   432  			So(cveCmd.Execute(), ShouldNotBeNil)
   433  		})
   434  		Convey("Second input is arch 3rd is arch as well", func() {
   435  			args := []string{"diff", "repo:base-image", "linux/amd64", "linux/amd64", "--config", "cvetest"}
   436  			cveCmd.SetArgs(args)
   437  			So(cveCmd.Execute(), ShouldNotBeNil)
   438  		})
   439  		Convey("Second input is repo:tag 3rd is arch", func() {
   440  			args := []string{"diff", "repo:base-image", "repo:base-image", "linux/amd64", "--config", "cvetest"}
   441  			cveCmd.SetArgs(args)
   442  			So(cveCmd.Execute(), ShouldBeNil)
   443  		})
   444  		Convey("Second input is repo:tag 3rd is arch, 4th is repo:tag", func() {
   445  			args := []string{
   446  				"diff", "repo:base-image", "repo:base-image", "linux/amd64", "repo:base-image",
   447  				"--config", "cvetest",
   448  			}
   449  
   450  			cveCmd.SetArgs(args)
   451  			So(cveCmd.Execute(), ShouldNotBeNil)
   452  		})
   453  		Convey("Second input is arch 3rd is repo:tag, 4th is arch", func() {
   454  			args := []string{"diff", "repo:base-image", "linux/amd64", "repo:base-image", "linux/amd64", "--config", "cvetest"}
   455  			cveCmd.SetArgs(args)
   456  			So(cveCmd.Execute(), ShouldBeNil)
   457  		})
   458  		Convey("input is with digest ref", func() {
   459  			args := []string{"diff", "repo@sha256:123123", "--config", "cvetest"}
   460  			cveCmd.SetArgs(args)
   461  			So(cveCmd.Execute(), ShouldNotBeNil)
   462  		})
   463  		Convey("input is with just repo no ref", func() {
   464  			args := []string{"diff", "repo", "--config", "cvetest"}
   465  			cveCmd.SetArgs(args)
   466  			So(cveCmd.Execute(), ShouldNotBeNil)
   467  		})
   468  	})
   469  }
   470  
   471  //nolint:dupl
   472  func TestServerCVEResponse(t *testing.T) {
   473  	port := test.GetFreePort()
   474  	url := test.GetBaseURL(port)
   475  	conf := config.New()
   476  	conf.HTTP.Port = port
   477  
   478  	dir := t.TempDir()
   479  
   480  	conf.Storage.RootDirectory = dir
   481  	trivyConfig := &extconf.TrivyConfig{
   482  		DBRepository: "ghcr.io/project-zot/trivy-db",
   483  	}
   484  	cveConfig := &extconf.CVEConfig{
   485  		UpdateInterval: 2,
   486  		Trivy:          trivyConfig,
   487  	}
   488  	defaultVal := true
   489  	searchConfig := &extconf.SearchConfig{
   490  		BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   491  		CVE:        cveConfig,
   492  	}
   493  	conf.Extensions = &extconf.ExtensionConfig{
   494  		Search: searchConfig,
   495  	}
   496  
   497  	logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   498  	if err != nil {
   499  		panic(err)
   500  	}
   501  
   502  	logPath := logFile.Name()
   503  	defer os.Remove(logPath)
   504  
   505  	writers := io.MultiWriter(os.Stdout, logFile)
   506  
   507  	ctlr := api.NewController(conf)
   508  	ctlr.Log.Logger = ctlr.Log.Output(writers)
   509  
   510  	if err := ctlr.Init(); err != nil {
   511  		panic(err)
   512  	}
   513  
   514  	ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
   515  
   516  	go func() {
   517  		if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
   518  			panic(err)
   519  		}
   520  	}()
   521  
   522  	defer ctlr.Shutdown()
   523  
   524  	test.WaitTillServerReady(url)
   525  
   526  	image := CreateDefaultImage()
   527  
   528  	err = UploadImage(image, url, "zot-cve-test", "0.0.1")
   529  	if err != nil {
   530  		panic(err)
   531  	}
   532  
   533  	_, err = test.ReadLogFileAndSearchString(logPath, "cve-db update completed, next update scheduled after interval",
   534  		90*time.Second)
   535  	if err != nil {
   536  		panic(err)
   537  	}
   538  
   539  	Convey("Test CVE by image name - GQL - positive", t, func() {
   540  		args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"}
   541  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   542  		defer os.Remove(configPath)
   543  		cveCmd := client.NewCVECommand(client.NewSearchService())
   544  		buff := bytes.NewBufferString("")
   545  		cveCmd.SetOut(buff)
   546  		cveCmd.SetErr(buff)
   547  		cveCmd.SetArgs(args)
   548  		err = cveCmd.Execute()
   549  		space := regexp.MustCompile(`\s+`)
   550  		str := space.ReplaceAllString(buff.String(), " ")
   551  		str = strings.TrimSpace(str)
   552  		So(err, ShouldBeNil)
   553  		So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
   554  		So(str, ShouldContainSubstring, "CVE")
   555  	})
   556  
   557  	Convey("Test CVE by image name - GQL - search CVE by title in results", t, func() {
   558  		args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-C1", "--config", "cvetest"}
   559  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   560  		defer os.Remove(configPath)
   561  		cveCmd := client.NewCVECommand(client.NewSearchService())
   562  		buff := bytes.NewBufferString("")
   563  		cveCmd.SetOut(buff)
   564  		cveCmd.SetErr(buff)
   565  		cveCmd.SetArgs(args)
   566  		err = cveCmd.Execute()
   567  		space := regexp.MustCompile(`\s+`)
   568  		str := space.ReplaceAllString(buff.String(), " ")
   569  		str = strings.TrimSpace(str)
   570  		So(err, ShouldBeNil)
   571  		So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
   572  		So(str, ShouldContainSubstring, "CVE-C1")
   573  		So(str, ShouldNotContainSubstring, "CVE-2")
   574  	})
   575  
   576  	Convey("Test CVE by image name - GQL - search CVE by id in results", t, func() {
   577  		args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-2", "--config", "cvetest"}
   578  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   579  		defer os.Remove(configPath)
   580  		cveCmd := client.NewCVECommand(client.NewSearchService())
   581  		buff := bytes.NewBufferString("")
   582  		cveCmd.SetOut(buff)
   583  		cveCmd.SetErr(buff)
   584  		cveCmd.SetArgs(args)
   585  		err = cveCmd.Execute()
   586  		space := regexp.MustCompile(`\s+`)
   587  		str := space.ReplaceAllString(buff.String(), " ")
   588  		str = strings.TrimSpace(str)
   589  		So(err, ShouldBeNil)
   590  		So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
   591  		So(str, ShouldContainSubstring, "CVE-2")
   592  		So(str, ShouldNotContainSubstring, "CVE-1")
   593  	})
   594  
   595  	Convey("Test CVE by image name - GQL - search nonexistent CVE", t, func() {
   596  		args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-100", "--config", "cvetest"}
   597  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   598  		defer os.Remove(configPath)
   599  		cveCmd := client.NewCVECommand(client.NewSearchService())
   600  		buff := bytes.NewBufferString("")
   601  		cveCmd.SetOut(buff)
   602  		cveCmd.SetErr(buff)
   603  		cveCmd.SetArgs(args)
   604  		err = cveCmd.Execute()
   605  		space := regexp.MustCompile(`\s+`)
   606  		str := space.ReplaceAllString(buff.String(), " ")
   607  		str = strings.TrimSpace(str)
   608  		So(err, ShouldBeNil)
   609  		So(str, ShouldContainSubstring, "No CVEs found for image")
   610  	})
   611  
   612  	Convey("Test CVE by image name - GQL - invalid image", t, func() {
   613  		args := []string{"list", "invalid:0.0.1", "--config", "cvetest"}
   614  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   615  		defer os.Remove(configPath)
   616  		cveCmd := client.NewCVECommand(client.NewSearchService())
   617  		buff := bytes.NewBufferString("")
   618  		cveCmd.SetOut(buff)
   619  		cveCmd.SetErr(buff)
   620  		cveCmd.SetArgs(args)
   621  		err = cveCmd.Execute()
   622  		So(err, ShouldNotBeNil)
   623  	})
   624  
   625  	Convey("Test CVE by image name - GQL - invalid image name and tag", t, func() {
   626  		args := []string{"list", "invalid:", "--config", "cvetest"}
   627  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   628  		defer os.Remove(configPath)
   629  		cveCmd := client.NewCVECommand(client.NewSearchService())
   630  		buff := bytes.NewBufferString("")
   631  		cveCmd.SetOut(buff)
   632  		cveCmd.SetErr(buff)
   633  		cveCmd.SetArgs(args)
   634  		err = cveCmd.Execute()
   635  		So(err, ShouldNotBeNil)
   636  	})
   637  
   638  	Convey("Test CVE by image name - GQL - invalid cli output format", t, func() {
   639  		args := []string{"list", "zot-cve-test:0.0.1", "-f", "random", "--config", "cvetest"}
   640  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   641  		defer os.Remove(configPath)
   642  		cveCmd := client.NewCVECommand(client.NewSearchService())
   643  		buff := bytes.NewBufferString("")
   644  		cveCmd.SetOut(buff)
   645  		cveCmd.SetErr(buff)
   646  		cveCmd.SetArgs(args)
   647  		err = cveCmd.Execute()
   648  		So(err, ShouldNotBeNil)
   649  		So(buff.String(), ShouldContainSubstring, "invalid cli output format")
   650  	})
   651  
   652  	Convey("Test images by CVE ID - GQL - positive", t, func() {
   653  		args := []string{"affected", "CVE-2019-9923", "--config", "cvetest"}
   654  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   655  		defer os.Remove(configPath)
   656  		cveCmd := client.NewCVECommand(client.NewSearchService())
   657  		buff := bytes.NewBufferString("")
   658  		cveCmd.SetOut(buff)
   659  		cveCmd.SetErr(buff)
   660  		cveCmd.SetArgs(args)
   661  		err := cveCmd.Execute()
   662  		space := regexp.MustCompile(`\s+`)
   663  		str := space.ReplaceAllString(buff.String(), " ")
   664  		str = strings.TrimSpace(str)
   665  		So(err, ShouldBeNil)
   666  		So(str, ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B")
   667  	})
   668  
   669  	Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() {
   670  		args := []string{"affected", "CVE-invalid", "--config", "cvetest"}
   671  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   672  		defer os.Remove(configPath)
   673  		cveCmd := client.NewCVECommand(client.NewSearchService())
   674  		buff := bytes.NewBufferString("")
   675  		cveCmd.SetOut(buff)
   676  		cveCmd.SetErr(buff)
   677  		cveCmd.SetArgs(args)
   678  		err := cveCmd.Execute()
   679  		space := regexp.MustCompile(`\s+`)
   680  		str := space.ReplaceAllString(buff.String(), " ")
   681  		str = strings.TrimSpace(str)
   682  		So(err, ShouldBeNil)
   683  		So(str, ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   684  	})
   685  
   686  	Convey("Test images by CVE ID - GQL - invalid cli output format", t, func() {
   687  		args := []string{"affected", "CVE-2019-9923", "-f", "random", "--config", "cvetest"}
   688  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   689  		defer os.Remove(configPath)
   690  		cveCmd := client.NewCVECommand(client.NewSearchService())
   691  		buff := bytes.NewBufferString("")
   692  		cveCmd.SetOut(buff)
   693  		cveCmd.SetErr(buff)
   694  		cveCmd.SetArgs(args)
   695  		err = cveCmd.Execute()
   696  		So(err, ShouldNotBeNil)
   697  		So(buff.String(), ShouldContainSubstring, "invalid cli output format")
   698  	})
   699  
   700  	Convey("Test fixed tags by image name and CVE ID - GQL - positive", t, func() {
   701  		args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"}
   702  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   703  		defer os.Remove(configPath)
   704  		cveCmd := client.NewCVECommand(client.NewSearchService())
   705  		buff := bytes.NewBufferString("")
   706  		cveCmd.SetOut(buff)
   707  		cveCmd.SetErr(buff)
   708  		cveCmd.SetArgs(args)
   709  		err := cveCmd.Execute()
   710  		space := regexp.MustCompile(`\s+`)
   711  		str := space.ReplaceAllString(buff.String(), " ")
   712  		str = strings.TrimSpace(str)
   713  		So(err, ShouldBeNil)
   714  		So(str, ShouldEqual, "")
   715  	})
   716  
   717  	Convey("Test fixed tags by image name and CVE ID - GQL - random cve", t, func() {
   718  		args := []string{"fixed", "zot-cve-test", "random", "--config", "cvetest"}
   719  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   720  		defer os.Remove(configPath)
   721  		cveCmd := client.NewCVECommand(client.NewSearchService())
   722  		buff := bytes.NewBufferString("")
   723  		cveCmd.SetOut(buff)
   724  		cveCmd.SetErr(buff)
   725  		cveCmd.SetArgs(args)
   726  		err := cveCmd.Execute()
   727  		space := regexp.MustCompile(`\s+`)
   728  		str := space.ReplaceAllString(buff.String(), " ")
   729  		str = strings.TrimSpace(str)
   730  		So(err, ShouldBeNil)
   731  		So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   732  	})
   733  
   734  	Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() {
   735  		args := []string{"fixed", "zot-cv-test", "CVE-2019-20807", "--config", "cvetest"}
   736  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   737  		defer os.Remove(configPath)
   738  		cveCmd := client.NewCVECommand(client.NewSearchService())
   739  		buff := bytes.NewBufferString("")
   740  		cveCmd.SetOut(buff)
   741  		cveCmd.SetErr(buff)
   742  		cveCmd.SetArgs(args)
   743  		err := cveCmd.Execute()
   744  		space := regexp.MustCompile(`\s+`)
   745  		str := space.ReplaceAllString(buff.String(), " ")
   746  		str = strings.TrimSpace(str)
   747  		So(err, ShouldNotBeNil)
   748  		So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   749  	})
   750  
   751  	Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() {
   752  		args := []string{"fixed", "zot-cv-test:tag", "CVE-2019-20807", "--config", "cvetest"}
   753  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   754  		defer os.Remove(configPath)
   755  		cveCmd := client.NewCVECommand(client.NewSearchService())
   756  		buff := bytes.NewBufferString("")
   757  		cveCmd.SetOut(buff)
   758  		cveCmd.SetErr(buff)
   759  		cveCmd.SetArgs(args)
   760  		err := cveCmd.Execute()
   761  		space := regexp.MustCompile(`\s+`)
   762  		str := space.ReplaceAllString(buff.String(), " ")
   763  		str = strings.TrimSpace(str)
   764  		So(err, ShouldNotBeNil)
   765  		So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   766  	})
   767  
   768  	Convey("Test CVE by name and CVE ID - GQL - positive", t, func() {
   769  		args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "--config", "cvetest"}
   770  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   771  		defer os.Remove(configPath)
   772  		cveCmd := client.NewCVECommand(client.NewSearchService())
   773  		buff := bytes.NewBufferString("")
   774  		cveCmd.SetOut(buff)
   775  		cveCmd.SetErr(buff)
   776  		cveCmd.SetArgs(args)
   777  		err := cveCmd.Execute()
   778  		space := regexp.MustCompile(`\s+`)
   779  		str := space.ReplaceAllString(buff.String(), " ")
   780  		So(err, ShouldBeNil)
   781  		So(strings.TrimSpace(str), ShouldEqual,
   782  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B")
   783  	})
   784  
   785  	Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() {
   786  		args := []string{"affected", "CVE-20807", "--repo", "test", "--config", "cvetest"}
   787  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   788  		defer os.Remove(configPath)
   789  		cveCmd := client.NewCVECommand(client.NewSearchService())
   790  		buff := bytes.NewBufferString("")
   791  		cveCmd.SetOut(buff)
   792  		cveCmd.SetErr(buff)
   793  		cveCmd.SetArgs(args)
   794  		err := cveCmd.Execute()
   795  		space := regexp.MustCompile(`\s+`)
   796  		str := space.ReplaceAllString(buff.String(), " ")
   797  		So(err, ShouldBeNil)
   798  		So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH SIGNED SIZE")
   799  	})
   800  
   801  	Convey("Test CVE by name and CVE ID - GQL - invalid cli output format", t, func() {
   802  		args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "-f", "random", "--config", "cvetest"}
   803  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   804  		defer os.Remove(configPath)
   805  		cveCmd := client.NewCVECommand(client.NewSearchService())
   806  		buff := bytes.NewBufferString("")
   807  		cveCmd.SetOut(buff)
   808  		cveCmd.SetErr(buff)
   809  		cveCmd.SetArgs(args)
   810  		err = cveCmd.Execute()
   811  		So(err, ShouldNotBeNil)
   812  		So(buff.String(), ShouldContainSubstring, "invalid cli output format")
   813  	})
   814  }
   815  
   816  func TestCVESort(t *testing.T) {
   817  	rootDir := t.TempDir()
   818  	port := test.GetFreePort()
   819  	baseURL := test.GetBaseURL(port)
   820  	conf := config.New()
   821  	conf.HTTP.Port = port
   822  
   823  	defaultVal := true
   824  	conf.Extensions = &extconf.ExtensionConfig{
   825  		Search: &extconf.SearchConfig{
   826  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   827  			CVE: &extconf.CVEConfig{
   828  				UpdateInterval: 2,
   829  				Trivy: &extconf.TrivyConfig{
   830  					DBRepository: "ghcr.io/project-zot/trivy-db",
   831  				},
   832  			},
   833  		},
   834  	}
   835  	ctlr := api.NewController(conf)
   836  	ctlr.Config.Storage.RootDirectory = rootDir
   837  
   838  	image1 := CreateRandomImage()
   839  
   840  	storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log)
   841  
   842  	err := WriteImageToFileSystem(image1, "repo", "tag", storeController)
   843  	if err != nil {
   844  		t.FailNow()
   845  	}
   846  
   847  	if err := ctlr.Init(); err != nil {
   848  		panic(err)
   849  	}
   850  
   851  	ctlr.CveScanner = mocks.CveScannerMock{
   852  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   853  			return map[string]cvemodel.CVE{
   854  				"CVE-2023-1255": {
   855  					ID:       "CVE-2023-1255",
   856  					Severity: "LOW",
   857  					Title:    "Input buffer over-read in AES-XTS implementation and testing",
   858  				},
   859  				"CVE-2023-2650": {
   860  					ID:       "CVE-2023-2650",
   861  					Severity: "MEDIUM",
   862  					Title:    "Possible DoS translating ASN.1 object identifier and executer",
   863  				},
   864  				"CVE-2023-2975": {
   865  					ID:       "CVE-2023-2975",
   866  					Severity: "HIGH",
   867  					Title:    "AES-SIV cipher implementation contains a bug that can break",
   868  				},
   869  				"CVE-2023-3446": {
   870  					ID:       "CVE-2023-3446",
   871  					Severity: "CRITICAL",
   872  					Title:    "Excessive time spent checking DH keys and parenthesis",
   873  				},
   874  				"CVE-2023-3817": {
   875  					ID:       "CVE-2023-3817",
   876  					Severity: "MEDIUM",
   877  					Title:    "Excessive time spent checking DH q parameter and arguments",
   878  				},
   879  			}, nil
   880  		},
   881  	}
   882  
   883  	go func() {
   884  		if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
   885  			panic(err)
   886  		}
   887  	}()
   888  
   889  	defer ctlr.Shutdown()
   890  
   891  	test.WaitTillServerReady(baseURL)
   892  
   893  	space := regexp.MustCompile(`\s+`)
   894  
   895  	Convey("test sorting", t, func() {
   896  		args := []string{"list", "repo:tag", "--sort-by", "severity", "--url", baseURL}
   897  		cmd := client.NewCVECommand(client.NewSearchService())
   898  		buff := bytes.NewBufferString("")
   899  		cmd.SetOut(buff)
   900  		cmd.SetErr(buff)
   901  		cmd.SetArgs(args)
   902  		err := cmd.Execute()
   903  		So(err, ShouldBeNil)
   904  		str := space.ReplaceAllString(buff.String(), " ")
   905  		actual := strings.TrimSpace(str)
   906  		So(actual, ShouldResemble,
   907  			"CRITICAL 1, HIGH 1, MEDIUM 2, LOW 1, UNKNOWN 0, TOTAL 5 ID SEVERITY TITLE "+
   908  				"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
   909  				"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
   910  				"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
   911  				"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
   912  				"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...")
   913  
   914  		args = []string{"list", "repo:tag", "--sort-by", "alpha-asc", "--url", baseURL}
   915  		cmd = client.NewCVECommand(client.NewSearchService())
   916  		buff = bytes.NewBufferString("")
   917  		cmd.SetOut(buff)
   918  		cmd.SetErr(buff)
   919  		cmd.SetArgs(args)
   920  		err = cmd.Execute()
   921  		So(err, ShouldBeNil)
   922  		str = space.ReplaceAllString(buff.String(), " ")
   923  		actual = strings.TrimSpace(str)
   924  		So(actual, ShouldResemble,
   925  			"CRITICAL 1, HIGH 1, MEDIUM 2, LOW 1, UNKNOWN 0, TOTAL 5 ID SEVERITY TITLE "+
   926  				"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+
   927  				"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
   928  				"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
   929  				"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
   930  				"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ...")
   931  
   932  		args = []string{"list", "repo:tag", "--sort-by", "alpha-dsc", "--url", baseURL}
   933  		cmd = client.NewCVECommand(client.NewSearchService())
   934  		buff = bytes.NewBufferString("")
   935  		cmd.SetOut(buff)
   936  		cmd.SetErr(buff)
   937  		cmd.SetArgs(args)
   938  		err = cmd.Execute()
   939  		So(err, ShouldBeNil)
   940  		str = space.ReplaceAllString(buff.String(), " ")
   941  		actual = strings.TrimSpace(str)
   942  		So(actual, ShouldResemble,
   943  			"CRITICAL 1, HIGH 1, MEDIUM 2, LOW 1, UNKNOWN 0, TOTAL 5 ID SEVERITY TITLE "+
   944  				"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
   945  				"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
   946  				"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
   947  				"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
   948  				"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...")
   949  	})
   950  }
   951  
   952  func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner {
   953  	// MetaDB loaded with initial data now mock the scanner
   954  	// Setup test CVE data in mock scanner
   955  	scanner := mocks.CveScannerMock{
   956  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   957  			if strings.Contains(image, "zot-cve-test@sha256:db573b01") ||
   958  				image == "zot-cve-test:0.0.1" {
   959  				return map[string]cvemodel.CVE{
   960  					"CVE-1": {
   961  						ID:          "CVE-1",
   962  						Severity:    "CRITICAL",
   963  						Title:       "Title for CVE-C1",
   964  						Description: "Description of CVE-1",
   965  					},
   966  					"CVE-2019-9923": {
   967  						ID:          "CVE-2019-9923",
   968  						Severity:    "HIGH",
   969  						Title:       "Title for CVE-2",
   970  						Description: "Description of CVE-2",
   971  					},
   972  					"CVE-3": {
   973  						ID:          "CVE-3",
   974  						Severity:    "MEDIUM",
   975  						Title:       "Title for CVE-3",
   976  						Description: "Description of CVE-3",
   977  					},
   978  					"CVE-4": {
   979  						ID:          "CVE-4",
   980  						Severity:    "LOW",
   981  						Title:       "Title for CVE-4",
   982  						Description: "Description of CVE-4",
   983  					},
   984  					"CVE-5": {
   985  						ID:          "CVE-5",
   986  						Severity:    "UNKNOWN",
   987  						Title:       "Title for CVE-5",
   988  						Description: "Description of CVE-5",
   989  					},
   990  				}, nil
   991  			}
   992  
   993  			// By default the image has no vulnerabilities
   994  			return map[string]cvemodel.CVE{}, nil
   995  		},
   996  		IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
   997  			// Almost same logic compared to actual Trivy specific implementation
   998  			imageDir := repo
   999  			inputTag := reference
  1000  
  1001  			repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir)
  1002  			if err != nil {
  1003  				return false, err
  1004  			}
  1005  
  1006  			manifestDigestStr := reference
  1007  
  1008  			if zcommon.IsTag(reference) {
  1009  				var ok bool
  1010  
  1011  				descriptor, ok := repoMeta.Tags[inputTag]
  1012  				if !ok {
  1013  					return false, zerr.ErrTagMetaNotFound
  1014  				}
  1015  
  1016  				manifestDigestStr = descriptor.Digest
  1017  			}
  1018  
  1019  			manifestDigest, err := godigest.Parse(manifestDigestStr)
  1020  			if err != nil {
  1021  				return false, err
  1022  			}
  1023  
  1024  			manifestData, err := metaDB.GetImageMeta(manifestDigest)
  1025  			if err != nil {
  1026  				return false, err
  1027  			}
  1028  
  1029  			for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers {
  1030  				switch imageLayer.MediaType {
  1031  				case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
  1032  
  1033  					return true, nil
  1034  				default:
  1035  
  1036  					return false, zerr.ErrScanNotSupported
  1037  				}
  1038  			}
  1039  
  1040  			return false, nil
  1041  		},
  1042  	}
  1043  
  1044  	return &scanner
  1045  }