zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/search_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package search_test
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"path"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	guuid "github.com/gofrs/uuid"
    22  	regTypes "github.com/google/go-containerregistry/pkg/v1/types"
    23  	notreg "github.com/notaryproject/notation-go/registry"
    24  	godigest "github.com/opencontainers/go-digest"
    25  	"github.com/opencontainers/image-spec/specs-go"
    26  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    27  	. "github.com/smartystreets/goconvey/convey"
    28  	"gopkg.in/resty.v1"
    29  
    30  	zerr "zotregistry.dev/zot/errors"
    31  	"zotregistry.dev/zot/pkg/api"
    32  	"zotregistry.dev/zot/pkg/api/config"
    33  	"zotregistry.dev/zot/pkg/api/constants"
    34  	zcommon "zotregistry.dev/zot/pkg/common"
    35  	extconf "zotregistry.dev/zot/pkg/extensions/config"
    36  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    37  	cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
    38  	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
    39  	"zotregistry.dev/zot/pkg/log"
    40  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    41  	"zotregistry.dev/zot/pkg/storage"
    42  	"zotregistry.dev/zot/pkg/storage/local"
    43  	storageTypes "zotregistry.dev/zot/pkg/storage/types"
    44  	. "zotregistry.dev/zot/pkg/test/common"
    45  	. "zotregistry.dev/zot/pkg/test/image-utils"
    46  	"zotregistry.dev/zot/pkg/test/mocks"
    47  	ociutils "zotregistry.dev/zot/pkg/test/oci-utils"
    48  	"zotregistry.dev/zot/pkg/test/signature"
    49  	tskip "zotregistry.dev/zot/pkg/test/skip"
    50  )
    51  
    52  const (
    53  	graphqlQueryPrefix = constants.FullSearchPrefix
    54  	DBFileName         = "meta.db"
    55  )
    56  
    57  var (
    58  	ErrTestError   = errors.New("test error")
    59  	ErrPutManifest = errors.New("can't put manifest")
    60  )
    61  
    62  func readFileAndSearchString(filePath string, stringToMatch string, timeout time.Duration) (bool, error) {
    63  	ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
    64  	defer cancelFunc()
    65  
    66  	for {
    67  		select {
    68  		case <-ctx.Done():
    69  			return false, nil
    70  		default:
    71  			content, err := os.ReadFile(filePath)
    72  			if err != nil {
    73  				return false, err
    74  			}
    75  
    76  			if strings.Contains(string(content), stringToMatch) {
    77  				return true, nil
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  func verifyRepoSummaryFields(t *testing.T,
    84  	actualRepoSummary, expectedRepoSummary *zcommon.RepoSummary,
    85  ) {
    86  	t.Helper()
    87  
    88  	t.Logf("Verify RepoSummary \n%v \nmatches fields of \n%v",
    89  		actualRepoSummary, expectedRepoSummary,
    90  	)
    91  
    92  	So(actualRepoSummary.Name, ShouldEqual, expectedRepoSummary.Name)
    93  	So(actualRepoSummary.LastUpdated, ShouldEqual, expectedRepoSummary.LastUpdated)
    94  	So(actualRepoSummary.Size, ShouldEqual, expectedRepoSummary.Size)
    95  	So(len(actualRepoSummary.Vendors), ShouldEqual, len(expectedRepoSummary.Vendors))
    96  
    97  	for index, vendor := range actualRepoSummary.Vendors {
    98  		So(vendor, ShouldEqual, expectedRepoSummary.Vendors[index])
    99  	}
   100  
   101  	So(len(actualRepoSummary.Platforms), ShouldEqual, len(expectedRepoSummary.Platforms))
   102  
   103  	for index, platform := range actualRepoSummary.Platforms {
   104  		So(platform.Os, ShouldEqual, expectedRepoSummary.Platforms[index].Os)
   105  		So(platform.Arch, ShouldEqual, expectedRepoSummary.Platforms[index].Arch)
   106  	}
   107  
   108  	So(actualRepoSummary.NewestImage.Tag, ShouldEqual, expectedRepoSummary.NewestImage.Tag)
   109  	verifyImageSummaryFields(t, &actualRepoSummary.NewestImage, &expectedRepoSummary.NewestImage)
   110  }
   111  
   112  func verifyImageSummaryFields(t *testing.T,
   113  	actualImageSummary, expectedImageSummary *zcommon.ImageSummary,
   114  ) {
   115  	t.Helper()
   116  
   117  	t.Logf("Verify ImageSummary \n%v \nmatches fields of \n%v",
   118  		actualImageSummary, expectedImageSummary,
   119  	)
   120  
   121  	So(actualImageSummary.Tag, ShouldEqual, expectedImageSummary.Tag)
   122  	So(actualImageSummary.LastUpdated, ShouldEqual, expectedImageSummary.LastUpdated)
   123  	So(actualImageSummary.Size, ShouldEqual, expectedImageSummary.Size)
   124  	So(actualImageSummary.IsSigned, ShouldEqual, expectedImageSummary.IsSigned)
   125  	So(actualImageSummary.Vendor, ShouldEqual, expectedImageSummary.Vendor)
   126  	So(actualImageSummary.Title, ShouldEqual, expectedImageSummary.Title)
   127  	So(actualImageSummary.Description, ShouldEqual, expectedImageSummary.Description)
   128  	So(actualImageSummary.Source, ShouldEqual, expectedImageSummary.Source)
   129  	So(actualImageSummary.Documentation, ShouldEqual, expectedImageSummary.Documentation)
   130  	So(actualImageSummary.Licenses, ShouldEqual, expectedImageSummary.Licenses)
   131  
   132  	So(len(actualImageSummary.Manifests), ShouldEqual, len(expectedImageSummary.Manifests))
   133  
   134  	for i := range actualImageSummary.Manifests {
   135  		So(actualImageSummary.Manifests[i].Platform.Os, ShouldEqual, expectedImageSummary.Manifests[i].Platform.Os)
   136  		So(actualImageSummary.Manifests[i].Platform.Arch, ShouldEqual, expectedImageSummary.Manifests[i].Platform.Arch)
   137  		So(len(actualImageSummary.Manifests[i].History), ShouldEqual, len(expectedImageSummary.Manifests[i].History))
   138  
   139  		expectedHistories := expectedImageSummary.Manifests[i].History
   140  
   141  		for index, history := range actualImageSummary.Manifests[i].History {
   142  			// Digest could be empty string if the history entry is not associated with a layer
   143  			So(history.Layer.Digest, ShouldEqual, expectedHistories[index].Layer.Digest)
   144  			So(history.Layer.Size, ShouldEqual, expectedHistories[index].Layer.Size)
   145  			So(
   146  				history.HistoryDescription.Author,
   147  				ShouldEqual,
   148  				expectedHistories[index].HistoryDescription.Author,
   149  			)
   150  			So(
   151  				history.HistoryDescription.Created,
   152  				ShouldEqual,
   153  				expectedHistories[index].HistoryDescription.Created,
   154  			)
   155  			So(
   156  				history.HistoryDescription.CreatedBy,
   157  				ShouldEqual,
   158  				expectedHistories[index].HistoryDescription.CreatedBy,
   159  			)
   160  			So(
   161  				history.HistoryDescription.EmptyLayer,
   162  				ShouldEqual,
   163  				expectedHistories[index].HistoryDescription.EmptyLayer,
   164  			)
   165  			So(
   166  				history.HistoryDescription.Comment,
   167  				ShouldEqual,
   168  				expectedHistories[index].HistoryDescription.Comment,
   169  			)
   170  		}
   171  	}
   172  }
   173  
   174  func uploadNewRepoTag(tag string, repoName string, baseURL string, layers [][]byte) error {
   175  	created := time.Now()
   176  	config := ispec.Image{
   177  		Created: &created,
   178  		Platform: ispec.Platform{
   179  			Architecture: "amd64",
   180  			OS:           "linux",
   181  		},
   182  		RootFS: ispec.RootFS{
   183  			Type:    "layers",
   184  			DiffIDs: []godigest.Digest{},
   185  		},
   186  		Author: "ZotUser",
   187  	}
   188  
   189  	configBlob, err := json.Marshal(config)
   190  	So(err, ShouldBeNil)
   191  
   192  	configDigest := godigest.FromBytes(configBlob)
   193  
   194  	manifest := ispec.Manifest{
   195  		Versioned: specs.Versioned{
   196  			SchemaVersion: 2,
   197  		},
   198  		Config: ispec.Descriptor{
   199  			MediaType: "application/vnd.oci.image.config.v1+json",
   200  			Digest:    configDigest,
   201  			Size:      int64(len(configBlob)),
   202  		},
   203  		Layers: []ispec.Descriptor{
   204  			{
   205  				MediaType: "application/vnd.oci.image.layer.v1.tar",
   206  				Digest:    godigest.FromBytes(layers[0]),
   207  				Size:      int64(len(layers[0])),
   208  			},
   209  		},
   210  	}
   211  
   212  	err = UploadImage(
   213  		Image{
   214  			Manifest: manifest,
   215  			Config:   config,
   216  			Layers:   layers,
   217  		}, baseURL, repoName, tag,
   218  	)
   219  
   220  	return err
   221  }
   222  
   223  func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner {
   224  	// MetaDB loaded with initial data, mock the scanner
   225  	// Setup test CVE data in mock scanner
   226  	getCveResults := func(image string) map[string]cvemodel.CVE {
   227  		if image == "zot-cve-test:0.0.1" || image == "a/zot-cve-test:0.0.1" ||
   228  			image == "zot-test:0.0.1" || image == "a/zot-test:0.0.1" ||
   229  			strings.Contains(image, "sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495") ||
   230  			strings.Contains(image, "sha256:db573b0146a853af339bde42256a810b911d89c9252d055e0218de53690e031e") ||
   231  			strings.Contains(image, "sha256:0d8d7cae58478b43cde65d4fd495e1b4ab3f6404450b17e75d7e04eb2040d297") {
   232  			return map[string]cvemodel.CVE{
   233  				"CVE1": {
   234  					ID:          "CVE1",
   235  					Severity:    "MEDIUM",
   236  					Title:       "Title CVE1",
   237  					Description: "Description CVE1",
   238  				},
   239  				"CVE2": {
   240  					ID:          "CVE2",
   241  					Severity:    "HIGH",
   242  					Title:       "Title CVE2",
   243  					Description: "Description CVE2",
   244  				},
   245  				"CVE3": {
   246  					ID:          "CVE3",
   247  					Severity:    "LOW",
   248  					Title:       "Title CVE3",
   249  					Description: "Description CVE3",
   250  				},
   251  				"CVE4": {
   252  					ID:          "CVE4",
   253  					Severity:    "CRITICAL",
   254  					Title:       "Title CVE4",
   255  					Description: "Description CVE4",
   256  				},
   257  			}
   258  		}
   259  
   260  		if image == "test-repo:latest" ||
   261  			strings.Contains(image, "sha256:9f8e1a125c4fb03a0f157d75999b73284ccc5cba18eb772e4643e3499343607e") ||
   262  			strings.Contains(image, "sha256:5c6e8dd5dce1c488fc79d02690b11ff7a190fad21885297101dbd531f0db3597") {
   263  			return map[string]cvemodel.CVE{
   264  				"CVE1": {
   265  					ID:          "CVE1",
   266  					Severity:    "MEDIUM",
   267  					Title:       "Title CVE1",
   268  					Description: "Description CVE1",
   269  				},
   270  				"CVE2": {
   271  					ID:          "CVE2",
   272  					Severity:    "HIGH",
   273  					Title:       "Title CVE2",
   274  					Description: "Description CVE2",
   275  				},
   276  				"CVE3": {
   277  					ID:          "CVE3",
   278  					Severity:    "LOW",
   279  					Title:       "Title CVE3",
   280  					Description: "Description CVE3",
   281  				},
   282  				"CVE4": {
   283  					ID:          "CVE4",
   284  					Severity:    "CRITICAL",
   285  					Title:       "Title CVE4",
   286  					Description: "Description CVE4",
   287  				},
   288  			}
   289  		}
   290  
   291  		// By default the image has no vulnerabilities
   292  		return map[string]cvemodel.CVE{}
   293  	}
   294  
   295  	scanner := mocks.CveScannerMock{
   296  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   297  			return getCveResults(image), nil
   298  		},
   299  		GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE {
   300  			return getCveResults(digestStr)
   301  		},
   302  		IsResultCachedFn: func(digestStr string) bool {
   303  			return true
   304  		},
   305  		IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
   306  			// Almost same logic compared to actual Trivy specific implementation
   307  			imageDir := repo
   308  			inputTag := reference
   309  
   310  			repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir)
   311  			if err != nil {
   312  				return false, err
   313  			}
   314  
   315  			manifestDigestStr := reference
   316  
   317  			if zcommon.IsTag(reference) {
   318  				var ok bool
   319  
   320  				descriptor, ok := repoMeta.Tags[inputTag]
   321  				if !ok {
   322  					return false, zerr.ErrTagMetaNotFound
   323  				}
   324  
   325  				manifestDigestStr = descriptor.Digest
   326  			}
   327  
   328  			manifestDigest, err := godigest.Parse(manifestDigestStr)
   329  			if err != nil {
   330  				return false, err
   331  			}
   332  
   333  			manifestData, err := metaDB.GetImageMeta(manifestDigest)
   334  			if err != nil {
   335  				return false, err
   336  			}
   337  
   338  			for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers {
   339  				switch imageLayer.MediaType {
   340  				case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
   341  
   342  					return true, nil
   343  				default:
   344  
   345  					return false, zerr.ErrScanNotSupported
   346  				}
   347  			}
   348  
   349  			return false, nil
   350  		},
   351  	}
   352  
   353  	return &scanner
   354  }
   355  
   356  func TestRepoListWithNewestImage(t *testing.T) {
   357  	Convey("Test repoListWithNewestImage by tag with HTTP", t, func() {
   358  		subpath := "/a"
   359  		port := GetFreePort()
   360  		baseURL := GetBaseURL(port)
   361  		conf := config.New()
   362  		conf.HTTP.Port = port
   363  		rootDir := t.TempDir()
   364  		subRootDir := t.TempDir()
   365  		conf.Storage.RootDirectory = rootDir
   366  		conf.Storage.SubPaths = make(map[string]config.StorageConfig)
   367  		conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
   368  		defaultVal := true
   369  		conf.Extensions = &extconf.ExtensionConfig{
   370  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   371  		}
   372  
   373  		conf.Extensions.Search.CVE = nil
   374  
   375  		ctlr := api.NewController(conf)
   376  
   377  		ctlrManager := NewControllerManager(ctlr)
   378  		ctlrManager.StartAndWait(port)
   379  		defer ctlrManager.StopServer()
   380  
   381  		uploadedImage := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Build()
   382  
   383  		err := UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1")
   384  		So(err, ShouldBeNil)
   385  
   386  		err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1")
   387  		So(err, ShouldBeNil)
   388  
   389  		err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1")
   390  		So(err, ShouldBeNil)
   391  
   392  		err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1")
   393  		So(err, ShouldBeNil)
   394  
   395  		resp, err := resty.R().Get(baseURL + "/v2/")
   396  		So(resp, ShouldNotBeNil)
   397  		So(err, ShouldBeNil)
   398  		So(resp.StatusCode(), ShouldEqual, 200)
   399  
   400  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix)
   401  		So(resp, ShouldNotBeNil)
   402  		So(err, ShouldBeNil)
   403  		So(resp.StatusCode(), ShouldEqual, 422)
   404  
   405  		Convey("Test repoListWithNewestImage with pagination", func() {
   406  			query := `{
   407  				RepoListWithNewestImage(requestedPage:{
   408  					limit: 2
   409  					offset: 0
   410  					sortBy: UPDATE_TIME
   411  				}){
   412  					Page{
   413  						ItemCount
   414  						TotalCount
   415  					}
   416  					Results{
   417  						Name
   418  						NewestImage{
   419  							Tag
   420  						}
   421  					}
   422  				}
   423  			}`
   424  
   425  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   426  				"?query=" + url.QueryEscape(query))
   427  			So(resp, ShouldNotBeNil)
   428  			So(err, ShouldBeNil)
   429  			So(resp.StatusCode(), ShouldEqual, 200)
   430  
   431  			var responseStruct zcommon.RepoWithNewestImageResponse
   432  			err = json.Unmarshal(resp.Body(), &responseStruct)
   433  			So(err, ShouldBeNil)
   434  			So(len(responseStruct.Results), ShouldEqual, 2)
   435  			So(responseStruct.Page.ItemCount, ShouldEqual, 2)
   436  			So(responseStruct.Page.TotalCount, ShouldEqual, 4)
   437  		})
   438  
   439  		Convey("Test repoListWithNewestImage with pagination, no limit or offset", func() {
   440  			query := `{
   441  				RepoListWithNewestImage(requestedPage:{
   442  					limit: 0
   443  					offset: 0
   444  					sortBy: UPDATE_TIME
   445  				}){
   446  					Page{
   447  						ItemCount
   448  						TotalCount
   449  					}
   450  					Results{
   451  						Name
   452  						NewestImage{
   453  							Tag
   454  						}
   455  					}
   456  				}
   457  			}`
   458  
   459  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   460  				"?query=" + url.QueryEscape(query))
   461  			So(resp, ShouldNotBeNil)
   462  			So(err, ShouldBeNil)
   463  			So(resp.StatusCode(), ShouldEqual, 200)
   464  
   465  			var responseStruct zcommon.RepoWithNewestImageResponse
   466  			err = json.Unmarshal(resp.Body(), &responseStruct)
   467  			So(err, ShouldBeNil)
   468  			So(len(responseStruct.Results), ShouldEqual, 4)
   469  			So(responseStruct.Page.ItemCount, ShouldEqual, 4)
   470  			So(responseStruct.Page.TotalCount, ShouldEqual, 4)
   471  		})
   472  
   473  		Convey("Test repoListWithNewestImage multiple", func() {
   474  			query := `{RepoListWithNewestImage{
   475  							Results{
   476  								Name
   477  								NewestImage{
   478  									Tag
   479  								}
   480  							}
   481  						}}`
   482  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   483  				"?query=" + url.QueryEscape(query))
   484  			So(resp, ShouldNotBeNil)
   485  			So(err, ShouldBeNil)
   486  			So(resp.StatusCode(), ShouldEqual, 200)
   487  
   488  			var responseStruct zcommon.RepoWithNewestImageResponse
   489  			err = json.Unmarshal(resp.Body(), &responseStruct)
   490  			So(err, ShouldBeNil)
   491  			So(len(responseStruct.Results), ShouldEqual, 4)
   492  
   493  			images := responseStruct.Results
   494  			So(images[0].NewestImage.Tag, ShouldEqual, "0.0.1")
   495  
   496  			query = `{
   497  				RepoListWithNewestImage(requestedPage: {
   498  					limit: 1
   499  					offset: 0
   500  					sortBy: UPDATE_TIME
   501  				}){
   502  					Results{
   503  						Name
   504  						NewestImage{
   505  							Tag
   506  						}
   507  					}
   508  				}
   509  			}`
   510  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   511  				"?query=" + url.QueryEscape(query))
   512  			So(resp, ShouldNotBeNil)
   513  			So(err, ShouldBeNil)
   514  			So(resp.StatusCode(), ShouldEqual, 200)
   515  
   516  			err = json.Unmarshal(resp.Body(), &responseStruct)
   517  			So(err, ShouldBeNil)
   518  			So(len(responseStruct.Results), ShouldEqual, 1)
   519  
   520  			repos := responseStruct.Results
   521  			So(repos[0].NewestImage.Tag, ShouldEqual, "0.0.1")
   522  
   523  			query = `{
   524  				RepoListWithNewestImage{
   525  					Results{
   526  						Name
   527  						NewestImage{
   528  							Tag
   529  							Vulnerabilities{
   530  								MaxSeverity
   531  								UnknownCount
   532  								LowCount
   533  								MediumCount
   534  								HighCount
   535  								CriticalCount
   536  								Count
   537  							}
   538  						}
   539  					}
   540  				}
   541  			}`
   542  
   543  			// Verify we don't return any vulnerabilities if CVE scanning is disabled
   544  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   545  				"?query=" + url.QueryEscape(query))
   546  			So(resp, ShouldNotBeNil)
   547  			So(err, ShouldBeNil)
   548  			So(resp.StatusCode(), ShouldEqual, 200)
   549  
   550  			err = json.Unmarshal(resp.Body(), &responseStruct)
   551  			So(err, ShouldBeNil)
   552  			So(len(responseStruct.Results), ShouldEqual, 4)
   553  
   554  			images = responseStruct.Results
   555  			So(images[0].NewestImage.Tag, ShouldEqual, "0.0.1")
   556  			So(images[0].NewestImage.Vulnerabilities.Count, ShouldEqual, 0)
   557  			So(images[0].NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0)
   558  			So(images[0].NewestImage.Vulnerabilities.LowCount, ShouldEqual, 0)
   559  			So(images[0].NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 0)
   560  			So(images[0].NewestImage.Vulnerabilities.HighCount, ShouldEqual, 0)
   561  			So(images[0].NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 0)
   562  			So(images[0].NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "")
   563  
   564  			query = `{
   565  				RepoListWithNewestImage{
   566  					Results{
   567  						Name
   568  						NewestImage{
   569  							Tag
   570  						}
   571  					}
   572  				}
   573  			}`
   574  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   575  				"?query=" + url.QueryEscape(query))
   576  			So(resp, ShouldNotBeNil)
   577  			So(err, ShouldBeNil)
   578  
   579  			err = os.Chmod(rootDir, 0o000)
   580  			if err != nil {
   581  				panic(err)
   582  			}
   583  
   584  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   585  				"?query=" + url.QueryEscape(query))
   586  			So(resp, ShouldNotBeNil)
   587  			So(err, ShouldBeNil)
   588  			So(resp.StatusCode(), ShouldEqual, 200)
   589  
   590  			err = json.Unmarshal(resp.Body(), &responseStruct)
   591  			So(err, ShouldBeNil)
   592  			So(responseStruct.Errors, ShouldBeNil) // Even if permissions fail data is coming from the DB
   593  
   594  			err = os.Chmod(rootDir, 0o755)
   595  			if err != nil {
   596  				panic(err)
   597  			}
   598  
   599  			manifestDigest := uploadedImage.ManifestDescriptor.Digest
   600  			configDigest := uploadedImage.ConfigDescriptor.Digest
   601  
   602  			// Delete config blob and try.
   603  			err = os.Remove(path.Join(subRootDir, "a/zot-test/blobs/sha256", configDigest.Encoded()))
   604  			if err != nil {
   605  				panic(err)
   606  			}
   607  
   608  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   609  				"?query=" + url.QueryEscape(query))
   610  			So(resp, ShouldNotBeNil)
   611  			So(err, ShouldBeNil)
   612  			So(resp.StatusCode(), ShouldEqual, 200)
   613  
   614  			err = os.Remove(path.Join(subRootDir, "a/zot-test/blobs/sha256",
   615  				manifestDigest.Encoded()))
   616  			if err != nil {
   617  				panic(err)
   618  			}
   619  
   620  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   621  				"?query=" + url.QueryEscape(query))
   622  			So(resp, ShouldNotBeNil)
   623  			So(err, ShouldBeNil)
   624  			So(resp.StatusCode(), ShouldEqual, 200)
   625  
   626  			err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", configDigest.Encoded()))
   627  			if err != nil {
   628  				panic(err)
   629  			}
   630  
   631  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   632  				"?query=" + url.QueryEscape(query))
   633  			So(resp, ShouldNotBeNil)
   634  			So(err, ShouldBeNil)
   635  			So(resp.StatusCode(), ShouldEqual, 200)
   636  
   637  			// Delete manifest blob also and try
   638  			err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded()))
   639  			if err != nil {
   640  				panic(err)
   641  			}
   642  
   643  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
   644  				"?query=" + url.QueryEscape(query))
   645  			So(resp, ShouldNotBeNil)
   646  			So(err, ShouldBeNil)
   647  			So(resp.StatusCode(), ShouldEqual, 200)
   648  		})
   649  	})
   650  
   651  	Convey("Test repoListWithNewestImage with vulnerability scan enabled", t, func() {
   652  		subpath := "/a"
   653  		port := GetFreePort()
   654  		baseURL := GetBaseURL(port)
   655  		conf := config.New()
   656  		conf.HTTP.Port = port
   657  		rootDir := t.TempDir()
   658  		subRootDir := t.TempDir()
   659  		conf.Storage.RootDirectory = rootDir
   660  		conf.Storage.SubPaths = make(map[string]config.StorageConfig)
   661  		conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
   662  		defaultVal := true
   663  
   664  		updateDuration, _ := time.ParseDuration("1h")
   665  		trivyConfig := &extconf.TrivyConfig{
   666  			DBRepository: "ghcr.io/project-zot/trivy-db",
   667  		}
   668  		cveConfig := &extconf.CVEConfig{
   669  			UpdateInterval: updateDuration,
   670  			Trivy:          trivyConfig,
   671  		}
   672  		searchConfig := &extconf.SearchConfig{
   673  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   674  			CVE:        cveConfig,
   675  		}
   676  		conf.Extensions = &extconf.ExtensionConfig{
   677  			Search: searchConfig,
   678  		}
   679  
   680  		// we won't use the logging config feature as we want logs in both
   681  		// stdout and a file
   682  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   683  		So(err, ShouldBeNil)
   684  		logPath := logFile.Name()
   685  		defer os.Remove(logPath)
   686  
   687  		writers := io.MultiWriter(os.Stdout, logFile)
   688  
   689  		ctlr := api.NewController(conf)
   690  		ctlr.Log.Logger = ctlr.Log.Output(writers)
   691  
   692  		if err := ctlr.Init(); err != nil {
   693  			panic(err)
   694  		}
   695  
   696  		ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
   697  
   698  		go func() {
   699  			if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
   700  				panic(err)
   701  			}
   702  		}()
   703  
   704  		defer ctlr.Shutdown()
   705  
   706  		substring := "{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000," +
   707  			"\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"}}}"
   708  		found, err := readFileAndSearchString(logPath, substring, 2*time.Minute)
   709  		So(found, ShouldBeTrue)
   710  		So(err, ShouldBeNil)
   711  
   712  		found, err = readFileAndSearchString(logPath, "updating cve-db", 2*time.Minute)
   713  		So(found, ShouldBeTrue)
   714  		So(err, ShouldBeNil)
   715  
   716  		found, err = readFileAndSearchString(logPath,
   717  			"cve-db update completed, next update scheduled after interval", 4*time.Minute)
   718  		So(found, ShouldBeTrue)
   719  		So(err, ShouldBeNil)
   720  
   721  		WaitTillServerReady(baseURL)
   722  
   723  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix)
   724  		So(resp, ShouldNotBeNil)
   725  		So(err, ShouldBeNil)
   726  		So(resp.StatusCode(), ShouldEqual, 422)
   727  
   728  		uploadedImage := CreateDefaultImage()
   729  
   730  		err = UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1")
   731  		So(err, ShouldBeNil)
   732  
   733  		err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1")
   734  		So(err, ShouldBeNil)
   735  
   736  		err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1")
   737  		So(err, ShouldBeNil)
   738  
   739  		err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1")
   740  		So(err, ShouldBeNil)
   741  
   742  		query := `{
   743  			RepoListWithNewestImage{
   744  				Results{
   745  					Name
   746  					NewestImage{
   747  						Tag
   748  						Digest
   749  						Vulnerabilities{
   750  							MaxSeverity
   751  							UnknownCount
   752  							LowCount
   753  							MediumCount
   754  							HighCount
   755  							CriticalCount
   756  							Count
   757  						}
   758  					}
   759  				}
   760  			}
   761  		}`
   762  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
   763  		So(resp, ShouldNotBeNil)
   764  		So(err, ShouldBeNil)
   765  		So(resp.StatusCode(), ShouldEqual, 200)
   766  
   767  		var responseStruct zcommon.RepoWithNewestImageResponse
   768  		err = json.Unmarshal(resp.Body(), &responseStruct)
   769  		So(err, ShouldBeNil)
   770  		So(len(responseStruct.Results), ShouldEqual, 4)
   771  
   772  		repos := responseStruct.Results
   773  		So(repos[0].NewestImage.Tag, ShouldEqual, "0.0.1")
   774  
   775  		for _, repo := range repos {
   776  			vulnerabilities := repo.NewestImage.Vulnerabilities
   777  			So(vulnerabilities, ShouldNotBeNil)
   778  			t.Logf("Found vulnerability summary %v", vulnerabilities)
   779  			// Depends on test data, but current tested images contain hundreds
   780  			So(vulnerabilities.Count, ShouldEqual, 4)
   781  			So(vulnerabilities.UnknownCount, ShouldEqual, 0)
   782  			So(vulnerabilities.LowCount, ShouldEqual, 1)
   783  			So(vulnerabilities.MediumCount, ShouldEqual, 1)
   784  			So(vulnerabilities.HighCount, ShouldEqual, 1)
   785  			So(vulnerabilities.CriticalCount, ShouldEqual, 1)
   786  			So(vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
   787  		}
   788  	})
   789  }
   790  
   791  func TestGetReferrersGQL(t *testing.T) {
   792  	Convey("get referrers", t, func() {
   793  		port := GetFreePort()
   794  		baseURL := GetBaseURL(port)
   795  		conf := config.New()
   796  		conf.HTTP.Port = port
   797  		conf.Storage.RootDirectory = t.TempDir()
   798  
   799  		defaultVal := true
   800  		conf.Extensions = &extconf.ExtensionConfig{
   801  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   802  			Lint: &extconf.LintConfig{
   803  				BaseConfig: extconf.BaseConfig{
   804  					Enable: &defaultVal,
   805  				},
   806  			},
   807  		}
   808  
   809  		gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix)
   810  
   811  		conf.Extensions.Search.CVE = nil
   812  
   813  		ctlr := api.NewController(conf)
   814  		ctlrManager := NewControllerManager(ctlr)
   815  		ctlrManager.StartAndWait(port)
   816  		defer ctlrManager.StopServer()
   817  
   818  		// =======================
   819  
   820  		uploadedImage := CreateImageWith().RandomLayers(1, 1000).DefaultConfig().Build()
   821  
   822  		repo := "artifact-ref"
   823  
   824  		err := UploadImage(uploadedImage, baseURL, repo, "1.0")
   825  		So(err, ShouldBeNil)
   826  
   827  		manifestDigest := uploadedImage.ManifestDescriptor.Digest
   828  		manifestSize := uploadedImage.ManifestDescriptor.Size
   829  
   830  		subjectDescriptor := &ispec.Descriptor{
   831  			MediaType: "application/vnd.oci.image.manifest.v1+json",
   832  			Size:      manifestSize,
   833  			Digest:    manifestDigest,
   834  		}
   835  
   836  		artifactContentBlob := []byte("test artifact")
   837  		artifactContentBlobSize := int64(len(artifactContentBlob))
   838  		artifactContentType := "application/octet-stream"
   839  		artifactContentBlobDigest := godigest.FromBytes(artifactContentBlob)
   840  		artifactType := "com.artifact.test/type1"
   841  
   842  		artifactImg := Image{
   843  			Manifest: ispec.Manifest{
   844  				Layers: []ispec.Descriptor{
   845  					{
   846  						MediaType: artifactContentType,
   847  						Digest:    artifactContentBlobDigest,
   848  						Size:      artifactContentBlobSize,
   849  					},
   850  				},
   851  				Subject:      subjectDescriptor,
   852  				ArtifactType: artifactType,
   853  				Config: ispec.Descriptor{
   854  					MediaType: ispec.MediaTypeEmptyJSON,
   855  					Digest:    ispec.DescriptorEmptyJSON.Digest,
   856  					Data:      ispec.DescriptorEmptyJSON.Data,
   857  				},
   858  				MediaType: ispec.MediaTypeImageManifest,
   859  				Annotations: map[string]string{
   860  					"com.artifact.format": "test",
   861  				},
   862  			},
   863  			Config: ispec.Image{},
   864  			Layers: [][]byte{artifactContentBlob},
   865  		}
   866  
   867  		artifactImg.Manifest.SchemaVersion = 2
   868  
   869  		artifactManifestBlob, err := json.Marshal(artifactImg.Manifest)
   870  		So(err, ShouldBeNil)
   871  		artifactManifestDigest := godigest.FromBytes(artifactManifestBlob)
   872  
   873  		err = UploadImage(artifactImg, baseURL, repo, artifactManifestDigest.String())
   874  		So(err, ShouldBeNil)
   875  
   876  		gqlQuery := `
   877  			{
   878  				Referrers(
   879  					repo: "%s", digest: "%s", type: ""){
   880  					ArtifactType,
   881  					Digest,
   882  					MediaType,
   883  					Size,
   884  					Annotations{
   885  						Key
   886  						Value
   887  					}
   888  		   		}
   889  			}`
   890  
   891  		strQuery := fmt.Sprintf(gqlQuery, repo, manifestDigest.String())
   892  
   893  		targetURL := fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
   894  
   895  		resp, err := resty.R().Get(targetURL)
   896  		So(resp, ShouldNotBeNil)
   897  		So(err, ShouldBeNil)
   898  		So(resp.StatusCode(), ShouldEqual, 200)
   899  		So(resp.Body(), ShouldNotBeNil)
   900  
   901  		referrersResp := &zcommon.ReferrersResp{}
   902  
   903  		err = json.Unmarshal(resp.Body(), referrersResp)
   904  		So(err, ShouldBeNil)
   905  		So(referrersResp.Errors, ShouldBeNil)
   906  		So(referrersResp.Referrers[0].ArtifactType, ShouldEqual, artifactType)
   907  		So(referrersResp.Referrers[0].MediaType, ShouldEqual, ispec.MediaTypeImageManifest)
   908  
   909  		So(referrersResp.Referrers[0].Annotations[0].Key, ShouldEqual, "com.artifact.format")
   910  		So(referrersResp.Referrers[0].Annotations[0].Value, ShouldEqual, "test")
   911  
   912  		So(referrersResp.Referrers[0].Digest, ShouldEqual, artifactManifestDigest.String())
   913  	})
   914  
   915  	Convey("referrers for image index", t, func() {
   916  		port := GetFreePort()
   917  		baseURL := GetBaseURL(port)
   918  		conf := config.New()
   919  		conf.HTTP.Port = port
   920  		conf.Storage.RootDirectory = t.TempDir()
   921  		conf.Storage.GC = false
   922  
   923  		defaultVal := true
   924  		conf.Extensions = &extconf.ExtensionConfig{
   925  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   926  			Lint: &extconf.LintConfig{
   927  				BaseConfig: extconf.BaseConfig{
   928  					Enable: &defaultVal,
   929  				},
   930  			},
   931  		}
   932  
   933  		gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix)
   934  
   935  		conf.Extensions.Search.CVE = nil
   936  
   937  		ctlr := api.NewController(conf)
   938  		ctlrManager := NewControllerManager(ctlr)
   939  		ctlrManager.StartAndWait(port)
   940  		defer ctlrManager.StopServer()
   941  
   942  		// =======================
   943  
   944  		multiarch := CreateRandomMultiarch()
   945  
   946  		repo := "artifact-ref"
   947  
   948  		err := UploadMultiarchImage(multiarch, baseURL, repo, "multiarch")
   949  		So(err, ShouldBeNil)
   950  
   951  		indexBlob, err := json.Marshal(multiarch.Index)
   952  		So(err, ShouldBeNil)
   953  		indexDigest := godigest.FromBytes(indexBlob)
   954  		indexSize := int64(len(indexBlob))
   955  
   956  		subjectDescriptor := &ispec.Descriptor{
   957  			MediaType: ispec.MediaTypeImageIndex,
   958  			Size:      indexSize,
   959  			Digest:    indexDigest,
   960  		}
   961  
   962  		artifactContentBlob := []byte("test artifact")
   963  		artifactContentBlobSize := int64(len(artifactContentBlob))
   964  		artifactContentType := "application/octet-stream"
   965  		artifactContentBlobDigest := godigest.FromBytes(artifactContentBlob)
   966  		artifactType := "com.artifact.test/type2"
   967  
   968  		configBlob, err := json.Marshal(ispec.Image{})
   969  		So(err, ShouldBeNil)
   970  
   971  		artifactManifest := ispec.Manifest{
   972  			Layers: []ispec.Descriptor{
   973  				{
   974  					MediaType: artifactContentType,
   975  					Digest:    artifactContentBlobDigest,
   976  					Size:      artifactContentBlobSize,
   977  				},
   978  			},
   979  			Subject: subjectDescriptor,
   980  			Config: ispec.Descriptor{
   981  				MediaType: artifactType,
   982  				Digest:    godigest.FromBytes(configBlob),
   983  			},
   984  			MediaType: ispec.MediaTypeImageManifest,
   985  			Annotations: map[string]string{
   986  				"com.artifact.format": "test",
   987  			},
   988  		}
   989  
   990  		artifactManifest.SchemaVersion = 2
   991  
   992  		artifactManifestBlob, err := json.Marshal(artifactManifest)
   993  		So(err, ShouldBeNil)
   994  		artifactManifestDigest := godigest.FromBytes(artifactManifestBlob)
   995  
   996  		err = UploadImage(
   997  			Image{
   998  				Manifest: artifactManifest,
   999  				Config:   ispec.Image{},
  1000  				Layers:   [][]byte{artifactContentBlob},
  1001  			}, baseURL, repo, artifactManifestDigest.String())
  1002  		So(err, ShouldBeNil)
  1003  
  1004  		gqlQuery := `
  1005  			{
  1006  				Referrers( repo: "%s", digest: "%s", type: "" ){
  1007  					ArtifactType,
  1008  					Digest,
  1009  					MediaType,
  1010  					Size,
  1011  					Annotations{
  1012  						Key
  1013  						Value
  1014  					}
  1015  		   		}
  1016  			}`
  1017  
  1018  		strQuery := fmt.Sprintf(gqlQuery, repo, indexDigest.String())
  1019  
  1020  		targetURL := fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
  1021  
  1022  		resp, err := resty.R().Get(targetURL)
  1023  		So(resp, ShouldNotBeNil)
  1024  		So(err, ShouldBeNil)
  1025  		So(resp.StatusCode(), ShouldEqual, 200)
  1026  		So(resp.Body(), ShouldNotBeNil)
  1027  
  1028  		referrersResp := &zcommon.ReferrersResp{}
  1029  
  1030  		err = json.Unmarshal(resp.Body(), referrersResp)
  1031  		So(err, ShouldBeNil)
  1032  		So(referrersResp.Errors, ShouldBeNil)
  1033  		So(len(referrersResp.Referrers), ShouldEqual, 1)
  1034  		So(referrersResp.Referrers[0].ArtifactType, ShouldEqual, artifactType)
  1035  		So(referrersResp.Referrers[0].MediaType, ShouldEqual, ispec.MediaTypeImageManifest)
  1036  
  1037  		So(referrersResp.Referrers[0].Annotations[0].Key, ShouldEqual, "com.artifact.format")
  1038  		So(referrersResp.Referrers[0].Annotations[0].Value, ShouldEqual, "test")
  1039  
  1040  		So(referrersResp.Referrers[0].Digest, ShouldEqual, artifactManifestDigest.String())
  1041  	})
  1042  
  1043  	Convey("Get referrers with index as referrer", t, func() {
  1044  		port := GetFreePort()
  1045  		baseURL := GetBaseURL(port)
  1046  		conf := config.New()
  1047  		conf.HTTP.Port = port
  1048  		conf.Storage.RootDirectory = t.TempDir()
  1049  		conf.Storage.GC = false
  1050  
  1051  		defaultVal := true
  1052  		conf.Extensions = &extconf.ExtensionConfig{
  1053  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1054  			Lint: &extconf.LintConfig{
  1055  				BaseConfig: extconf.BaseConfig{
  1056  					Enable: &defaultVal,
  1057  				},
  1058  			},
  1059  		}
  1060  
  1061  		conf.Extensions.Search.CVE = nil
  1062  
  1063  		ctlr := api.NewController(conf)
  1064  		ctlrManager := NewControllerManager(ctlr)
  1065  		ctlrManager.StartAndWait(port)
  1066  		defer ctlrManager.StopServer()
  1067  
  1068  		// Upload the index referrer
  1069  		targetImg := CreateRandomImage()
  1070  		targetDigest := targetImg.Digest()
  1071  
  1072  		err := UploadImage(targetImg, baseURL, "repo", targetImg.DigestStr())
  1073  		So(err, ShouldBeNil)
  1074  
  1075  		artifactType := "com.artifact.art/type"
  1076  		indexReferrer := CreateMultiarchWith().RandomImages(2).
  1077  			ArtifactType(artifactType).
  1078  			Subject(targetImg.DescriptorRef()).
  1079  			Build()
  1080  		indexReferrerDigest := indexReferrer.Digest()
  1081  
  1082  		err = UploadMultiarchImage(indexReferrer, baseURL, "repo", "ref")
  1083  		So(err, ShouldBeNil)
  1084  
  1085  		// Call Referrers GQL
  1086  
  1087  		referrersQuery := `
  1088  			{
  1089  				Referrers( repo: "%s", digest: "%s"){
  1090  					ArtifactType,
  1091  					Digest,
  1092  					MediaType,
  1093  					Size,
  1094  					Annotations{
  1095  						Key
  1096  						Value
  1097  					}
  1098  		   		}
  1099  			}`
  1100  
  1101  		referrersQuery = fmt.Sprintf(referrersQuery, "repo", targetDigest.String())
  1102  
  1103  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(referrersQuery))
  1104  		So(resp, ShouldNotBeNil)
  1105  		So(err, ShouldBeNil)
  1106  
  1107  		So(resp.StatusCode(), ShouldEqual, 200)
  1108  		So(resp.Body(), ShouldNotBeNil)
  1109  		So(err, ShouldBeNil)
  1110  
  1111  		referrersResp := &zcommon.ReferrersResp{}
  1112  
  1113  		err = json.Unmarshal(resp.Body(), referrersResp)
  1114  		So(err, ShouldBeNil)
  1115  		So(len(referrersResp.Referrers), ShouldEqual, 1)
  1116  		So(referrersResp.Referrers[0].ArtifactType, ShouldResemble, artifactType)
  1117  		So(referrersResp.Referrers[0].Digest, ShouldResemble, indexReferrerDigest.String())
  1118  		So(referrersResp.Referrers[0].MediaType, ShouldResemble, ispec.MediaTypeImageIndex)
  1119  
  1120  		// Make REST call
  1121  
  1122  		resp, err = resty.R().Get(baseURL + "/v2/repo/referrers/" + targetDigest.String())
  1123  		So(err, ShouldBeNil)
  1124  
  1125  		var index ispec.Index
  1126  
  1127  		err = json.Unmarshal(resp.Body(), &index)
  1128  		So(err, ShouldBeNil)
  1129  		So(len(index.Manifests), ShouldEqual, 1)
  1130  		So(index.Manifests[0].ArtifactType, ShouldEqual, artifactType)
  1131  		So(index.Manifests[0].Digest.String(), ShouldResemble, indexReferrerDigest.String())
  1132  		So(index.Manifests[0].MediaType, ShouldResemble, ispec.MediaTypeImageIndex)
  1133  	})
  1134  }
  1135  
  1136  func TestExpandedRepoInfo(t *testing.T) {
  1137  	Convey("Filter out manifests with no tag", t, func() {
  1138  		tagToBeRemoved := "3.0"
  1139  		repo1 := "test1"
  1140  		tempDir := t.TempDir()
  1141  		port := GetFreePort()
  1142  		baseURL := GetBaseURL(port)
  1143  
  1144  		conf := config.New()
  1145  		conf.HTTP.Port = port
  1146  		conf.Storage.RootDirectory = tempDir
  1147  		defaultVal := true
  1148  		conf.Extensions = &extconf.ExtensionConfig{
  1149  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1150  		}
  1151  
  1152  		conf.Extensions.Search.CVE = nil
  1153  
  1154  		ctlr := api.NewController(conf)
  1155  
  1156  		imageStore := local.NewImageStore(tempDir, false, false,
  1157  			log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil)
  1158  
  1159  		storeController := storage.StoreController{
  1160  			DefaultStore: imageStore,
  1161  		}
  1162  
  1163  		// init storage layout with 3 images
  1164  		for i := 1; i <= 3; i++ {
  1165  			image := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Build()
  1166  
  1167  			err := WriteImageToFileSystem(image, repo1, fmt.Sprintf("%d.0", i), storeController)
  1168  			So(err, ShouldBeNil)
  1169  		}
  1170  
  1171  		// remote a tag from index.json
  1172  		indexPath := path.Join(tempDir, repo1, "index.json")
  1173  		indexFile, err := os.Open(indexPath)
  1174  		So(err, ShouldBeNil)
  1175  		buf, err := io.ReadAll(indexFile)
  1176  		So(err, ShouldBeNil)
  1177  
  1178  		var index ispec.Index
  1179  		if err = json.Unmarshal(buf, &index); err == nil {
  1180  			for _, manifest := range index.Manifests {
  1181  				if val, ok := manifest.Annotations[ispec.AnnotationRefName]; ok && val == tagToBeRemoved {
  1182  					delete(manifest.Annotations, ispec.AnnotationRefName)
  1183  
  1184  					break
  1185  				}
  1186  			}
  1187  		}
  1188  		buf, err = json.Marshal(index)
  1189  		So(err, ShouldBeNil)
  1190  
  1191  		err = os.WriteFile(indexPath, buf, 0o600)
  1192  		So(err, ShouldBeNil)
  1193  
  1194  		ctlrManager := NewControllerManager(ctlr)
  1195  		ctlrManager.StartAndWait(port)
  1196  		defer ctlrManager.StopServer()
  1197  
  1198  		query := `{
  1199  				ExpandedRepoInfo(repo:"test1"){
  1200  					Summary {
  1201  						Name LastUpdated Size
  1202  						Platforms {Os Arch}
  1203  						Vendors
  1204  					}
  1205  					Images {
  1206  						Tag
  1207  						Manifests {
  1208  							Digest
  1209  							Layers {Size Digest}
  1210  						}
  1211  					}
  1212  				}
  1213  			}`
  1214  
  1215  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1216  		So(resp, ShouldNotBeNil)
  1217  		So(err, ShouldBeNil)
  1218  		responseStruct := &zcommon.ExpandedRepoInfoResp{}
  1219  
  1220  		err = json.Unmarshal(resp.Body(), responseStruct)
  1221  		So(err, ShouldBeNil)
  1222  
  1223  		So(resp.StatusCode(), ShouldEqual, 200)
  1224  
  1225  		responseStruct = &zcommon.ExpandedRepoInfoResp{}
  1226  
  1227  		err = json.Unmarshal(resp.Body(), responseStruct)
  1228  		So(err, ShouldBeNil)
  1229  		So(responseStruct.Summary, ShouldNotBeEmpty)
  1230  		So(responseStruct.Summary.Name, ShouldEqual, "test1")
  1231  		So(len(responseStruct.ImageSummaries), ShouldEqual, 2)
  1232  	})
  1233  
  1234  	Convey("Test expanded repo info", t, func() {
  1235  		subpath := "/a"
  1236  		rootDir := t.TempDir()
  1237  		subRootDir := t.TempDir()
  1238  		port := GetFreePort()
  1239  		baseURL := GetBaseURL(port)
  1240  		conf := config.New()
  1241  		conf.HTTP.Port = port
  1242  		conf.Storage.RootDirectory = rootDir
  1243  		conf.Storage.GC = false
  1244  		conf.Storage.SubPaths = make(map[string]config.StorageConfig)
  1245  		conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
  1246  		defaultVal := true
  1247  		conf.Extensions = &extconf.ExtensionConfig{
  1248  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1249  		}
  1250  
  1251  		conf.Extensions.Search.CVE = nil
  1252  
  1253  		ctlr := api.NewController(conf)
  1254  		ctlrManager := NewControllerManager(ctlr)
  1255  		ctlrManager.StartAndWait(port)
  1256  		defer ctlrManager.StopServer()
  1257  
  1258  		annotations := make(map[string]string)
  1259  		annotations["org.opencontainers.image.vendor"] = "zot"
  1260  
  1261  		uploadedImage := CreateImageWith().RandomLayers(1, 100).DefaultConfig().
  1262  			Annotations(annotations).Build()
  1263  
  1264  		err := UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1")
  1265  		So(err, ShouldBeNil)
  1266  
  1267  		err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1")
  1268  		So(err, ShouldBeNil)
  1269  
  1270  		err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1")
  1271  		So(err, ShouldBeNil)
  1272  
  1273  		err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1")
  1274  		So(err, ShouldBeNil)
  1275  
  1276  		log := log.NewLogger("debug", "")
  1277  		metrics := monitoring.NewMetricsServer(false, log)
  1278  		testStorage := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil)
  1279  
  1280  		resp, err := resty.R().Get(baseURL + "/v2/")
  1281  		So(resp, ShouldNotBeNil)
  1282  		So(err, ShouldBeNil)
  1283  		So(resp.StatusCode(), ShouldEqual, 200)
  1284  
  1285  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix)
  1286  		So(resp, ShouldNotBeNil)
  1287  		So(err, ShouldBeNil)
  1288  		So(resp.StatusCode(), ShouldEqual, 422)
  1289  
  1290  		query := `{
  1291  			ExpandedRepoInfo(repo:"zot-cve-test"){
  1292  				Summary {
  1293  					Name LastUpdated Size
  1294  					}
  1295  				}
  1296  			}`
  1297  
  1298  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1299  		So(resp, ShouldNotBeNil)
  1300  		So(err, ShouldBeNil)
  1301  		So(resp.StatusCode(), ShouldEqual, 200)
  1302  
  1303  		responseStruct := &zcommon.ExpandedRepoInfoResp{}
  1304  
  1305  		err = json.Unmarshal(resp.Body(), responseStruct)
  1306  		So(err, ShouldBeNil)
  1307  		So(responseStruct.Summary, ShouldNotBeEmpty)
  1308  		So(responseStruct.Summary.Name, ShouldEqual, "zot-cve-test")
  1309  
  1310  		query = `{
  1311  			ExpandedRepoInfo(repo:"zot-cve-test"){
  1312  				Images {
  1313  					Tag
  1314  					Manifests {
  1315  						Digest
  1316  						Layers {Size Digest}
  1317  					}
  1318  					IsSigned
  1319  				}
  1320  			}
  1321  		}`
  1322  
  1323  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1324  		So(resp, ShouldNotBeNil)
  1325  		So(err, ShouldBeNil)
  1326  		So(resp.StatusCode(), ShouldEqual, 200)
  1327  
  1328  		responseStruct = &zcommon.ExpandedRepoInfoResp{}
  1329  
  1330  		err = json.Unmarshal(resp.Body(), responseStruct)
  1331  		So(err, ShouldBeNil)
  1332  		So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0)
  1333  		So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0)
  1334  
  1335  		_, testManifestDigest, _, err := testStorage.GetImageManifest("zot-cve-test", "0.0.1")
  1336  		So(err, ShouldBeNil)
  1337  
  1338  		found := false
  1339  		for _, m := range responseStruct.ImageSummaries {
  1340  			if m.Manifests[0].Digest == testManifestDigest.String() {
  1341  				found = true
  1342  				So(m.IsSigned, ShouldEqual, false)
  1343  			}
  1344  		}
  1345  		So(found, ShouldEqual, true)
  1346  
  1347  		err = signature.SignImageUsingCosign("zot-cve-test:0.0.1", port, false)
  1348  		So(err, ShouldBeNil)
  1349  
  1350  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1351  		So(resp, ShouldNotBeNil)
  1352  		So(err, ShouldBeNil)
  1353  		So(resp.StatusCode(), ShouldEqual, 200)
  1354  
  1355  		err = json.Unmarshal(resp.Body(), responseStruct)
  1356  		So(err, ShouldBeNil)
  1357  		So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0)
  1358  		So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0)
  1359  
  1360  		_, testManifestDigest, _, err = testStorage.GetImageManifest("zot-cve-test", "0.0.1")
  1361  		So(err, ShouldBeNil)
  1362  
  1363  		found = false
  1364  		for _, m := range responseStruct.ImageSummaries {
  1365  			if m.Manifests[0].Digest == testManifestDigest.String() {
  1366  				found = true
  1367  				So(m.IsSigned, ShouldEqual, true)
  1368  			}
  1369  		}
  1370  		So(found, ShouldEqual, true)
  1371  
  1372  		query = `{
  1373  			ExpandedRepoInfo(repo:""){
  1374  				Images {
  1375  					Tag
  1376  					}
  1377  				}
  1378  			}`
  1379  
  1380  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1381  		So(resp, ShouldNotBeNil)
  1382  		So(err, ShouldBeNil)
  1383  		So(resp.StatusCode(), ShouldEqual, 200)
  1384  
  1385  		query = `{
  1386  			ExpandedRepoInfo(repo:"zot-test"){
  1387  				Images {
  1388  					RepoName
  1389  					Tag IsSigned
  1390  					Manifests{
  1391  						Digest
  1392  						Layers {Size Digest}
  1393  					}
  1394  				}
  1395  			}
  1396  		}`
  1397  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1398  		So(resp, ShouldNotBeNil)
  1399  		So(err, ShouldBeNil)
  1400  		So(resp.StatusCode(), ShouldEqual, 200)
  1401  
  1402  		err = json.Unmarshal(resp.Body(), responseStruct)
  1403  		So(err, ShouldBeNil)
  1404  		So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0)
  1405  		So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0)
  1406  
  1407  		_, testManifestDigest, _, err = testStorage.GetImageManifest("zot-test", "0.0.1")
  1408  		So(err, ShouldBeNil)
  1409  
  1410  		found = false
  1411  		for _, m := range responseStruct.ImageSummaries {
  1412  			if m.Manifests[0].Digest == testManifestDigest.String() {
  1413  				found = true
  1414  				So(m.IsSigned, ShouldEqual, false)
  1415  			}
  1416  		}
  1417  		So(found, ShouldEqual, true)
  1418  
  1419  		err = signature.SignImageUsingCosign("zot-test@"+testManifestDigest.String(), port, false)
  1420  		So(err, ShouldBeNil)
  1421  
  1422  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "/query?query=" + url.QueryEscape(query))
  1423  		So(resp, ShouldNotBeNil)
  1424  		So(err, ShouldBeNil)
  1425  		So(resp.StatusCode(), ShouldEqual, 200)
  1426  
  1427  		err = json.Unmarshal(resp.Body(), responseStruct)
  1428  		So(err, ShouldBeNil)
  1429  		So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0)
  1430  		So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0)
  1431  
  1432  		_, testManifestDigest, _, err = testStorage.GetImageManifest("zot-test", "0.0.1")
  1433  		So(err, ShouldBeNil)
  1434  
  1435  		found = false
  1436  		for _, m := range responseStruct.ImageSummaries {
  1437  			if m.Manifests[0].Digest == testManifestDigest.String() {
  1438  				found = true
  1439  				So(m.IsSigned, ShouldEqual, true)
  1440  			}
  1441  		}
  1442  		So(found, ShouldEqual, true)
  1443  
  1444  		manifestDigest := uploadedImage.ManifestDescriptor.Digest
  1445  
  1446  		err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded()))
  1447  		So(err, ShouldBeNil)
  1448  
  1449  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1450  		So(resp, ShouldNotBeNil)
  1451  		So(err, ShouldBeNil)
  1452  		So(resp.StatusCode(), ShouldEqual, 200)
  1453  
  1454  		err = json.Unmarshal(resp.Body(), responseStruct)
  1455  		So(err, ShouldBeNil)
  1456  	})
  1457  
  1458  	Convey("Test expanded repo info with tagged referrers", t, func() {
  1459  		const testTag = "test"
  1460  		rootDir := t.TempDir()
  1461  		port := GetFreePort()
  1462  		baseURL := GetBaseURL(port)
  1463  		conf := config.New()
  1464  		conf.HTTP.Port = port
  1465  		conf.Storage.RootDirectory = rootDir
  1466  		conf.Storage.GC = false
  1467  		defaultVal := true
  1468  		conf.Extensions = &extconf.ExtensionConfig{
  1469  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1470  		}
  1471  
  1472  		conf.Extensions.Search.CVE = nil
  1473  
  1474  		ctlr := api.NewController(conf)
  1475  		ctlrManager := NewControllerManager(ctlr)
  1476  		ctlrManager.StartAndWait(port)
  1477  		defer ctlrManager.StopServer()
  1478  
  1479  		image := CreateImageWith().RandomLayers(1, 20).DefaultConfig().Build()
  1480  
  1481  		err := UploadImage(image, baseURL, "repo", testTag)
  1482  		So(err, ShouldBeNil)
  1483  
  1484  		referrer := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Subject(&ispec.Descriptor{
  1485  			Digest:    image.ManifestDescriptor.Digest,
  1486  			MediaType: ispec.MediaTypeImageManifest,
  1487  		}).Build()
  1488  
  1489  		tag := "test-ref-tag"
  1490  		err = UploadImage(referrer, baseURL, "repo", tag)
  1491  		So(err, ShouldBeNil)
  1492  
  1493  		// ------- Make the call to GQL and see that it doesn't crash
  1494  		responseStruct := &zcommon.ExpandedRepoInfoResp{}
  1495  		query := `
  1496  		{
  1497  			ExpandedRepoInfo(repo:"repo"){
  1498  				Images {
  1499  					RepoName
  1500  					Tag
  1501  					Manifests {
  1502  						Digest
  1503  						Layers {Size Digest}
  1504  					}
  1505  				}
  1506  			}
  1507  		}`
  1508  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1509  		So(resp, ShouldNotBeNil)
  1510  		So(err, ShouldBeNil)
  1511  		So(resp.StatusCode(), ShouldEqual, 200)
  1512  
  1513  		err = json.Unmarshal(resp.Body(), responseStruct)
  1514  		So(err, ShouldBeNil)
  1515  		So(len(responseStruct.ImageSummaries), ShouldEqual, 2)
  1516  
  1517  		repoInfo := responseStruct.RepoInfo
  1518  
  1519  		foundTagTest := false
  1520  		foundTagRefTag := false
  1521  
  1522  		for _, imgSum := range repoInfo.ImageSummaries {
  1523  			switch imgSum.Tag {
  1524  			case testTag:
  1525  				foundTagTest = true
  1526  			case "test-ref-tag":
  1527  				foundTagRefTag = true
  1528  			}
  1529  		}
  1530  
  1531  		So(foundTagTest || foundTagRefTag, ShouldEqual, true)
  1532  	})
  1533  
  1534  	Convey("Test image tags order", t, func() {
  1535  		port := GetFreePort()
  1536  		baseURL := GetBaseURL(port)
  1537  		conf := config.New()
  1538  		conf.HTTP.Port = port
  1539  		conf.Storage.RootDirectory = t.TempDir()
  1540  
  1541  		defaultVal := true
  1542  		conf.Extensions = &extconf.ExtensionConfig{
  1543  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1544  		}
  1545  
  1546  		conf.Extensions.Search.CVE = nil
  1547  
  1548  		ctlr := api.NewController(conf)
  1549  
  1550  		ctlrManager := NewControllerManager(ctlr)
  1551  		ctlrManager.StartAndWait(port)
  1552  		defer ctlrManager.StopServer()
  1553  
  1554  		resp, err := resty.R().Get(baseURL + "/v2/")
  1555  		So(resp, ShouldNotBeNil)
  1556  		So(err, ShouldBeNil)
  1557  		So(resp.StatusCode(), ShouldEqual, 200)
  1558  
  1559  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix)
  1560  		So(resp, ShouldNotBeNil)
  1561  		So(err, ShouldBeNil)
  1562  		So(resp.StatusCode(), ShouldEqual, 422)
  1563  
  1564  		// create test images
  1565  		repoName := "test-repo" //nolint:goconst
  1566  		layers := [][]byte{
  1567  			{10, 11, 10, 11},
  1568  		}
  1569  
  1570  		err = uploadNewRepoTag("1.0", repoName, baseURL, layers)
  1571  		So(err, ShouldBeNil)
  1572  
  1573  		err = uploadNewRepoTag("2.0", repoName, baseURL, layers)
  1574  		So(err, ShouldBeNil)
  1575  
  1576  		err = uploadNewRepoTag("3.0", repoName, baseURL, layers)
  1577  		So(err, ShouldBeNil)
  1578  
  1579  		responseStruct := &zcommon.ExpandedRepoInfoResp{}
  1580  		query := `
  1581  		{
  1582  			ExpandedRepoInfo(repo:"test-repo"){
  1583  				Images {
  1584  					RepoName
  1585  					Tag
  1586  					Manifests {
  1587  						Digest
  1588  						Layers {Size Digest}
  1589  					}
  1590  				}
  1591  			}
  1592  		}`
  1593  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1594  		So(resp, ShouldNotBeNil)
  1595  		So(err, ShouldBeNil)
  1596  		So(resp.StatusCode(), ShouldEqual, 200)
  1597  
  1598  		err = json.Unmarshal(resp.Body(), responseStruct)
  1599  		So(err, ShouldBeNil)
  1600  		So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0)
  1601  		So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0)
  1602  
  1603  		So(responseStruct.ImageSummaries[0].Tag, ShouldEqual, "3.0")
  1604  		So(responseStruct.ImageSummaries[1].Tag, ShouldEqual, "2.0")
  1605  		So(responseStruct.ImageSummaries[2].Tag, ShouldEqual, "1.0")
  1606  	})
  1607  
  1608  	Convey("With Multiarch Images", t, func() {
  1609  		conf := config.New()
  1610  		conf.HTTP.Port = GetFreePort()
  1611  		baseURL := GetBaseURL(conf.HTTP.Port)
  1612  		conf.Storage.RootDirectory = t.TempDir()
  1613  
  1614  		defaultVal := true
  1615  		conf.Extensions = &extconf.ExtensionConfig{
  1616  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1617  		}
  1618  
  1619  		conf.Extensions.Search.CVE = nil
  1620  		ctlr := api.NewController(conf)
  1621  
  1622  		imageStore := local.NewImageStore(conf.Storage.RootDirectory, false, false,
  1623  			log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil)
  1624  
  1625  		storeController := storage.StoreController{
  1626  			DefaultStore: imageStore,
  1627  		}
  1628  
  1629  		// ------- Create test images
  1630  		indexSubImage11 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  1631  			Platform: ispec.Platform{
  1632  				OS:           "os11",
  1633  				Architecture: "arch11",
  1634  			},
  1635  		}).Build()
  1636  
  1637  		indexSubImage12 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  1638  			Platform: ispec.Platform{
  1639  				OS:           "os12",
  1640  				Architecture: "arch12",
  1641  			},
  1642  		}).Build()
  1643  
  1644  		multiImage1 := CreateMultiarchWith().Images([]Image{
  1645  			indexSubImage11, indexSubImage12,
  1646  		}).Build()
  1647  
  1648  		indexSubImage21 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  1649  			Platform: ispec.Platform{
  1650  				OS:           "os21",
  1651  				Architecture: "arch21",
  1652  			},
  1653  		}).Build()
  1654  
  1655  		indexSubImage22 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  1656  			Platform: ispec.Platform{
  1657  				OS:           "os22",
  1658  				Architecture: "arch22",
  1659  			},
  1660  		}).Build()
  1661  
  1662  		indexSubImage23 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  1663  			Platform: ispec.Platform{
  1664  				OS:           "os23",
  1665  				Architecture: "arch23",
  1666  			},
  1667  		}).Build()
  1668  
  1669  		multiImage2 := CreateMultiarchWith().Images([]Image{
  1670  			indexSubImage21, indexSubImage22, indexSubImage23,
  1671  		}).Build()
  1672  
  1673  		// ------- Write test Images
  1674  		err := WriteMultiArchImageToFileSystem(multiImage1, "repo", "1.0.0", storeController)
  1675  		So(err, ShouldBeNil)
  1676  
  1677  		err = WriteMultiArchImageToFileSystem(multiImage2, "repo", "2.0.0", storeController)
  1678  		So(err, ShouldBeNil)
  1679  		// ------- Start Server /tmp/TestExpandedRepoInfo4021254039/005
  1680  
  1681  		ctlrManager := NewControllerManager(ctlr)
  1682  		ctlrManager.StartAndWait(conf.HTTP.Port)
  1683  		defer ctlrManager.StopServer()
  1684  
  1685  		// ------- Test ExpandedRepoInfo
  1686  		responseStruct := &zcommon.ExpandedRepoInfoResp{}
  1687  
  1688  		query := `
  1689  		{
  1690  			ExpandedRepoInfo(repo:"repo"){
  1691  				Images {
  1692  					RepoName
  1693  					Tag
  1694  					Manifests {
  1695  						Digest
  1696  						Layers {Size Digest}
  1697  					}
  1698  				}
  1699  			}
  1700  		}`
  1701  
  1702  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  1703  		So(resp, ShouldNotBeNil)
  1704  		So(err, ShouldBeNil)
  1705  		So(resp.StatusCode(), ShouldEqual, 200)
  1706  
  1707  		err = json.Unmarshal(resp.Body(), responseStruct)
  1708  		So(err, ShouldBeNil)
  1709  		So(len(responseStruct.Summary.Platforms), ShouldNotEqual, 5)
  1710  
  1711  		found := false
  1712  		for _, is := range responseStruct.ImageSummaries {
  1713  			if is.Tag == "1.0.0" {
  1714  				found = true
  1715  
  1716  				So(len(is.Manifests), ShouldEqual, 2)
  1717  			}
  1718  		}
  1719  		So(found, ShouldBeTrue)
  1720  
  1721  		found = false
  1722  		for _, is := range responseStruct.ImageSummaries {
  1723  			if is.Tag == "2.0.0" {
  1724  				found = true
  1725  
  1726  				So(len(is.Manifests), ShouldEqual, 3)
  1727  			}
  1728  		}
  1729  		So(found, ShouldBeTrue)
  1730  	})
  1731  }
  1732  
  1733  func TestDerivedImageList(t *testing.T) {
  1734  	rootDir := t.TempDir()
  1735  
  1736  	port := GetFreePort()
  1737  	baseURL := GetBaseURL(port)
  1738  	conf := config.New()
  1739  	conf.HTTP.Port = port
  1740  	conf.Storage.RootDirectory = rootDir
  1741  	defaultVal := true
  1742  	conf.Extensions = &extconf.ExtensionConfig{
  1743  		Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  1744  	}
  1745  
  1746  	conf.Extensions.Search.CVE = nil
  1747  
  1748  	ctlr := api.NewController(conf)
  1749  	ctlrManager := NewControllerManager(ctlr)
  1750  
  1751  	ctlrManager.StartAndWait(port)
  1752  	defer ctlrManager.StopServer()
  1753  
  1754  	Convey("Test dependency list for image working", t, func() {
  1755  		// create test images
  1756  		config := ispec.Image{
  1757  			Platform: ispec.Platform{
  1758  				Architecture: "amd64",
  1759  				OS:           "linux",
  1760  			},
  1761  			RootFS: ispec.RootFS{
  1762  				Type:    "layers",
  1763  				DiffIDs: []godigest.Digest{},
  1764  			},
  1765  			Author: "ZotUser",
  1766  		}
  1767  
  1768  		configBlob, err := json.Marshal(config)
  1769  		So(err, ShouldBeNil)
  1770  
  1771  		configDigest := godigest.FromBytes(configBlob)
  1772  
  1773  		layers := [][]byte{
  1774  			{10, 11, 10, 11},
  1775  			{11, 11, 11, 11},
  1776  			{10, 10, 10, 11},
  1777  		}
  1778  
  1779  		manifest := ispec.Manifest{
  1780  			Versioned: specs.Versioned{
  1781  				SchemaVersion: 2,
  1782  			},
  1783  			Config: ispec.Descriptor{
  1784  				MediaType: "application/vnd.oci.image.config.v1+json",
  1785  				Digest:    configDigest,
  1786  				Size:      int64(len(configBlob)),
  1787  			},
  1788  			Layers: []ispec.Descriptor{
  1789  				{
  1790  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1791  					Digest:    godigest.FromBytes(layers[0]),
  1792  					Size:      int64(len(layers[0])),
  1793  				},
  1794  				{
  1795  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1796  					Digest:    godigest.FromBytes(layers[1]),
  1797  					Size:      int64(len(layers[1])),
  1798  				},
  1799  				{
  1800  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1801  					Digest:    godigest.FromBytes(layers[2]),
  1802  					Size:      int64(len(layers[2])),
  1803  				},
  1804  			},
  1805  		}
  1806  
  1807  		repoName := "test-repo" //nolint:goconst
  1808  
  1809  		err = UploadImage(
  1810  			Image{
  1811  				Manifest: manifest,
  1812  				Config:   config,
  1813  				Layers:   layers,
  1814  			}, baseURL, repoName, "latest",
  1815  		)
  1816  		So(err, ShouldBeNil)
  1817  
  1818  		// create image with the same layers
  1819  		manifest = ispec.Manifest{
  1820  			Versioned: specs.Versioned{
  1821  				SchemaVersion: 2,
  1822  			},
  1823  			Config: ispec.Descriptor{
  1824  				MediaType: "application/vnd.oci.image.config.v1+json",
  1825  				Digest:    configDigest,
  1826  				Size:      int64(len(configBlob)),
  1827  			},
  1828  			Layers: []ispec.Descriptor{
  1829  				{
  1830  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1831  					Digest:    godigest.FromBytes(layers[0]),
  1832  					Size:      int64(len(layers[0])),
  1833  				},
  1834  				{
  1835  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1836  					Digest:    godigest.FromBytes(layers[1]),
  1837  					Size:      int64(len(layers[1])),
  1838  				},
  1839  				{
  1840  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1841  					Digest:    godigest.FromBytes(layers[2]),
  1842  					Size:      int64(len(layers[2])),
  1843  				},
  1844  			},
  1845  		}
  1846  
  1847  		repoName = "same-layers" //nolint:goconst
  1848  
  1849  		err = UploadImage(
  1850  			Image{
  1851  				Manifest: manifest,
  1852  				Config:   config,
  1853  				Layers:   layers,
  1854  			}, baseURL, repoName, "latest",
  1855  		)
  1856  		So(err, ShouldBeNil)
  1857  
  1858  		// create image with missing layer
  1859  		layers = [][]byte{
  1860  			{10, 11, 10, 11},
  1861  			{10, 10, 10, 11},
  1862  		}
  1863  
  1864  		manifest = ispec.Manifest{
  1865  			Versioned: specs.Versioned{
  1866  				SchemaVersion: 2,
  1867  			},
  1868  			Config: ispec.Descriptor{
  1869  				MediaType: "application/vnd.oci.image.config.v1+json",
  1870  				Digest:    configDigest,
  1871  				Size:      int64(len(configBlob)),
  1872  			},
  1873  			Layers: []ispec.Descriptor{
  1874  				{
  1875  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1876  					Digest:    godigest.FromBytes(layers[0]),
  1877  					Size:      int64(len(layers[0])),
  1878  				},
  1879  				{
  1880  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1881  					Digest:    godigest.FromBytes(layers[1]),
  1882  					Size:      int64(len(layers[1])),
  1883  				},
  1884  			},
  1885  		}
  1886  
  1887  		repoName = "missing-layer"
  1888  
  1889  		err = UploadImage(
  1890  			Image{
  1891  				Manifest: manifest,
  1892  				Config:   config,
  1893  				Layers:   layers,
  1894  			}, baseURL, repoName, "latest",
  1895  		)
  1896  		So(err, ShouldBeNil)
  1897  
  1898  		// create image with more layers than the original
  1899  		layers = [][]byte{
  1900  			{10, 11, 10, 11},
  1901  			{11, 11, 11, 11},
  1902  			{10, 10, 10, 10},
  1903  			{10, 10, 10, 11},
  1904  			{11, 11, 10, 10},
  1905  			{11, 10, 10, 10},
  1906  		}
  1907  
  1908  		manifest = ispec.Manifest{
  1909  			Versioned: specs.Versioned{
  1910  				SchemaVersion: 2,
  1911  			},
  1912  			Config: ispec.Descriptor{
  1913  				MediaType: "application/vnd.oci.image.config.v1+json",
  1914  				Digest:    configDigest,
  1915  				Size:      int64(len(configBlob)),
  1916  			},
  1917  			Layers: []ispec.Descriptor{
  1918  				{
  1919  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1920  					Digest:    godigest.FromBytes(layers[0]),
  1921  					Size:      int64(len(layers[0])),
  1922  				},
  1923  				{
  1924  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1925  					Digest:    godigest.FromBytes(layers[1]),
  1926  					Size:      int64(len(layers[1])),
  1927  				},
  1928  				{
  1929  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1930  					Digest:    godigest.FromBytes(layers[2]),
  1931  					Size:      int64(len(layers[2])),
  1932  				},
  1933  				{
  1934  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1935  					Digest:    godigest.FromBytes(layers[3]),
  1936  					Size:      int64(len(layers[3])),
  1937  				},
  1938  				{
  1939  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1940  					Digest:    godigest.FromBytes(layers[4]),
  1941  					Size:      int64(len(layers[4])),
  1942  				},
  1943  			},
  1944  		}
  1945  
  1946  		repoName = "more-layers"
  1947  
  1948  		err = UploadImage(
  1949  			Image{
  1950  				Manifest: manifest,
  1951  				Config:   config,
  1952  				Layers:   layers,
  1953  			}, baseURL, repoName, "latest",
  1954  		)
  1955  		So(err, ShouldBeNil)
  1956  
  1957  		manifest = ispec.Manifest{
  1958  			Versioned: specs.Versioned{
  1959  				SchemaVersion: 2,
  1960  			},
  1961  			Config: ispec.Descriptor{
  1962  				MediaType: "application/vnd.oci.image.config.v1+json",
  1963  				Digest:    configDigest,
  1964  				Size:      int64(len(configBlob)),
  1965  			},
  1966  			Layers: []ispec.Descriptor{
  1967  				{
  1968  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1969  					Digest:    godigest.FromBytes(layers[0]),
  1970  					Size:      int64(len(layers[0])),
  1971  				},
  1972  				{
  1973  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1974  					Digest:    godigest.FromBytes(layers[1]),
  1975  					Size:      int64(len(layers[1])),
  1976  				},
  1977  				{
  1978  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1979  					Digest:    godigest.FromBytes(layers[2]),
  1980  					Size:      int64(len(layers[2])),
  1981  				},
  1982  				{
  1983  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1984  					Digest:    godigest.FromBytes(layers[3]),
  1985  					Size:      int64(len(layers[3])),
  1986  				},
  1987  				{
  1988  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1989  					Digest:    godigest.FromBytes(layers[4]),
  1990  					Size:      int64(len(layers[4])),
  1991  				},
  1992  				{
  1993  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  1994  					Digest:    godigest.FromBytes(layers[5]),
  1995  					Size:      int64(len(layers[5])),
  1996  				},
  1997  			},
  1998  		}
  1999  
  2000  		repoName = "all-layers"
  2001  
  2002  		err = UploadImage(
  2003  			Image{
  2004  				Manifest: manifest,
  2005  				Config:   config,
  2006  				Layers:   layers,
  2007  			}, baseURL, repoName, "latest",
  2008  		)
  2009  		So(err, ShouldBeNil)
  2010  
  2011  		Convey("non paginated query", func() {
  2012  			query := `
  2013  				{
  2014  					DerivedImageList(image:"test-repo:latest"){
  2015  						Results{
  2016  							RepoName
  2017  							Tag
  2018  							Manifests {
  2019  								Digest
  2020  								ConfigDigest
  2021  								LastUpdated
  2022  								Size
  2023  							}
  2024  							Size
  2025  						}
  2026  					}
  2027  				}`
  2028  
  2029  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2030  			So(resp, ShouldNotBeNil)
  2031  			So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
  2032  			So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse)
  2033  			So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeTrue)
  2034  			So(strings.Contains(string(resp.Body()), "all-layers"), ShouldBeTrue)
  2035  			So(err, ShouldBeNil)
  2036  			So(resp.StatusCode(), ShouldEqual, 200)
  2037  		})
  2038  
  2039  		Convey("paginated query", func() {
  2040  			query := `
  2041  				{
  2042  					DerivedImageList(image:"test-repo:latest", requestedPage:{limit: 1, offset: 0, sortBy:ALPHABETIC_ASC}){
  2043  						Results{
  2044  							RepoName
  2045  							Tag
  2046  							Manifests {
  2047  								Digest
  2048  								ConfigDigest
  2049  								LastUpdated
  2050  								Size
  2051  							}
  2052  							Size
  2053  						}
  2054  					}
  2055  				}`
  2056  
  2057  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2058  			So(resp, ShouldNotBeNil)
  2059  			So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
  2060  			So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse)
  2061  			So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
  2062  			So(strings.Contains(string(resp.Body()), "all-layers"), ShouldBeTrue)
  2063  			So(err, ShouldBeNil)
  2064  			So(resp.StatusCode(), ShouldEqual, 200)
  2065  		})
  2066  	})
  2067  
  2068  	Convey("Inexistent repository", t, func() {
  2069  		query := `
  2070  			{
  2071  				DerivedImageList(image:"inexistent-image:latest"){
  2072  					Results{
  2073  						RepoName
  2074  						Tag
  2075  						Manifests {
  2076  							Digest
  2077  							ConfigDigest
  2078  							LastUpdated
  2079  							Size
  2080  						}
  2081  						Size
  2082  					}
  2083  				}
  2084  			}`
  2085  
  2086  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2087  		So(string(resp.Body()), ShouldContainSubstring, "repository not found")
  2088  		So(err, ShouldBeNil)
  2089  	})
  2090  
  2091  	Convey("Invalid query, no reference provided", t, func() {
  2092  		query := `
  2093  			{
  2094  				DerivedImageList(image:"inexistent-image"){
  2095  					Results{
  2096  						RepoName
  2097  						Tag
  2098  						Manifests {
  2099  							Digest
  2100  							ConfigDigest
  2101  							LastUpdated
  2102  							Size
  2103  						}
  2104  						Size
  2105  					}
  2106  				}
  2107  			}`
  2108  
  2109  		responseStruct := &zcommon.DerivedImageListResponse{}
  2110  		contains := false
  2111  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2112  		So(err, ShouldBeNil)
  2113  
  2114  		err = json.Unmarshal(resp.Body(), responseStruct)
  2115  		So(err, ShouldBeNil)
  2116  		for _, err := range responseStruct.Errors {
  2117  			result := strings.Contains(err.Message, "no reference provided")
  2118  			if result {
  2119  				contains = result
  2120  			}
  2121  		}
  2122  		So(contains, ShouldBeTrue)
  2123  	})
  2124  }
  2125  
  2126  //nolint:dupl
  2127  func TestDerivedImageListNoRepos(t *testing.T) {
  2128  	Convey("No repositories found", t, func() {
  2129  		port := GetFreePort()
  2130  		baseURL := GetBaseURL(port)
  2131  
  2132  		conf := config.New()
  2133  		conf.HTTP.Port = port
  2134  		conf.Storage.RootDirectory = t.TempDir()
  2135  		defaultVal := true
  2136  		conf.Extensions = &extconf.ExtensionConfig{
  2137  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  2138  		}
  2139  
  2140  		conf.Extensions.Search.CVE = nil
  2141  
  2142  		ctlr := api.NewController(conf)
  2143  
  2144  		ctlrManager := NewControllerManager(ctlr)
  2145  		ctlrManager.StartAndWait(port)
  2146  		defer ctlrManager.StopServer()
  2147  
  2148  		query := `
  2149  			{
  2150  				DerivedImageList(image:"test-image:latest"){
  2151  					Results{
  2152  						RepoName
  2153  						Tag
  2154  						Manifests {
  2155  							Digest
  2156  							ConfigDigest
  2157  							LastUpdated
  2158  							Size
  2159  						}
  2160  						Size
  2161  					}
  2162  				}
  2163  			}`
  2164  
  2165  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2166  		So(resp, ShouldNotBeNil)
  2167  		So(err, ShouldBeNil)
  2168  		So(resp.StatusCode(), ShouldEqual, 200)
  2169  
  2170  		So(string(resp.Body()), ShouldContainSubstring, "repository not found")
  2171  		So(err, ShouldBeNil)
  2172  	})
  2173  }
  2174  
  2175  func TestGetImageManifest(t *testing.T) {
  2176  	Convey("Test nonexistent image", t, func() {
  2177  		mockImageStore := mocks.MockedImageStore{}
  2178  
  2179  		storeController := storage.StoreController{
  2180  			DefaultStore: mockImageStore,
  2181  		}
  2182  		olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
  2183  
  2184  		_, _, err := olu.GetImageManifest("nonexistent-repo", "latest")
  2185  		So(err, ShouldNotBeNil)
  2186  	})
  2187  
  2188  	Convey("Test nonexistent image", t, func() {
  2189  		mockImageStore := mocks.MockedImageStore{
  2190  			GetImageManifestFn: func(repo string, reference string) ([]byte, godigest.Digest, string, error) {
  2191  				return []byte{}, "", "", ErrTestError
  2192  			},
  2193  		}
  2194  
  2195  		storeController := storage.StoreController{
  2196  			DefaultStore: mockImageStore,
  2197  		}
  2198  		olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
  2199  
  2200  		_, _, err := olu.GetImageManifest("test-repo", "latest") //nolint:goconst
  2201  		So(err, ShouldNotBeNil)
  2202  	})
  2203  }
  2204  
  2205  func TestBaseImageList(t *testing.T) {
  2206  	rootDir := t.TempDir()
  2207  
  2208  	port := GetFreePort()
  2209  	baseURL := GetBaseURL(port)
  2210  	conf := config.New()
  2211  	conf.HTTP.Port = port
  2212  	conf.Storage.RootDirectory = rootDir
  2213  	defaultVal := true
  2214  	conf.Extensions = &extconf.ExtensionConfig{
  2215  		Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  2216  	}
  2217  
  2218  	conf.Extensions.Search.CVE = nil
  2219  
  2220  	ctlr := api.NewController(conf)
  2221  	ctlrManager := NewControllerManager(ctlr)
  2222  
  2223  	ctlrManager.StartAndWait(port)
  2224  	defer ctlrManager.StopServer()
  2225  
  2226  	Convey("Test base image list for image working", t, func() {
  2227  		// create test images
  2228  		config := ispec.Image{
  2229  			Platform: ispec.Platform{
  2230  				Architecture: "amd64",
  2231  				OS:           "linux",
  2232  			},
  2233  			RootFS: ispec.RootFS{
  2234  				Type:    "layers",
  2235  				DiffIDs: []godigest.Digest{},
  2236  			},
  2237  			Author: "ZotUser",
  2238  		}
  2239  
  2240  		configBlob, err := json.Marshal(config)
  2241  		So(err, ShouldBeNil)
  2242  
  2243  		configDigest := godigest.FromBytes(configBlob)
  2244  
  2245  		layers := [][]byte{
  2246  			{10, 11, 10, 11},
  2247  			{11, 11, 11, 11},
  2248  			{10, 10, 10, 11},
  2249  			{10, 10, 10, 10},
  2250  		}
  2251  
  2252  		manifest := ispec.Manifest{
  2253  			Versioned: specs.Versioned{
  2254  				SchemaVersion: 2,
  2255  			},
  2256  			Config: ispec.Descriptor{
  2257  				MediaType: "application/vnd.oci.image.config.v1+json",
  2258  				Digest:    configDigest,
  2259  				Size:      int64(len(configBlob)),
  2260  			},
  2261  			Layers: []ispec.Descriptor{
  2262  				{
  2263  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2264  					Digest:    godigest.FromBytes(layers[0]),
  2265  					Size:      int64(len(layers[0])),
  2266  				},
  2267  				{
  2268  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2269  					Digest:    godigest.FromBytes(layers[1]),
  2270  					Size:      int64(len(layers[1])),
  2271  				},
  2272  				{
  2273  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2274  					Digest:    godigest.FromBytes(layers[2]),
  2275  					Size:      int64(len(layers[2])),
  2276  				},
  2277  				{
  2278  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2279  					Digest:    godigest.FromBytes(layers[3]),
  2280  					Size:      int64(len(layers[3])),
  2281  				},
  2282  			},
  2283  		}
  2284  
  2285  		repoName := "test-repo" //nolint:goconst
  2286  
  2287  		err = UploadImage(
  2288  			Image{
  2289  				Manifest: manifest,
  2290  				Config:   config,
  2291  				Layers:   layers,
  2292  			}, baseURL, repoName, "latest",
  2293  		)
  2294  		So(err, ShouldBeNil)
  2295  
  2296  		// create image with the same layers
  2297  		manifest = ispec.Manifest{
  2298  			Versioned: specs.Versioned{
  2299  				SchemaVersion: 2,
  2300  			},
  2301  			Config: ispec.Descriptor{
  2302  				MediaType: "application/vnd.oci.image.config.v1+json",
  2303  				Digest:    configDigest,
  2304  				Size:      int64(len(configBlob)),
  2305  			},
  2306  			Layers: []ispec.Descriptor{
  2307  				{
  2308  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2309  					Digest:    godigest.FromBytes(layers[0]),
  2310  					Size:      int64(len(layers[0])),
  2311  				},
  2312  				{
  2313  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2314  					Digest:    godigest.FromBytes(layers[1]),
  2315  					Size:      int64(len(layers[1])),
  2316  				},
  2317  				{
  2318  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2319  					Digest:    godigest.FromBytes(layers[2]),
  2320  					Size:      int64(len(layers[2])),
  2321  				},
  2322  				{
  2323  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2324  					Digest:    godigest.FromBytes(layers[3]),
  2325  					Size:      int64(len(layers[3])),
  2326  				},
  2327  			},
  2328  		}
  2329  
  2330  		repoName = "same-layers" //nolint:goconst
  2331  
  2332  		err = UploadImage(
  2333  			Image{
  2334  				Manifest: manifest,
  2335  				Config:   config,
  2336  				Layers:   layers,
  2337  			}, baseURL, repoName, "latest",
  2338  		)
  2339  		So(err, ShouldBeNil)
  2340  
  2341  		// create image with less layers than the given image, but which are in the given image
  2342  		layers = [][]byte{
  2343  			{10, 11, 10, 11},
  2344  			{10, 10, 10, 11},
  2345  		}
  2346  
  2347  		manifest = ispec.Manifest{
  2348  			Versioned: specs.Versioned{
  2349  				SchemaVersion: 2,
  2350  			},
  2351  			Config: ispec.Descriptor{
  2352  				MediaType: "application/vnd.oci.image.config.v1+json",
  2353  				Digest:    configDigest,
  2354  				Size:      int64(len(configBlob)),
  2355  			},
  2356  			Layers: []ispec.Descriptor{
  2357  				{
  2358  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2359  					Digest:    godigest.FromBytes(layers[0]),
  2360  					Size:      int64(len(layers[0])),
  2361  				},
  2362  				{
  2363  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2364  					Digest:    godigest.FromBytes(layers[1]),
  2365  					Size:      int64(len(layers[1])),
  2366  				},
  2367  			},
  2368  		}
  2369  
  2370  		repoName = "less-layers"
  2371  
  2372  		err = UploadImage(
  2373  			Image{
  2374  				Manifest: manifest,
  2375  				Config:   config,
  2376  				Layers:   layers,
  2377  			}, baseURL, repoName, "latest",
  2378  		)
  2379  		So(err, ShouldBeNil)
  2380  
  2381  		// create image with one layer, which is also present in the given image
  2382  		layers = [][]byte{
  2383  			{10, 11, 10, 11},
  2384  		}
  2385  
  2386  		manifest = ispec.Manifest{
  2387  			Versioned: specs.Versioned{
  2388  				SchemaVersion: 2,
  2389  			},
  2390  			Config: ispec.Descriptor{
  2391  				MediaType: "application/vnd.oci.image.config.v1+json",
  2392  				Digest:    configDigest,
  2393  				Size:      int64(len(configBlob)),
  2394  			},
  2395  			Layers: []ispec.Descriptor{
  2396  				{
  2397  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2398  					Digest:    godigest.FromBytes(layers[0]),
  2399  					Size:      int64(len(layers[0])),
  2400  				},
  2401  			},
  2402  		}
  2403  
  2404  		err = UploadImage(
  2405  			Image{
  2406  				Manifest: manifest,
  2407  				Config:   config,
  2408  				Layers:   layers,
  2409  			}, baseURL, "one-layer", "latest",
  2410  		)
  2411  		So(err, ShouldBeNil)
  2412  
  2413  		// create image with one layer, which is also present in the given image
  2414  		layers = [][]byte{
  2415  			{10, 11, 10, 11},
  2416  		}
  2417  
  2418  		manifest = ispec.Manifest{
  2419  			Versioned: specs.Versioned{
  2420  				SchemaVersion: 2,
  2421  			},
  2422  			Config: ispec.Descriptor{
  2423  				MediaType: "application/vnd.oci.image.config.v1+json",
  2424  				Digest:    configDigest,
  2425  				Size:      int64(len(configBlob)),
  2426  			},
  2427  			Layers: []ispec.Descriptor{
  2428  				{
  2429  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2430  					Digest:    godigest.FromBytes(layers[0]),
  2431  					Size:      int64(len(layers[0])),
  2432  				},
  2433  			},
  2434  		}
  2435  
  2436  		err = UploadImage(
  2437  			Image{
  2438  				Manifest: manifest,
  2439  				Config:   config,
  2440  				Layers:   layers,
  2441  			}, baseURL, "one-layer", "latest",
  2442  		)
  2443  		So(err, ShouldBeNil)
  2444  
  2445  		// create image with one layer, which is also present in the given image
  2446  		layers = [][]byte{
  2447  			{10, 11, 10, 11},
  2448  		}
  2449  
  2450  		manifest = ispec.Manifest{
  2451  			Versioned: specs.Versioned{
  2452  				SchemaVersion: 2,
  2453  			},
  2454  			Config: ispec.Descriptor{
  2455  				MediaType: "application/vnd.oci.image.config.v1+json",
  2456  				Digest:    configDigest,
  2457  				Size:      int64(len(configBlob)),
  2458  			},
  2459  			Layers: []ispec.Descriptor{
  2460  				{
  2461  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2462  					Digest:    godigest.FromBytes(layers[0]),
  2463  					Size:      int64(len(layers[0])),
  2464  				},
  2465  			},
  2466  		}
  2467  
  2468  		repoName = "one-layer"
  2469  
  2470  		err = UploadImage(
  2471  			Image{
  2472  				Manifest: manifest,
  2473  				Config:   config,
  2474  				Layers:   layers,
  2475  			}, baseURL, repoName, "latest",
  2476  		)
  2477  		So(err, ShouldBeNil)
  2478  
  2479  		// create image with one layer, which is also present in the given image
  2480  		layers = [][]byte{
  2481  			{10, 11, 10, 11},
  2482  		}
  2483  
  2484  		manifest = ispec.Manifest{
  2485  			Versioned: specs.Versioned{
  2486  				SchemaVersion: 2,
  2487  			},
  2488  			Config: ispec.Descriptor{
  2489  				MediaType: "application/vnd.oci.image.config.v1+json",
  2490  				Digest:    configDigest,
  2491  				Size:      int64(len(configBlob)),
  2492  			},
  2493  			Layers: []ispec.Descriptor{
  2494  				{
  2495  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2496  					Digest:    godigest.FromBytes(layers[0]),
  2497  					Size:      int64(len(layers[0])),
  2498  				},
  2499  			},
  2500  		}
  2501  
  2502  		repoName = "one-layer"
  2503  
  2504  		err = UploadImage(
  2505  			Image{
  2506  				Manifest: manifest,
  2507  				Config:   config,
  2508  				Layers:   layers,
  2509  			}, baseURL, repoName, "latest",
  2510  		)
  2511  		So(err, ShouldBeNil)
  2512  
  2513  		// create image with less layers than the given image, but one layer isn't in the given image
  2514  		layers = [][]byte{
  2515  			{10, 11, 10, 11},
  2516  			{11, 10, 10, 11},
  2517  		}
  2518  
  2519  		manifest = ispec.Manifest{
  2520  			Versioned: specs.Versioned{
  2521  				SchemaVersion: 2,
  2522  			},
  2523  			Config: ispec.Descriptor{
  2524  				MediaType: "application/vnd.oci.image.config.v1+json",
  2525  				Digest:    configDigest,
  2526  				Size:      int64(len(configBlob)),
  2527  			},
  2528  			Layers: []ispec.Descriptor{
  2529  				{
  2530  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2531  					Digest:    godigest.FromBytes(layers[0]),
  2532  					Size:      int64(len(layers[0])),
  2533  				},
  2534  				{
  2535  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2536  					Digest:    godigest.FromBytes(layers[1]),
  2537  					Size:      int64(len(layers[1])),
  2538  				},
  2539  			},
  2540  		}
  2541  
  2542  		repoName = "less-layers-false"
  2543  
  2544  		err = UploadImage(
  2545  			Image{
  2546  				Manifest: manifest,
  2547  				Config:   config,
  2548  				Layers:   layers,
  2549  			}, baseURL, repoName, "latest",
  2550  		)
  2551  		So(err, ShouldBeNil)
  2552  
  2553  		// create image with more layers than the original
  2554  		layers = [][]byte{
  2555  			{10, 11, 10, 11},
  2556  			{11, 11, 11, 11},
  2557  			{10, 10, 10, 10},
  2558  			{10, 10, 10, 11},
  2559  			{11, 11, 10, 10},
  2560  		}
  2561  
  2562  		manifest = ispec.Manifest{
  2563  			Versioned: specs.Versioned{
  2564  				SchemaVersion: 2,
  2565  			},
  2566  			Config: ispec.Descriptor{
  2567  				MediaType: "application/vnd.oci.image.config.v1+json",
  2568  				Digest:    configDigest,
  2569  				Size:      int64(len(configBlob)),
  2570  			},
  2571  			Layers: []ispec.Descriptor{
  2572  				{
  2573  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2574  					Digest:    godigest.FromBytes(layers[0]),
  2575  					Size:      int64(len(layers[0])),
  2576  				},
  2577  				{
  2578  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2579  					Digest:    godigest.FromBytes(layers[1]),
  2580  					Size:      int64(len(layers[1])),
  2581  				},
  2582  				{
  2583  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2584  					Digest:    godigest.FromBytes(layers[2]),
  2585  					Size:      int64(len(layers[2])),
  2586  				},
  2587  				{
  2588  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2589  					Digest:    godigest.FromBytes(layers[3]),
  2590  					Size:      int64(len(layers[3])),
  2591  				},
  2592  				{
  2593  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2594  					Digest:    godigest.FromBytes(layers[4]),
  2595  					Size:      int64(len(layers[4])),
  2596  				},
  2597  			},
  2598  		}
  2599  
  2600  		repoName = "more-layers"
  2601  
  2602  		err = UploadImage(
  2603  			Image{
  2604  				Manifest: manifest,
  2605  				Config:   config,
  2606  				Layers:   layers,
  2607  			}, baseURL, repoName, "latest",
  2608  		)
  2609  		So(err, ShouldBeNil)
  2610  
  2611  		// create image with no shared layers with the given image
  2612  		layers = [][]byte{
  2613  			{12, 12, 12, 12},
  2614  			{12, 10, 10, 12},
  2615  		}
  2616  
  2617  		manifest = ispec.Manifest{
  2618  			Versioned: specs.Versioned{
  2619  				SchemaVersion: 2,
  2620  			},
  2621  			Config: ispec.Descriptor{
  2622  				MediaType: "application/vnd.oci.image.config.v1+json",
  2623  				Digest:    configDigest,
  2624  				Size:      int64(len(configBlob)),
  2625  			},
  2626  			Layers: []ispec.Descriptor{
  2627  				{
  2628  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2629  					Digest:    godigest.FromBytes(layers[0]),
  2630  					Size:      int64(len(layers[0])),
  2631  				},
  2632  				{
  2633  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2634  					Digest:    godigest.FromBytes(layers[1]),
  2635  					Size:      int64(len(layers[1])),
  2636  				},
  2637  			},
  2638  		}
  2639  
  2640  		repoName = "diff-layers"
  2641  
  2642  		err = UploadImage(
  2643  			Image{
  2644  				Manifest: manifest,
  2645  				Config:   config,
  2646  				Layers:   layers,
  2647  			}, baseURL, repoName, "latest",
  2648  		)
  2649  		So(err, ShouldBeNil)
  2650  
  2651  		Convey("non paginated query", func() {
  2652  			query := `
  2653  				{
  2654  					BaseImageList(image:"test-repo:latest"){
  2655  						Results{
  2656  							RepoName
  2657  							Tag IsSigned
  2658  							Manifests {
  2659  								Digest
  2660  								ConfigDigest
  2661  								LastUpdated
  2662  								Size
  2663  							}
  2664  							Size
  2665  						}
  2666  					}
  2667  				}`
  2668  
  2669  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2670  			So(resp, ShouldNotBeNil)
  2671  			So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue)
  2672  			So(strings.Contains(string(resp.Body()), "one-layer"), ShouldBeTrue)
  2673  			So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
  2674  			So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse)
  2675  			So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
  2676  			So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse)
  2677  			So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image
  2678  			So(err, ShouldBeNil)
  2679  			So(resp.StatusCode(), ShouldEqual, 200)
  2680  		})
  2681  
  2682  		Convey("paginated query", func() {
  2683  			query := `
  2684  				{
  2685  					BaseImageList(image:"test-repo:latest", requestedPage:{limit: 1, offset: 0, sortBy:RELEVANCE}){
  2686  						Results{
  2687  							RepoName
  2688  							Tag
  2689  							Manifests {
  2690  								Digest
  2691  								ConfigDigest
  2692  								LastUpdated
  2693  								Size
  2694  							}
  2695  							Size
  2696  						}
  2697  					}
  2698  				}`
  2699  
  2700  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2701  			So(resp, ShouldNotBeNil)
  2702  			So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue)
  2703  			So(strings.Contains(string(resp.Body()), "one-layer"), ShouldBeFalse)
  2704  			So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
  2705  			So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse)
  2706  			So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
  2707  			So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse)
  2708  			So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image
  2709  			So(err, ShouldBeNil)
  2710  			So(resp.StatusCode(), ShouldEqual, 200)
  2711  		})
  2712  	})
  2713  
  2714  	Convey("Nonexistent repository", t, func() {
  2715  		query := `
  2716  			{
  2717  				BaseImageList(image:"nonexistent-image:latest"){
  2718  					Results{
  2719  						RepoName
  2720  						Tag
  2721  						Manifests {
  2722  							Digest
  2723  							ConfigDigest
  2724  							LastUpdated
  2725  							Size
  2726  						}
  2727  						Size
  2728  					}
  2729  				}
  2730  			}`
  2731  
  2732  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2733  		So(string(resp.Body()), ShouldContainSubstring, "repository not found")
  2734  		So(err, ShouldBeNil)
  2735  	})
  2736  
  2737  	Convey("Invalid query, no reference provided", t, func() {
  2738  		query := `
  2739  		{
  2740  			BaseImageList(image:"nonexistent-image"){
  2741  				Results{
  2742  					RepoName
  2743  					Tag
  2744  					Manifests {
  2745  						Digest
  2746  						ConfigDigest
  2747  						LastUpdated
  2748  						Size
  2749  					}
  2750  					Size
  2751  				}
  2752  			}
  2753  		}`
  2754  
  2755  		responseStruct := &zcommon.BaseImageListResponse{}
  2756  		contains := false
  2757  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2758  		So(err, ShouldBeNil)
  2759  
  2760  		err = json.Unmarshal(resp.Body(), responseStruct)
  2761  		So(err, ShouldBeNil)
  2762  		for _, err := range responseStruct.Errors {
  2763  			result := strings.Contains(err.Message, "no reference provided")
  2764  			if result {
  2765  				contains = result
  2766  			}
  2767  		}
  2768  		So(contains, ShouldBeTrue)
  2769  	})
  2770  }
  2771  
  2772  //nolint:dupl
  2773  func TestBaseImageListNoRepos(t *testing.T) {
  2774  	Convey("No repositories found", t, func() {
  2775  		port := GetFreePort()
  2776  		baseURL := GetBaseURL(port)
  2777  
  2778  		conf := config.New()
  2779  		conf.HTTP.Port = port
  2780  		conf.Storage.RootDirectory = t.TempDir()
  2781  		defaultVal := true
  2782  		conf.Extensions = &extconf.ExtensionConfig{
  2783  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  2784  		}
  2785  
  2786  		conf.Extensions.Search.CVE = nil
  2787  
  2788  		ctlr := api.NewController(conf)
  2789  
  2790  		ctlrManager := NewControllerManager(ctlr)
  2791  		ctlrManager.StartAndWait(port)
  2792  		defer ctlrManager.StopServer()
  2793  
  2794  		query := `
  2795  			{
  2796  				BaseImageList(image:"test-image"){
  2797  					Results{
  2798  						RepoName
  2799  						Tag
  2800  						Manifests {
  2801  							Digest
  2802  							ConfigDigest
  2803  							LastUpdated
  2804  							Size
  2805  						}
  2806  						IsSigned
  2807  						Size
  2808  					}
  2809  				}
  2810  			}`
  2811  
  2812  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2813  		So(strings.Contains(string(resp.Body()), "no reference provided"), ShouldBeTrue)
  2814  		So(err, ShouldBeNil)
  2815  	})
  2816  }
  2817  
  2818  func TestGetRepositories(t *testing.T) {
  2819  	Convey("Test getting the repositories list", t, func() {
  2820  		mockImageStore := mocks.MockedImageStore{
  2821  			GetRepositoriesFn: func() ([]string, error) {
  2822  				return []string{}, ErrTestError
  2823  			},
  2824  		}
  2825  
  2826  		storeController := storage.StoreController{
  2827  			DefaultStore: mockImageStore,
  2828  			SubStore:     map[string]storageTypes.ImageStore{"test": mockImageStore},
  2829  		}
  2830  		olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
  2831  
  2832  		repoList, err := olu.GetRepositories()
  2833  		So(repoList, ShouldBeEmpty)
  2834  		So(err, ShouldNotBeNil)
  2835  
  2836  		storeController = storage.StoreController{
  2837  			DefaultStore: mocks.MockedImageStore{},
  2838  			SubStore:     map[string]storageTypes.ImageStore{"test": mockImageStore},
  2839  		}
  2840  		olu = ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
  2841  
  2842  		repoList, err = olu.GetRepositories()
  2843  		So(repoList, ShouldBeEmpty)
  2844  		So(err, ShouldNotBeNil)
  2845  	})
  2846  }
  2847  
  2848  func TestGlobalSearchImageAuthor(t *testing.T) {
  2849  	port := GetFreePort()
  2850  	baseURL := GetBaseURL(port)
  2851  	conf := config.New()
  2852  	conf.HTTP.Port = port
  2853  	tempDir := t.TempDir()
  2854  	conf.Storage.RootDirectory = tempDir
  2855  
  2856  	defaultVal := true
  2857  	conf.Extensions = &extconf.ExtensionConfig{
  2858  		Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  2859  	}
  2860  
  2861  	conf.Extensions.Search.CVE = nil
  2862  
  2863  	ctlr := api.NewController(conf)
  2864  	ctlrManager := NewControllerManager(ctlr)
  2865  
  2866  	ctlrManager.StartAndWait(port)
  2867  	defer ctlrManager.StopServer()
  2868  
  2869  	Convey("Test global search with author in manifest's annotations", t, func() {
  2870  		annotations := make(map[string]string)
  2871  		annotations["org.opencontainers.image.authors"] = "author name"
  2872  
  2873  		image := CreateImageWith().RandomLayers(1, 10000).DefaultConfig().
  2874  			Annotations(annotations).Build()
  2875  
  2876  		err := UploadImage(image, baseURL, "repowithauthor", "latest")
  2877  		So(err, ShouldBeNil)
  2878  
  2879  		query := `
  2880  			{
  2881  				GlobalSearch(query:"repowithauthor:latest"){
  2882  					Images {
  2883  						RepoName Tag LastUpdated Size IsSigned
  2884  						Authors
  2885  					}
  2886  				}
  2887  			}`
  2888  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2889  		So(resp, ShouldNotBeNil)
  2890  		So(err, ShouldBeNil)
  2891  		So(resp.StatusCode(), ShouldEqual, 200)
  2892  
  2893  		responseStructImages := &zcommon.GlobalSearchResultResp{}
  2894  
  2895  		err = json.Unmarshal(resp.Body(), responseStructImages)
  2896  		So(err, ShouldBeNil)
  2897  
  2898  		So(responseStructImages.Images[0].Authors, ShouldEqual, "author name")
  2899  
  2900  		query = `
  2901  		{
  2902  			GlobalSearch(query:"repowithauthor"){
  2903  				Repos {
  2904  					Name LastUpdated Size
  2905  					Platforms { Os Arch }
  2906  					Vendors
  2907  					NewestImage {
  2908  						RepoName Tag LastUpdated Size IsSigned
  2909  						Authors
  2910  					}
  2911  				}
  2912  			}
  2913  		}`
  2914  
  2915  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2916  		So(resp, ShouldNotBeNil)
  2917  		So(err, ShouldBeNil)
  2918  		So(resp.StatusCode(), ShouldEqual, 200)
  2919  
  2920  		responseStructRepos := &zcommon.GlobalSearchResultResp{}
  2921  
  2922  		err = json.Unmarshal(resp.Body(), responseStructRepos)
  2923  		So(err, ShouldBeNil)
  2924  
  2925  		So(responseStructRepos.Repos[0].NewestImage.Authors, ShouldEqual, "author name")
  2926  	})
  2927  
  2928  	Convey("Test global search with author in manifest's config", t, func() {
  2929  		image := CreateImageWith().RandomLayers(1, 10000).DefaultConfig().Build()
  2930  
  2931  		err := UploadImage(image, baseURL, "repowithauthorconfig", "latest")
  2932  		So(err, ShouldBeNil)
  2933  
  2934  		query := `
  2935  			{
  2936  				GlobalSearch(query:"repowithauthorconfig:latest"){
  2937  					Images {
  2938  						RepoName Tag LastUpdated Size IsSigned
  2939  						Authors
  2940  					}
  2941  				}
  2942  			}`
  2943  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2944  		So(resp, ShouldNotBeNil)
  2945  		So(err, ShouldBeNil)
  2946  		So(resp.StatusCode(), ShouldEqual, 200)
  2947  
  2948  		responseStructImages := &zcommon.GlobalSearchResultResp{}
  2949  
  2950  		err = json.Unmarshal(resp.Body(), responseStructImages)
  2951  		So(err, ShouldBeNil)
  2952  
  2953  		So(responseStructImages.Images[0].Authors, ShouldEqual, "ZotUser")
  2954  
  2955  		query = `
  2956  		{
  2957  			GlobalSearch(query:"repowithauthorconfig"){
  2958  				Repos {
  2959  					Name LastUpdated Size
  2960  					Platforms { Os Arch }
  2961  					Vendors
  2962  					NewestImage {
  2963  						RepoName Tag LastUpdated Size IsSigned
  2964  						Authors
  2965  					}
  2966  				}
  2967  			}
  2968  		}`
  2969  
  2970  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  2971  		So(resp, ShouldNotBeNil)
  2972  		So(err, ShouldBeNil)
  2973  		So(resp.StatusCode(), ShouldEqual, 200)
  2974  
  2975  		responseStructRepos := &zcommon.GlobalSearchResultResp{}
  2976  
  2977  		err = json.Unmarshal(resp.Body(), responseStructRepos)
  2978  		So(err, ShouldBeNil)
  2979  
  2980  		So(responseStructRepos.Repos[0].NewestImage.Authors, ShouldEqual, "ZotUser")
  2981  	})
  2982  }
  2983  
  2984  func TestGlobalSearch(t *testing.T) {
  2985  	Convey("Test searching for repos with vulnerabitity scanning disabled", t, func() {
  2986  		subpath := "/a"
  2987  
  2988  		dir := t.TempDir()
  2989  		subDir := t.TempDir()
  2990  
  2991  		subRootDir := path.Join(subDir, subpath)
  2992  
  2993  		port := GetFreePort()
  2994  		baseURL := GetBaseURL(port)
  2995  		conf := config.New()
  2996  		conf.HTTP.Port = port
  2997  		conf.Storage.RootDirectory = dir
  2998  		conf.Storage.SubPaths = make(map[string]config.StorageConfig)
  2999  		conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
  3000  		defaultVal := true
  3001  		conf.Extensions = &extconf.ExtensionConfig{
  3002  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  3003  		}
  3004  
  3005  		conf.Extensions.Search.CVE = nil
  3006  
  3007  		ctlr := api.NewController(conf)
  3008  
  3009  		ctlrManager := NewControllerManager(ctlr)
  3010  		ctlrManager.StartAndWait(port)
  3011  		defer ctlrManager.StopServer()
  3012  
  3013  		// push test images to repo 1 image 1
  3014  		createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
  3015  		createdTimeL2 := time.Date(2010, 2, 1, 12, 0, 0, 0, time.UTC)
  3016  		config1 := ispec.Image{
  3017  			Created: &createdTimeL2,
  3018  			Platform: ispec.Platform{
  3019  				Architecture: "amd64",
  3020  				OS:           "linux",
  3021  			},
  3022  			RootFS: ispec.RootFS{
  3023  				Type:    "layers",
  3024  				DiffIDs: []godigest.Digest{},
  3025  			},
  3026  			Author: "ZotUser",
  3027  			History: []ispec.History{
  3028  				{
  3029  					Created:    &createdTime,
  3030  					CreatedBy:  "go test data",
  3031  					Author:     "ZotUser",
  3032  					Comment:    "Test history comment",
  3033  					EmptyLayer: true,
  3034  				},
  3035  				{
  3036  					Created:    &createdTimeL2,
  3037  					CreatedBy:  "go test data 2",
  3038  					Author:     "ZotUser",
  3039  					Comment:    "Test history comment2",
  3040  					EmptyLayer: false,
  3041  				},
  3042  			},
  3043  		}
  3044  
  3045  		image1 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 100)}).
  3046  			ImageConfig(config1).Build()
  3047  
  3048  		layersSize1 := 0
  3049  		for _, l := range image1.Layers {
  3050  			layersSize1 += len(l)
  3051  		}
  3052  
  3053  		err := UploadImage(image1, baseURL, "repo1", "1.0.1")
  3054  		So(err, ShouldBeNil)
  3055  
  3056  		// push test images to repo 1 image 2
  3057  		createdTime2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
  3058  		createdTimeL2 = time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC)
  3059  
  3060  		config2 := ispec.Image{
  3061  			Created: &createdTimeL2,
  3062  			Platform: ispec.Platform{
  3063  				Architecture: "amd64",
  3064  				OS:           "linux",
  3065  			},
  3066  			RootFS: ispec.RootFS{
  3067  				Type:    "layers",
  3068  				DiffIDs: []godigest.Digest{},
  3069  			},
  3070  			Author: "ZotUser",
  3071  			History: []ispec.History{
  3072  				{
  3073  					Created:    &createdTime2,
  3074  					CreatedBy:  "go test data",
  3075  					Author:     "ZotUser",
  3076  					Comment:    "Test history comment",
  3077  					EmptyLayer: true,
  3078  				},
  3079  				{
  3080  					Created:    &createdTimeL2,
  3081  					CreatedBy:  "go test data 2",
  3082  					Author:     "ZotUser",
  3083  					Comment:    "Test history comment2",
  3084  					EmptyLayer: false,
  3085  				},
  3086  			},
  3087  		}
  3088  
  3089  		image2 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 200)}).
  3090  			ImageConfig(config2).Build()
  3091  
  3092  		layersSize2 := 0
  3093  		for _, l := range image2.Layers {
  3094  			layersSize2 += len(l)
  3095  		}
  3096  
  3097  		err = UploadImage(image2, baseURL, "repo1", "1.0.2")
  3098  		So(err, ShouldBeNil)
  3099  
  3100  		// push test images to repo 2 image 1
  3101  		createdTime3 := time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC)
  3102  		config3 := ispec.Image{
  3103  			Created: &createdTime3,
  3104  			Platform: ispec.Platform{
  3105  				Architecture: "amd64",
  3106  				OS:           "linux",
  3107  			},
  3108  			RootFS: ispec.RootFS{
  3109  				Type:    "layers",
  3110  				DiffIDs: []godigest.Digest{},
  3111  			},
  3112  			Author:  "ZotUser",
  3113  			History: []ispec.History{{Created: &createdTime3}},
  3114  		}
  3115  
  3116  		image3 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 300)}).
  3117  			ImageConfig(config3).Build()
  3118  
  3119  		layersSize3 := 0
  3120  		for _, l := range image3.Layers {
  3121  			layersSize3 += len(l)
  3122  		}
  3123  
  3124  		err = UploadImage(image3, baseURL, "repo2", "1.0.0")
  3125  		So(err, ShouldBeNil)
  3126  
  3127  		olu := ociutils.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
  3128  
  3129  		// Initialize the objects containing the expected data
  3130  		repos, err := olu.GetRepositories()
  3131  		So(err, ShouldBeNil)
  3132  
  3133  		allExpectedRepoInfoMap := make(map[string]zcommon.RepoInfo)
  3134  		allExpectedImageSummaryMap := make(map[string]zcommon.ImageSummary)
  3135  		for _, repo := range repos {
  3136  			repoInfo, err := olu.GetExpandedRepoInfo(repo)
  3137  			So(err, ShouldBeNil)
  3138  			allExpectedRepoInfoMap[repo] = repoInfo
  3139  			for _, image := range repoInfo.ImageSummaries {
  3140  				imageName := fmt.Sprintf("%s:%s", repo, image.Tag)
  3141  				allExpectedImageSummaryMap[imageName] = image
  3142  			}
  3143  		}
  3144  
  3145  		query := `
  3146  			{
  3147  				GlobalSearch(query:"repo"){
  3148  					Images {
  3149  						RepoName Tag LastUpdated Size IsSigned
  3150  						Manifests {
  3151  							LastUpdated
  3152  							Size
  3153  							Platform { Os Arch }
  3154  							History {
  3155  								Layer { Size Digest }
  3156  								HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3157  							}
  3158  							Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3159  						}
  3160  						Vendor
  3161  						Vulnerabilities { Count MaxSeverity }
  3162  					}
  3163  					Repos {
  3164  						Name LastUpdated Size
  3165  						Platforms { Os Arch }
  3166  						Vendors
  3167  						NewestImage {
  3168  							RepoName Tag LastUpdated Size
  3169  							Digest
  3170  							Manifests{
  3171  								Digest ConfigDigest
  3172  								LastUpdated Size
  3173  								Platform { Os Arch }
  3174  								History {
  3175  									Layer { Size Digest }
  3176  									HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3177  								}
  3178  							}
  3179  							Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3180  						}
  3181  					}
  3182  					Layers { Digest Size }
  3183  				}
  3184  			}`
  3185  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  3186  		So(resp, ShouldNotBeNil)
  3187  		So(err, ShouldBeNil)
  3188  		So(resp.StatusCode(), ShouldEqual, 200)
  3189  
  3190  		responseStruct := &zcommon.GlobalSearchResultResp{}
  3191  
  3192  		err = json.Unmarshal(resp.Body(), responseStruct)
  3193  		So(err, ShouldBeNil)
  3194  
  3195  		// Make sure the repo/image counts match before comparing actual content
  3196  		So(responseStruct.Images, ShouldNotBeNil)
  3197  		t.Logf("returned images: %v", responseStruct.Images)
  3198  		So(responseStruct.Images, ShouldBeEmpty)
  3199  		t.Logf("returned repos: %v", responseStruct.Repos)
  3200  		So(len(responseStruct.Repos), ShouldEqual, 2)
  3201  		t.Logf("returned layers: %v", responseStruct.GlobalSearch.Layers)
  3202  		So(responseStruct.Layers, ShouldBeEmpty)
  3203  
  3204  		newestImageMap := make(map[string]zcommon.ImageSummary)
  3205  		actualRepoMap := make(map[string]zcommon.RepoSummary)
  3206  		for _, repo := range responseStruct.Repos {
  3207  			newestImageMap[repo.Name] = repo.NewestImage
  3208  			actualRepoMap[repo.Name] = repo
  3209  		}
  3210  
  3211  		// Tag 1.0.2 has a history entry which is older compare to 1.0.1
  3212  		So(newestImageMap["repo1"].Tag, ShouldEqual, "1.0.1")
  3213  		So(newestImageMap["repo1"].LastUpdated, ShouldEqual, time.Date(2010, 2, 1, 12, 0, 0, 0, time.UTC))
  3214  
  3215  		So(newestImageMap["repo2"].Tag, ShouldEqual, "1.0.0")
  3216  		So(newestImageMap["repo2"].LastUpdated, ShouldEqual, time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC))
  3217  
  3218  		for repoName, repoSummary := range actualRepoMap {
  3219  			repoSummary := repoSummary
  3220  
  3221  			// Check if data in NewestImage is consistent with the data in RepoSummary
  3222  			So(repoName, ShouldEqual, repoSummary.NewestImage.RepoName)
  3223  			So(repoSummary.Name, ShouldEqual, repoSummary.NewestImage.RepoName)
  3224  			So(repoSummary.LastUpdated, ShouldEqual, repoSummary.NewestImage.LastUpdated)
  3225  
  3226  			// The data in the RepoSummary returned from the request matches the data returned from the disk
  3227  			repoInfo := allExpectedRepoInfoMap[repoName]
  3228  
  3229  			t.Logf("Validate repo summary returned by global search with vulnerability scanning disabled")
  3230  			verifyRepoSummaryFields(t, &repoSummary, &repoInfo.Summary)
  3231  
  3232  			// RepoInfo object does not provide vulnerability information so we need to check differently
  3233  			// No vulnerabilities should be detected since trivy is disabled
  3234  			t.Logf("Found vulnerability summary %v", repoSummary.NewestImage.Vulnerabilities)
  3235  			So(repoSummary.NewestImage.Vulnerabilities.Count, ShouldEqual, 0)
  3236  			So(repoSummary.NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  3237  			So(repoSummary.NewestImage.Vulnerabilities.LowCount, ShouldEqual, 0)
  3238  			So(repoSummary.NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 0)
  3239  			So(repoSummary.NewestImage.Vulnerabilities.HighCount, ShouldEqual, 0)
  3240  			So(repoSummary.NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 0)
  3241  			So(repoSummary.NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "")
  3242  		}
  3243  
  3244  		query = `
  3245  		{
  3246  			GlobalSearch(query:"repo1:1.0.1"){
  3247  				Images {
  3248  					RepoName Tag LastUpdated Size
  3249  					Manifests {
  3250  						LastUpdated Size
  3251  						Platform { Os Arch }
  3252  						History {
  3253  							Layer { Size Digest }
  3254  							HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3255  						}
  3256  					}
  3257  					Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3258  				}
  3259  				Repos {
  3260  					Name LastUpdated Size
  3261  					Platforms { Os Arch }
  3262  					Vendors
  3263  					NewestImage {
  3264  						RepoName Tag LastUpdated Size
  3265  						Manifests {
  3266  							LastUpdated Size
  3267  							Platform { Os Arch }
  3268  							History {
  3269  								Layer { Size Digest }
  3270  								HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3271  							}
  3272  						}
  3273  						Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3274  					}
  3275  				}
  3276  				Layers { Digest Size }
  3277  			}
  3278  		}`
  3279  
  3280  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  3281  		So(resp, ShouldNotBeNil)
  3282  		So(err, ShouldBeNil)
  3283  		So(resp.StatusCode(), ShouldEqual, 200)
  3284  
  3285  		responseStruct = &zcommon.GlobalSearchResultResp{}
  3286  
  3287  		err = json.Unmarshal(resp.Body(), responseStruct)
  3288  		So(err, ShouldBeNil)
  3289  
  3290  		So(responseStruct.Images, ShouldNotBeEmpty)
  3291  		So(responseStruct.Repos, ShouldBeEmpty)
  3292  		So(responseStruct.Layers, ShouldBeEmpty)
  3293  
  3294  		So(len(responseStruct.Images), ShouldEqual, 1)
  3295  		actualImageSummary := responseStruct.Images[0]
  3296  		So(actualImageSummary.Tag, ShouldEqual, "1.0.1")
  3297  
  3298  		expectedImageSummary, ok := allExpectedImageSummaryMap["repo1:1.0.1"]
  3299  		So(ok, ShouldEqual, true)
  3300  
  3301  		t.Logf("Validate image summary returned by global search with vulnerability scanning disabled")
  3302  		verifyImageSummaryFields(t, &actualImageSummary, &expectedImageSummary)
  3303  
  3304  		// RepoInfo object does not provide vulnerability information so we need to check differently
  3305  		// 0 vulnerabilities should be detected since trivy is disabled
  3306  		t.Logf("Found vulnerability summary %v", actualImageSummary.Vulnerabilities)
  3307  		So(actualImageSummary.Vulnerabilities.Count, ShouldEqual, 0)
  3308  		So(actualImageSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  3309  		So(actualImageSummary.Vulnerabilities.LowCount, ShouldEqual, 0)
  3310  		So(actualImageSummary.Vulnerabilities.MediumCount, ShouldEqual, 0)
  3311  		So(actualImageSummary.Vulnerabilities.HighCount, ShouldEqual, 0)
  3312  		So(actualImageSummary.Vulnerabilities.CriticalCount, ShouldEqual, 0)
  3313  		So(actualImageSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "")
  3314  	})
  3315  
  3316  	Convey("Test global search with real images and vulnerabitity scanning enabled", t, func() {
  3317  		subpath := "/a"
  3318  
  3319  		dir := t.TempDir()
  3320  		subDir := t.TempDir()
  3321  
  3322  		subRootDir := path.Join(subDir, subpath)
  3323  
  3324  		port := GetFreePort()
  3325  		baseURL := GetBaseURL(port)
  3326  		conf := config.New()
  3327  		conf.HTTP.Port = port
  3328  		conf.Storage.RootDirectory = dir
  3329  		conf.Storage.SubPaths = make(map[string]config.StorageConfig)
  3330  		conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
  3331  		defaultVal := true
  3332  
  3333  		updateDuration, _ := time.ParseDuration("1h")
  3334  		trivyConfig := &extconf.TrivyConfig{
  3335  			DBRepository: "ghcr.io/project-zot/trivy-db",
  3336  		}
  3337  		cveConfig := &extconf.CVEConfig{
  3338  			UpdateInterval: updateDuration,
  3339  			Trivy:          trivyConfig,
  3340  		}
  3341  		searchConfig := &extconf.SearchConfig{
  3342  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
  3343  			CVE:        cveConfig,
  3344  		}
  3345  		conf.Extensions = &extconf.ExtensionConfig{
  3346  			Search: searchConfig,
  3347  		}
  3348  
  3349  		// we won't use the logging config feature as we want logs in both
  3350  		// stdout and a file
  3351  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
  3352  		So(err, ShouldBeNil)
  3353  		logPath := logFile.Name()
  3354  		defer os.Remove(logPath)
  3355  
  3356  		writers := io.MultiWriter(os.Stdout, logFile)
  3357  
  3358  		ctlr := api.NewController(conf)
  3359  		ctlr.Log.Logger = ctlr.Log.Output(writers)
  3360  
  3361  		if err := ctlr.Init(); err != nil {
  3362  			panic(err)
  3363  		}
  3364  
  3365  		ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
  3366  
  3367  		go func() {
  3368  			if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
  3369  				panic(err)
  3370  			}
  3371  		}()
  3372  
  3373  		defer ctlr.Shutdown()
  3374  
  3375  		// Wait for trivy db to download
  3376  		substring := "{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000," +
  3377  			"\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"}}}"
  3378  		found, err := readFileAndSearchString(logPath, substring, 2*time.Minute)
  3379  		So(found, ShouldBeTrue)
  3380  		So(err, ShouldBeNil)
  3381  
  3382  		found, err = readFileAndSearchString(logPath, "updating cve-db", 2*time.Minute)
  3383  		So(found, ShouldBeTrue)
  3384  		So(err, ShouldBeNil)
  3385  
  3386  		found, err = readFileAndSearchString(logPath, "cve-db update completed, next update scheduled after interval",
  3387  			4*time.Minute)
  3388  		So(found, ShouldBeTrue)
  3389  		So(err, ShouldBeNil)
  3390  
  3391  		WaitTillServerReady(baseURL)
  3392  
  3393  		// push test images to repo 1 image 1
  3394  		createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
  3395  		config1 := ispec.Image{
  3396  			Created: &createdTime,
  3397  			Platform: ispec.Platform{
  3398  				Architecture: "amd64",
  3399  				OS:           "linux",
  3400  			},
  3401  			RootFS: ispec.RootFS{
  3402  				Type:    "layers",
  3403  				DiffIDs: []godigest.Digest{},
  3404  			},
  3405  			Author:  "ZotUser",
  3406  			History: []ispec.History{{Created: &createdTime}},
  3407  		}
  3408  
  3409  		image1 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 100)}).
  3410  			ImageConfig(config1).Build()
  3411  
  3412  		layersSize1 := 0
  3413  		for _, l := range image1.Layers {
  3414  			layersSize1 += len(l)
  3415  		}
  3416  
  3417  		err = UploadImage(image1, baseURL, "repo1", "1.0.1")
  3418  		So(err, ShouldBeNil)
  3419  
  3420  		// push test images to repo 1 image 2
  3421  		createdTime2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
  3422  		config2 := ispec.Image{
  3423  			Platform: ispec.Platform{
  3424  				Architecture: "amd64",
  3425  				OS:           "linux",
  3426  			},
  3427  			RootFS: ispec.RootFS{
  3428  				Type:    "layers",
  3429  				DiffIDs: []godigest.Digest{},
  3430  			},
  3431  			Author:  "ZotUser",
  3432  			History: []ispec.History{{Created: &createdTime2}},
  3433  		}
  3434  
  3435  		image2 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 200)}).
  3436  			ImageConfig(config2).Build()
  3437  
  3438  		layersSize2 := 0
  3439  		for _, l := range image2.Layers {
  3440  			layersSize2 += len(l)
  3441  		}
  3442  
  3443  		err = UploadImage(image2, baseURL, "repo1", "1.0.2")
  3444  		So(err, ShouldBeNil)
  3445  
  3446  		// push test images to repo 2 image 1
  3447  		createdTime3 := time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC)
  3448  		config3 := ispec.Image{
  3449  			Platform: ispec.Platform{
  3450  				Architecture: "amd64",
  3451  				OS:           "linux",
  3452  			},
  3453  			RootFS: ispec.RootFS{
  3454  				Type:    "layers",
  3455  				DiffIDs: []godigest.Digest{},
  3456  			},
  3457  			Author:  "ZotUser",
  3458  			History: []ispec.History{{Created: &createdTime3}},
  3459  		}
  3460  
  3461  		image3 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 300)}).
  3462  			ImageConfig(config3).Build()
  3463  
  3464  		layersSize3 := 0
  3465  		for _, l := range image3.Layers {
  3466  			layersSize3 += len(l)
  3467  		}
  3468  
  3469  		err = UploadImage(image3, baseURL, "repo2", "1.0.0")
  3470  		So(err, ShouldBeNil)
  3471  
  3472  		olu := ociutils.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
  3473  
  3474  		// Initialize the objects containing the expected data
  3475  		repos, err := olu.GetRepositories()
  3476  		So(err, ShouldBeNil)
  3477  
  3478  		allExpectedRepoInfoMap := make(map[string]zcommon.RepoInfo)
  3479  		allExpectedImageSummaryMap := make(map[string]zcommon.ImageSummary)
  3480  		for _, repo := range repos {
  3481  			repoInfo, err := olu.GetExpandedRepoInfo(repo)
  3482  			So(err, ShouldBeNil)
  3483  			allExpectedRepoInfoMap[repo] = repoInfo
  3484  			for _, image := range repoInfo.ImageSummaries {
  3485  				imageName := fmt.Sprintf("%s:%s", repo, image.Tag)
  3486  				allExpectedImageSummaryMap[imageName] = image
  3487  			}
  3488  		}
  3489  
  3490  		query := `
  3491  			{
  3492  				GlobalSearch(query:"repo"){
  3493  					Images {
  3494  						RepoName Tag LastUpdated Size
  3495  						Manifests {
  3496  							LastUpdated Size
  3497  							Platform { Os Arch }
  3498  							History {
  3499  								Layer { Size Digest }
  3500  								HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3501  							}
  3502  						}
  3503  						Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3504  					}
  3505  					Repos {
  3506  						Name LastUpdated Size
  3507  						Platforms { Os Arch }
  3508  						Vendors
  3509  						NewestImage {
  3510  							RepoName Tag LastUpdated Size
  3511  							Manifests {
  3512  								LastUpdated Size
  3513  								Platform { Os Arch }
  3514  								History {
  3515  									Layer { Size Digest }
  3516  									HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3517  								}
  3518  							}
  3519  							Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3520  						}
  3521  					}
  3522  					Layers { Digest Size }
  3523  				}
  3524  			}`
  3525  
  3526  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  3527  		So(resp, ShouldNotBeNil)
  3528  		So(err, ShouldBeNil)
  3529  		So(resp.StatusCode(), ShouldEqual, 200)
  3530  
  3531  		responseStruct := &zcommon.GlobalSearchResultResp{}
  3532  
  3533  		err = json.Unmarshal(resp.Body(), responseStruct)
  3534  		So(err, ShouldBeNil)
  3535  
  3536  		// Make sure the repo/image counts match before comparing actual content
  3537  		So(responseStruct.Images, ShouldNotBeNil)
  3538  		t.Logf("returned images: %v", responseStruct.Images)
  3539  		So(responseStruct.Images, ShouldBeEmpty)
  3540  		t.Logf("returned repos: %v", responseStruct.Repos)
  3541  		So(len(responseStruct.Repos), ShouldEqual, 2)
  3542  		t.Logf("returned layers: %v", responseStruct.Layers)
  3543  		So(responseStruct.Layers, ShouldBeEmpty)
  3544  
  3545  		newestImageMap := make(map[string]zcommon.ImageSummary)
  3546  		actualRepoMap := make(map[string]zcommon.RepoSummary)
  3547  		for _, repo := range responseStruct.Repos {
  3548  			newestImageMap[repo.Name] = repo.NewestImage
  3549  			actualRepoMap[repo.Name] = repo
  3550  		}
  3551  
  3552  		// Tag 1.0.2 has a history entry which is older compare to 1.0.1
  3553  		So(newestImageMap["repo1"].Tag, ShouldEqual, "1.0.1")
  3554  		So(newestImageMap["repo1"].LastUpdated, ShouldEqual, time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC))
  3555  
  3556  		So(newestImageMap["repo2"].Tag, ShouldEqual, "1.0.0")
  3557  		So(newestImageMap["repo2"].LastUpdated, ShouldEqual, time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC))
  3558  
  3559  		for repoName, repoSummary := range actualRepoMap {
  3560  			repoSummary := repoSummary
  3561  
  3562  			// Check if data in NewestImage is consistent with the data in RepoSummary
  3563  			So(repoName, ShouldEqual, repoSummary.NewestImage.RepoName)
  3564  			So(repoSummary.Name, ShouldEqual, repoSummary.NewestImage.RepoName)
  3565  			So(repoSummary.LastUpdated, ShouldEqual, repoSummary.NewestImage.LastUpdated)
  3566  
  3567  			// The data in the RepoSummary returned from the request matches the data returned from the disk
  3568  			repoInfo := allExpectedRepoInfoMap[repoName]
  3569  
  3570  			t.Logf("Validate repo summary returned by global search with vulnerability scanning enabled")
  3571  			verifyRepoSummaryFields(t, &repoSummary, &repoInfo.Summary)
  3572  
  3573  			// RepoInfo object does not provide vulnerability information so we need to check differently
  3574  			t.Logf("Found vulnerability summary %v", repoSummary.NewestImage.Vulnerabilities)
  3575  			if repoName == "repo1" { //nolint:goconst
  3576  				So(repoSummary.NewestImage.Vulnerabilities.Count, ShouldEqual, 4)
  3577  				// There are 4 vulnerabilities in the data used in tests
  3578  				So(repoSummary.NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  3579  				So(repoSummary.NewestImage.Vulnerabilities.LowCount, ShouldEqual, 1)
  3580  				So(repoSummary.NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 1)
  3581  				So(repoSummary.NewestImage.Vulnerabilities.HighCount, ShouldEqual, 1)
  3582  				So(repoSummary.NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 1)
  3583  				So(repoSummary.NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
  3584  			} else {
  3585  				So(repoSummary.NewestImage.Vulnerabilities.Count, ShouldEqual, 0)
  3586  				// There are 0 vulnerabilities this data used in tests
  3587  				So(repoSummary.NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  3588  				So(repoSummary.NewestImage.Vulnerabilities.LowCount, ShouldEqual, 0)
  3589  				So(repoSummary.NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 0)
  3590  				So(repoSummary.NewestImage.Vulnerabilities.HighCount, ShouldEqual, 0)
  3591  				So(repoSummary.NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 0)
  3592  				So(repoSummary.NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "NONE")
  3593  			}
  3594  		}
  3595  
  3596  		query = `
  3597  		{
  3598  			GlobalSearch(query:"repo1:1.0.1"){
  3599  				Images {
  3600  					RepoName Tag LastUpdated Size
  3601  					Manifests {
  3602  						LastUpdated Size
  3603  						Platform { Os Arch }
  3604  						History {
  3605  							Layer { Size Digest }
  3606  							HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3607  						}
  3608  					}
  3609  					Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount  MaxSeverity }
  3610  				}
  3611  				Repos {
  3612  					Name LastUpdated Size
  3613  					Platforms { Os Arch }
  3614  					Vendors
  3615  					NewestImage {
  3616  						RepoName Tag LastUpdated Size
  3617  						Manifests {
  3618  							LastUpdated Size
  3619  							Platform { Os Arch }
  3620  							History {
  3621  								Layer { Size Digest }
  3622  								HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  3623  							}
  3624  						}
  3625  						Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  3626  					}
  3627  				}
  3628  				Layers { Digest Size }
  3629  			}
  3630  		}`
  3631  
  3632  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  3633  		So(resp, ShouldNotBeNil)
  3634  		So(err, ShouldBeNil)
  3635  		So(resp.StatusCode(), ShouldEqual, 200)
  3636  
  3637  		responseStruct = &zcommon.GlobalSearchResultResp{}
  3638  
  3639  		err = json.Unmarshal(resp.Body(), responseStruct)
  3640  		So(err, ShouldBeNil)
  3641  
  3642  		So(responseStruct.Images, ShouldNotBeEmpty)
  3643  		So(responseStruct.Repos, ShouldBeEmpty)
  3644  		So(responseStruct.Layers, ShouldBeEmpty)
  3645  
  3646  		So(len(responseStruct.Images), ShouldEqual, 1)
  3647  		actualImageSummary := responseStruct.Images[0]
  3648  		So(actualImageSummary.Tag, ShouldEqual, "1.0.1")
  3649  
  3650  		expectedImageSummary, ok := allExpectedImageSummaryMap["repo1:1.0.1"]
  3651  		So(ok, ShouldEqual, true)
  3652  
  3653  		t.Logf("Validate image summary returned by global search with vulnerability scanning enable")
  3654  		verifyImageSummaryFields(t, &actualImageSummary, &expectedImageSummary)
  3655  
  3656  		// RepoInfo object does not provide vulnerability information so we need to check differently
  3657  		t.Logf("Found vulnerability summary %v", actualImageSummary.Vulnerabilities)
  3658  		// There are 4 vulnerabilities in the data used in tests
  3659  		So(actualImageSummary.Vulnerabilities.Count, ShouldEqual, 4)
  3660  		So(actualImageSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  3661  		So(actualImageSummary.Vulnerabilities.LowCount, ShouldEqual, 1)
  3662  		So(actualImageSummary.Vulnerabilities.MediumCount, ShouldEqual, 1)
  3663  		So(actualImageSummary.Vulnerabilities.HighCount, ShouldEqual, 1)
  3664  		So(actualImageSummary.Vulnerabilities.CriticalCount, ShouldEqual, 1)
  3665  		So(actualImageSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
  3666  	})
  3667  
  3668  	Convey("global searching by digest", t, func() {
  3669  		log := log.NewLogger("debug", "")
  3670  		rootDir := t.TempDir()
  3671  		port := GetFreePort()
  3672  		baseURL := GetBaseURL(port)
  3673  		conf := config.New()
  3674  		conf.HTTP.Port = port
  3675  		conf.Storage.RootDirectory = rootDir
  3676  		defaultVal := true
  3677  		conf.Extensions = &extconf.ExtensionConfig{
  3678  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  3679  		}
  3680  		conf.Extensions.Search.CVE = nil
  3681  
  3682  		ctlr := api.NewController(conf)
  3683  		ctlrManager := NewControllerManager(ctlr)
  3684  
  3685  		storeCtlr := ociutils.GetDefaultStoreController(rootDir, log)
  3686  
  3687  		image1 := CreateRandomImage()
  3688  		image2 := CreateRandomImage()
  3689  		multiArch := CreateRandomMultiarch()
  3690  
  3691  		err := WriteImageToFileSystem(image1, "repo1", "tag1", storeCtlr)
  3692  		So(err, ShouldBeNil)
  3693  		err = WriteImageToFileSystem(image2, "repo1", "tag2", storeCtlr)
  3694  		So(err, ShouldBeNil)
  3695  		err = WriteMultiArchImageToFileSystem(multiArch, "repo1", "tag-multi", storeCtlr)
  3696  		So(err, ShouldBeNil)
  3697  
  3698  		err = WriteImageToFileSystem(image2, "repo2", "tag2", storeCtlr)
  3699  		So(err, ShouldBeNil)
  3700  
  3701  		ctlrManager.StartAndWait(port)
  3702  		defer ctlrManager.StopServer()
  3703  
  3704  		// simple image
  3705  		results := GlobalSearchGQL(image1.DigestStr(), baseURL).GlobalSearch
  3706  		So(len(results.Images), ShouldEqual, 1)
  3707  		So(results.Images[0].Digest, ShouldResemble, image1.DigestStr())
  3708  		So(results.Images[0].RepoName, ShouldResemble, "repo1")
  3709  
  3710  		results = GlobalSearchGQL(image2.DigestStr(), baseURL).GlobalSearch
  3711  		So(len(results.Images), ShouldEqual, 2)
  3712  
  3713  		repos := AccumulateField(results.Images,
  3714  			func(is zcommon.ImageSummary) string { return is.RepoName })
  3715  		So(repos, ShouldContain, "repo1")
  3716  		So(repos, ShouldContain, "repo2")
  3717  
  3718  		// multiarch
  3719  		results = GlobalSearchGQL(multiArch.DigestStr(), baseURL).GlobalSearch
  3720  		So(len(results.Images), ShouldEqual, 1)
  3721  		So(results.Images[0].Digest, ShouldResemble, multiArch.DigestStr())
  3722  		So(len(results.Images[0].Manifests), ShouldEqual, len(multiArch.Images))
  3723  		So(results.Images[0].RepoName, ShouldResemble, "repo1")
  3724  
  3725  		results = GlobalSearchGQL(multiArch.Images[0].DigestStr(), baseURL).GlobalSearch
  3726  		So(len(results.Images), ShouldEqual, 1)
  3727  		So(results.Images[0].Digest, ShouldResemble, multiArch.DigestStr())
  3728  		So(len(results.Images[0].Manifests), ShouldEqual, 1)
  3729  		So(results.Images[0].Manifests[0].Digest, ShouldResemble, multiArch.Images[0].DigestStr())
  3730  		So(results.Images[0].RepoName, ShouldResemble, "repo1")
  3731  	})
  3732  
  3733  	Convey("global searching by tag cross repo", t, func() {
  3734  		log := log.NewLogger("debug", "")
  3735  		rootDir := t.TempDir()
  3736  		port := GetFreePort()
  3737  		baseURL := GetBaseURL(port)
  3738  		conf := config.New()
  3739  		conf.HTTP.Port = port
  3740  		conf.Storage.RootDirectory = rootDir
  3741  		defaultVal := true
  3742  		conf.Extensions = &extconf.ExtensionConfig{
  3743  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  3744  		}
  3745  		conf.Extensions.Search.CVE = nil
  3746  
  3747  		ctlr := api.NewController(conf)
  3748  		ctlrManager := NewControllerManager(ctlr)
  3749  
  3750  		storeCtlr := ociutils.GetDefaultStoreController(rootDir, log)
  3751  
  3752  		image11 := CreateRandomImage()
  3753  		image12 := CreateRandomImage()
  3754  		err := WriteImageToFileSystem(image11, "repo1", "tag1", storeCtlr)
  3755  		So(err, ShouldBeNil)
  3756  		err = WriteImageToFileSystem(image12, "repo1", "tag2", storeCtlr)
  3757  		So(err, ShouldBeNil)
  3758  
  3759  		image21 := CreateRandomImage()
  3760  		image22 := CreateRandomImage()
  3761  		multiArch2 := CreateRandomMultiarch()
  3762  		err = WriteImageToFileSystem(image21, "repo2", "tag1", storeCtlr)
  3763  		So(err, ShouldBeNil)
  3764  		err = WriteImageToFileSystem(image22, "repo2", "tag2", storeCtlr)
  3765  		So(err, ShouldBeNil)
  3766  		err = WriteMultiArchImageToFileSystem(multiArch2, "repo2", "tag-multi", storeCtlr)
  3767  		So(err, ShouldBeNil)
  3768  
  3769  		image31 := CreateRandomImage()
  3770  		image32 := CreateRandomImage()
  3771  		err = WriteImageToFileSystem(image31, "repo3", "tag1", storeCtlr)
  3772  		So(err, ShouldBeNil)
  3773  		err = WriteImageToFileSystem(image32, "repo3", "tag2", storeCtlr)
  3774  		So(err, ShouldBeNil)
  3775  
  3776  		image41 := CreateRandomImage()
  3777  		image42 := CreateRandomImage()
  3778  		multiArch4 := CreateRandomMultiarch()
  3779  		err = WriteImageToFileSystem(image41, "repo4", "tag1", storeCtlr)
  3780  		So(err, ShouldBeNil)
  3781  		err = WriteImageToFileSystem(image42, "repo4", "tag2", storeCtlr)
  3782  		So(err, ShouldBeNil)
  3783  		err = WriteMultiArchImageToFileSystem(multiArch4, "repo4", "tag-multi", storeCtlr)
  3784  		So(err, ShouldBeNil)
  3785  
  3786  		image51 := CreateRandomImage()
  3787  		err = WriteImageToFileSystem(image51, "repo5", "tag1", storeCtlr)
  3788  		So(err, ShouldBeNil)
  3789  
  3790  		multiArch62 := CreateRandomMultiarch()
  3791  		err = WriteMultiArchImageToFileSystem(multiArch62, "repo6", "tag2", storeCtlr)
  3792  		So(err, ShouldBeNil)
  3793  
  3794  		ctlrManager.StartAndWait(port)
  3795  		defer ctlrManager.StopServer()
  3796  
  3797  		// Search for a specific tag cross-repo and return single arch images
  3798  		results := GlobalSearchGQL(":tag1", baseURL).GlobalSearch
  3799  		So(len(results.Images), ShouldEqual, 5)
  3800  		So(len(results.Repos), ShouldEqual, 0)
  3801  
  3802  		expectedRepos := []string{"repo1", "repo2", "repo3", "repo4", "repo5"}
  3803  		for _, image := range results.Images {
  3804  			So(image.Tag, ShouldEqual, "tag1")
  3805  			So(image.RepoName, ShouldBeIn, expectedRepos)
  3806  			So(len(image.Manifests), ShouldEqual, 1)
  3807  		}
  3808  
  3809  		// Search for a specific tag cross-repo and return multi arch images
  3810  		results = GlobalSearchGQL(":tag-multi", baseURL).GlobalSearch
  3811  		So(len(results.Images), ShouldEqual, 2)
  3812  		So(len(results.Repos), ShouldEqual, 0)
  3813  
  3814  		expectedRepos = []string{"repo2", "repo4"}
  3815  		for _, image := range results.Images {
  3816  			So(image.Tag, ShouldEqual, "tag-multi")
  3817  			So(image.RepoName, ShouldBeIn, expectedRepos)
  3818  			So(len(image.Manifests), ShouldEqual, 3)
  3819  		}
  3820  
  3821  		// Search for a specific tag cross-repo and return mixed single and multiarch images
  3822  		results = GlobalSearchGQL(":tag2", baseURL).GlobalSearch
  3823  		So(len(results.Images), ShouldEqual, 5)
  3824  		So(len(results.Repos), ShouldEqual, 0)
  3825  
  3826  		expectedRepos = []string{"repo1", "repo2", "repo3", "repo4", "repo6"}
  3827  		for _, image := range results.Images {
  3828  			So(image.Tag, ShouldEqual, "tag2")
  3829  			So(image.RepoName, ShouldBeIn, expectedRepos)
  3830  			if image.RepoName == "repo6" {
  3831  				So(len(image.Manifests), ShouldEqual, 3)
  3832  			} else {
  3833  				So(len(image.Manifests), ShouldEqual, 1)
  3834  			}
  3835  		}
  3836  
  3837  		// Search for multiple tags using a partial match cross-repo and return multiarch images
  3838  		results = GlobalSearchGQL(":multi", baseURL).GlobalSearch
  3839  		So(len(results.Images), ShouldEqual, 2)
  3840  		So(len(results.Repos), ShouldEqual, 0)
  3841  
  3842  		expectedRepos = []string{"repo2", "repo4"}
  3843  		for _, image := range results.Images {
  3844  			So(image.Tag, ShouldContainSubstring, "multi")
  3845  			So(image.RepoName, ShouldBeIn, expectedRepos)
  3846  			So(len(image.Manifests), ShouldEqual, 3)
  3847  		}
  3848  
  3849  		// Search for multiple tags using a partial match cross-repo and return mixed single and multiarch images
  3850  		results = GlobalSearchGQL(":tag", baseURL).GlobalSearch
  3851  		So(len(results.Images), ShouldEqual, 12)
  3852  		So(len(results.Repos), ShouldEqual, 0)
  3853  
  3854  		expectedRepos = []string{"repo1", "repo2", "repo3", "repo4", "repo5", "repo6"}
  3855  		for _, image := range results.Images {
  3856  			So(image.Tag, ShouldContainSubstring, "tag")
  3857  			So(image.RepoName, ShouldBeIn, expectedRepos)
  3858  		}
  3859  
  3860  		// Search for a specific tag cross-repo and return mixt single and multiarch images
  3861  		result := GlobalSearchGQL(":", baseURL)
  3862  		errors := result.Errors
  3863  		So(len(errors), ShouldEqual, 1)
  3864  
  3865  		results = result.GlobalSearch
  3866  		So(len(results.Images), ShouldEqual, 0)
  3867  		So(len(results.Repos), ShouldEqual, 0)
  3868  	})
  3869  }
  3870  
  3871  func TestCleaningFilteringParamsGlobalSearch(t *testing.T) {
  3872  	Convey("Test cleaning filtering parameters for global search", t, func() {
  3873  		dir := t.TempDir()
  3874  
  3875  		port := GetFreePort()
  3876  		baseURL := GetBaseURL(port)
  3877  		conf := config.New()
  3878  		conf.HTTP.Port = port
  3879  		conf.Storage.RootDirectory = dir
  3880  		defaultVal := true
  3881  		conf.Extensions = &extconf.ExtensionConfig{
  3882  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  3883  		}
  3884  
  3885  		ctlr := api.NewController(conf)
  3886  
  3887  		ctlrManager := NewControllerManager(ctlr)
  3888  		ctlrManager.StartAndWait(port)
  3889  		defer ctlrManager.StopServer()
  3890  
  3891  		image := CreateImageWith().RandomLayers(1, 100).
  3892  			ImageConfig(ispec.Image{Platform: ispec.Platform{
  3893  				OS:           "windows",
  3894  				Architecture: "amd64",
  3895  			}}).Build()
  3896  
  3897  		err := UploadImage(image, baseURL, "repo1", image.DigestStr())
  3898  		So(err, ShouldBeNil)
  3899  
  3900  		image = CreateImageWith().RandomLayers(1, 100).
  3901  			ImageConfig(ispec.Image{Platform: ispec.Platform{
  3902  				OS:           "linux",
  3903  				Architecture: "amd64",
  3904  			}}).Build()
  3905  
  3906  		err = UploadImage(image, baseURL, "repo2", image.DigestStr())
  3907  		So(err, ShouldBeNil)
  3908  
  3909  		query := `
  3910  		{
  3911  			GlobalSearch(query:"repo", requestedPage:{limit: 3, offset: 0, sortBy:RELEVANCE},
  3912  			filter:{Os:["  linux", "Windows ", "  "], Arch:["","aMd64  "]}) {
  3913  				Repos {
  3914  					Name
  3915  				}
  3916  			}
  3917  		}`
  3918  
  3919  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  3920  		So(resp, ShouldNotBeNil)
  3921  		So(err, ShouldBeNil)
  3922  		So(resp.StatusCode(), ShouldEqual, 200)
  3923  
  3924  		responseStruct := &zcommon.GlobalSearchResultResp{}
  3925  
  3926  		err = json.Unmarshal(resp.Body(), responseStruct)
  3927  		So(err, ShouldBeNil)
  3928  	})
  3929  }
  3930  
  3931  func TestGlobalSearchFiltering(t *testing.T) {
  3932  	Convey("Global search HasToBeSigned filtering", t, func() {
  3933  		dir := t.TempDir()
  3934  		port := GetFreePort()
  3935  		baseURL := GetBaseURL(port)
  3936  		conf := config.New()
  3937  		conf.HTTP.Port = port
  3938  		conf.Storage.RootDirectory = dir
  3939  
  3940  		defaultVal := true
  3941  		conf.Extensions = &extconf.ExtensionConfig{
  3942  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  3943  		}
  3944  
  3945  		ctlr := api.NewController(conf)
  3946  
  3947  		ctlrManager := NewControllerManager(ctlr)
  3948  		ctlrManager.StartAndWait(port)
  3949  		defer ctlrManager.StopServer()
  3950  
  3951  		image := CreateRandomImage()
  3952  
  3953  		err := UploadImage(image, baseURL, "unsigned-repo", "test")
  3954  		So(err, ShouldBeNil)
  3955  
  3956  		image = CreateRandomImage()
  3957  
  3958  		err = UploadImage(image, baseURL, "signed-repo", "test")
  3959  		So(err, ShouldBeNil)
  3960  
  3961  		err = signature.SignImageUsingCosign("signed-repo:test", port, false)
  3962  		So(err, ShouldBeNil)
  3963  
  3964  		query := `{
  3965  			GlobalSearch(query:"repo",
  3966  			filter:{HasToBeSigned:true}) {
  3967  				Repos {
  3968  					Name
  3969  				}
  3970  			}
  3971  		}`
  3972  
  3973  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  3974  		So(resp, ShouldNotBeNil)
  3975  		So(err, ShouldBeNil)
  3976  		So(resp.StatusCode(), ShouldEqual, 200)
  3977  
  3978  		responseStruct := &zcommon.GlobalSearchResultResp{}
  3979  
  3980  		err = json.Unmarshal(resp.Body(), responseStruct)
  3981  		So(err, ShouldBeNil)
  3982  
  3983  		So(responseStruct.Repos, ShouldNotBeEmpty)
  3984  		So(responseStruct.Repos[0].Name, ShouldResemble, "signed-repo")
  3985  	})
  3986  }
  3987  
  3988  func TestGlobalSearchWithInvalidInput(t *testing.T) {
  3989  	Convey("Global search with invalid input", t, func() {
  3990  		dir := t.TempDir()
  3991  
  3992  		port := GetFreePort()
  3993  		baseURL := GetBaseURL(port)
  3994  		conf := config.New()
  3995  		conf.HTTP.Port = port
  3996  		conf.Storage.RootDirectory = dir
  3997  		defaultVal := true
  3998  		conf.Extensions = &extconf.ExtensionConfig{
  3999  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  4000  		}
  4001  
  4002  		ctlr := api.NewController(conf)
  4003  
  4004  		ctlrManager := NewControllerManager(ctlr)
  4005  		ctlrManager.StartAndWait(port)
  4006  		defer ctlrManager.StopServer()
  4007  
  4008  		longString := RandomString(1000)
  4009  
  4010  		query := fmt.Sprintf(`
  4011  		{
  4012  			GlobalSearch(query:"%s", requestedPage:{limit: 3, offset: 4, sortBy:RELEVANCE},
  4013  			filter:{Os:["linux", "Windows", ""], Arch:["","amd64"]}) {
  4014  				Repos {
  4015  					Name
  4016  				}
  4017  			}
  4018  		}`, longString)
  4019  
  4020  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4021  		So(resp, ShouldNotBeNil)
  4022  		So(err, ShouldBeNil)
  4023  		So(resp.StatusCode(), ShouldEqual, 200)
  4024  
  4025  		responseStruct := &zcommon.GlobalSearchResultResp{}
  4026  
  4027  		err = json.Unmarshal(resp.Body(), responseStruct)
  4028  		So(err, ShouldBeNil)
  4029  
  4030  		So(responseStruct.Errors, ShouldNotBeEmpty)
  4031  
  4032  		query = fmt.Sprintf(`
  4033  		{
  4034  			GlobalSearch(query:"repo", requestedPage:{limit: 3, offset: 4, sortBy:RELEVANCE},
  4035  			filter:{Os:["%s", "Windows", ""], Arch:["","amd64"]}) {
  4036  				Repos {
  4037  					Name
  4038  				}
  4039  			}
  4040  		}`, longString)
  4041  
  4042  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4043  		So(resp, ShouldNotBeNil)
  4044  		So(err, ShouldBeNil)
  4045  		So(resp.StatusCode(), ShouldEqual, 200)
  4046  
  4047  		responseStruct = &zcommon.GlobalSearchResultResp{}
  4048  
  4049  		err = json.Unmarshal(resp.Body(), responseStruct)
  4050  		So(err, ShouldBeNil)
  4051  
  4052  		So(responseStruct.Errors, ShouldNotBeEmpty)
  4053  
  4054  		query = fmt.Sprintf(`
  4055  		{
  4056  			GlobalSearch(query:"repo", requestedPage:{limit: 3, offset: 4, sortBy:RELEVANCE},
  4057  			filter:{Os:["", "Windows", ""], Arch:["","%s"]}) {
  4058  				Repos {
  4059  					Name
  4060  				}
  4061  			}
  4062  		}`, longString)
  4063  
  4064  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4065  		So(resp, ShouldNotBeNil)
  4066  		So(err, ShouldBeNil)
  4067  		So(resp.StatusCode(), ShouldEqual, 200)
  4068  
  4069  		responseStruct = &zcommon.GlobalSearchResultResp{}
  4070  
  4071  		err = json.Unmarshal(resp.Body(), responseStruct)
  4072  		So(err, ShouldBeNil)
  4073  
  4074  		So(responseStruct.Errors, ShouldNotBeEmpty)
  4075  	})
  4076  }
  4077  
  4078  func TestImageList(t *testing.T) {
  4079  	Convey("Test ImageList", t, func() {
  4080  		rootDir := t.TempDir()
  4081  
  4082  		port := GetFreePort()
  4083  		baseURL := GetBaseURL(port)
  4084  
  4085  		conf := config.New()
  4086  		conf.HTTP.Port = port
  4087  		conf.Storage.RootDirectory = rootDir
  4088  		defaultVal := true
  4089  		conf.Extensions = &extconf.ExtensionConfig{
  4090  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  4091  		}
  4092  
  4093  		conf.Extensions.Search.CVE = nil
  4094  
  4095  		ctlr := api.NewController(conf)
  4096  
  4097  		ctlrManager := NewControllerManager(ctlr)
  4098  		ctlrManager.StartAndWait(port)
  4099  		defer ctlrManager.StopServer()
  4100  
  4101  		createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
  4102  		createdTimeL2 := time.Date(2010, 2, 1, 12, 0, 0, 0, time.UTC)
  4103  		config := ispec.Image{
  4104  			Platform: ispec.Platform{
  4105  				Architecture: "amd64",
  4106  				OS:           "linux",
  4107  			},
  4108  			RootFS: ispec.RootFS{
  4109  				Type:    "layers",
  4110  				DiffIDs: []godigest.Digest{},
  4111  			},
  4112  			Author: "ZotUser",
  4113  			History: []ispec.History{
  4114  				{
  4115  					Created:    &createdTime,
  4116  					CreatedBy:  "go test data",
  4117  					Author:     "ZotUser",
  4118  					Comment:    "Test history comment",
  4119  					EmptyLayer: true,
  4120  				},
  4121  				{
  4122  					Created:    &createdTimeL2,
  4123  					CreatedBy:  "go test data 2",
  4124  					Author:     "ZotUser",
  4125  					Comment:    "Test history comment2",
  4126  					EmptyLayer: false,
  4127  				},
  4128  			},
  4129  		}
  4130  
  4131  		image := CreateImageWith().RandomLayers(1, 100).ImageConfig(config).Build()
  4132  
  4133  		err := UploadImage(image, baseURL, "zot-cve-test", "0.0.1")
  4134  		So(err, ShouldBeNil)
  4135  
  4136  		err = UploadImage(image, baseURL, "a/zot-cve-test", "0.0.1")
  4137  		So(err, ShouldBeNil)
  4138  
  4139  		err = UploadImage(image, baseURL, "zot-test", "0.0.1")
  4140  		So(err, ShouldBeNil)
  4141  
  4142  		err = UploadImage(image, baseURL, "a/zot-test", "0.0.1")
  4143  		So(err, ShouldBeNil)
  4144  
  4145  		imageStore := ctlr.StoreController.DefaultStore
  4146  
  4147  		repos, err := imageStore.GetRepositories()
  4148  		So(err, ShouldBeNil)
  4149  
  4150  		tags, err := imageStore.GetImageTags(repos[0])
  4151  		So(err, ShouldBeNil)
  4152  
  4153  		buf, _, _, err := imageStore.GetImageManifest(repos[0], tags[0])
  4154  		So(err, ShouldBeNil)
  4155  		var imageManifest ispec.Manifest
  4156  		err = json.Unmarshal(buf, &imageManifest)
  4157  		So(err, ShouldBeNil)
  4158  
  4159  		var imageConfigInfo ispec.Image
  4160  		imageConfigBuf, err := imageStore.GetBlobContent(repos[0], imageManifest.Config.Digest)
  4161  		So(err, ShouldBeNil)
  4162  		err = json.Unmarshal(imageConfigBuf, &imageConfigInfo)
  4163  		So(err, ShouldBeNil)
  4164  
  4165  		Convey("without pagination, valid response", func() {
  4166  			query := fmt.Sprintf(`{
  4167  				ImageList(repo:"%s"){
  4168  					Results {
  4169  						Manifests {
  4170  							History{
  4171  								HistoryDescription{
  4172  									Author
  4173  									Comment
  4174  									Created
  4175  									CreatedBy
  4176  									EmptyLayer
  4177  								},
  4178  								Layer{
  4179  									Digest
  4180  									Size
  4181  								}
  4182  							}
  4183  						}
  4184  					}
  4185  				}
  4186  			}`, repos[0])
  4187  
  4188  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4189  			So(err, ShouldBeNil)
  4190  			So(resp.StatusCode(), ShouldEqual, 200)
  4191  			So(resp, ShouldNotBeNil)
  4192  
  4193  			var responseStruct zcommon.ImageListResponse
  4194  			err = json.Unmarshal(resp.Body(), &responseStruct)
  4195  			So(err, ShouldBeNil)
  4196  
  4197  			So(len(responseStruct.Results), ShouldEqual, len(tags))
  4198  			So(len(responseStruct.Results[0].Manifests[0].History), ShouldEqual, len(imageConfigInfo.History))
  4199  		})
  4200  
  4201  		Convey("Pagination with valid params", func() {
  4202  			limit := 1
  4203  			query := fmt.Sprintf(`{
  4204  				ImageList(repo:"%s", requestedPage:{limit: %d, offset: 0, sortBy:RELEVANCE}){
  4205  					Results{
  4206  						Manifests {
  4207  							History{
  4208  								HistoryDescription{
  4209  									Author
  4210  									Comment
  4211  									Created
  4212  									CreatedBy
  4213  									EmptyLayer
  4214  								},
  4215  								Layer{
  4216  									Digest
  4217  									Size
  4218  								}
  4219  							}
  4220  						}
  4221  					}
  4222  				}
  4223  			}`, repos[0], limit)
  4224  
  4225  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4226  			So(err, ShouldBeNil)
  4227  			So(resp.StatusCode(), ShouldEqual, 200)
  4228  			So(resp, ShouldNotBeNil)
  4229  
  4230  			var responseStruct zcommon.ImageListResponse
  4231  			err = json.Unmarshal(resp.Body(), &responseStruct)
  4232  			So(err, ShouldBeNil)
  4233  
  4234  			So(len(responseStruct.Results), ShouldEqual, limit)
  4235  		})
  4236  	})
  4237  }
  4238  
  4239  func TestGlobalSearchPagination(t *testing.T) {
  4240  	Convey("Test global search pagination", t, func() {
  4241  		dir := t.TempDir()
  4242  
  4243  		port := GetFreePort()
  4244  		baseURL := GetBaseURL(port)
  4245  		conf := config.New()
  4246  		conf.HTTP.Port = port
  4247  		conf.Storage.RootDirectory = dir
  4248  		defaultVal := true
  4249  		conf.Extensions = &extconf.ExtensionConfig{
  4250  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  4251  		}
  4252  
  4253  		ctlr := api.NewController(conf)
  4254  
  4255  		ctlrManager := NewControllerManager(ctlr)
  4256  		ctlrManager.StartAndWait(port)
  4257  		defer ctlrManager.StopServer()
  4258  
  4259  		for i := 0; i < 3; i++ {
  4260  			image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
  4261  
  4262  			err := UploadImage(image, baseURL, fmt.Sprintf("repo%d", i), "0.0.1")
  4263  			So(err, ShouldBeNil)
  4264  		}
  4265  
  4266  		Convey("Limit is bigger than the repo count", func() {
  4267  			query := `
  4268  			{
  4269  				GlobalSearch(query:"repo", requestedPage:{limit: 9, offset: 0, sortBy:RELEVANCE}){
  4270  					Repos {
  4271  						Name
  4272  					}
  4273  				}
  4274  			}`
  4275  
  4276  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4277  			So(resp, ShouldNotBeNil)
  4278  			So(err, ShouldBeNil)
  4279  			So(resp.StatusCode(), ShouldEqual, 200)
  4280  
  4281  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4282  
  4283  			err = json.Unmarshal(resp.Body(), responseStruct)
  4284  			So(err, ShouldBeNil)
  4285  
  4286  			So(responseStruct.Images, ShouldBeEmpty)
  4287  			So(responseStruct.Repos, ShouldNotBeEmpty)
  4288  			So(responseStruct.Layers, ShouldBeEmpty)
  4289  
  4290  			So(len(responseStruct.Repos), ShouldEqual, 3)
  4291  		})
  4292  
  4293  		Convey("Limit is lower than the repo count", func() {
  4294  			query := `
  4295  			{
  4296  				GlobalSearch(query:"repo", requestedPage:{limit: 2, offset: 0, sortBy:RELEVANCE}){
  4297  					Repos {
  4298  						Name
  4299  					}
  4300  				}
  4301  			}`
  4302  
  4303  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4304  			So(resp, ShouldNotBeNil)
  4305  			So(err, ShouldBeNil)
  4306  			So(resp.StatusCode(), ShouldEqual, 200)
  4307  
  4308  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4309  
  4310  			err = json.Unmarshal(resp.Body(), responseStruct)
  4311  			So(err, ShouldBeNil)
  4312  
  4313  			So(responseStruct.Images, ShouldBeEmpty)
  4314  			So(responseStruct.Repos, ShouldNotBeEmpty)
  4315  			So(responseStruct.Layers, ShouldBeEmpty)
  4316  
  4317  			So(len(responseStruct.Repos), ShouldEqual, 2)
  4318  		})
  4319  
  4320  		Convey("PageInfo returned proper response", func() {
  4321  			query := `
  4322  			{
  4323  				GlobalSearch(query:"repo", requestedPage:{limit: 2, offset: 0, sortBy:RELEVANCE}){
  4324  					Repos {
  4325  						Name
  4326  					}
  4327  					Page{
  4328  						ItemCount
  4329  						TotalCount
  4330  					}
  4331  				}
  4332  			}`
  4333  
  4334  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4335  			So(resp, ShouldNotBeNil)
  4336  			So(err, ShouldBeNil)
  4337  			So(resp.StatusCode(), ShouldEqual, 200)
  4338  
  4339  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4340  
  4341  			err = json.Unmarshal(resp.Body(), responseStruct)
  4342  			So(err, ShouldBeNil)
  4343  
  4344  			So(responseStruct.Images, ShouldBeEmpty)
  4345  			So(responseStruct.Repos, ShouldNotBeEmpty)
  4346  			So(responseStruct.Layers, ShouldBeEmpty)
  4347  
  4348  			So(len(responseStruct.Repos), ShouldEqual, 2)
  4349  			So(responseStruct.Page.TotalCount, ShouldEqual, 3)
  4350  			So(responseStruct.Page.ItemCount, ShouldEqual, 2)
  4351  		})
  4352  
  4353  		Convey("PageInfo when limit is bigger than the repo count", func() {
  4354  			query := `
  4355  			{
  4356  				GlobalSearch(query:"repo", requestedPage:{limit: 9, offset: 0, sortBy:RELEVANCE}){
  4357  					Repos {
  4358  						Name
  4359  					}
  4360  					Page{
  4361  						ItemCount
  4362  						TotalCount
  4363  					}
  4364  				}
  4365  			}`
  4366  
  4367  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4368  			So(resp, ShouldNotBeNil)
  4369  			So(err, ShouldBeNil)
  4370  			So(resp.StatusCode(), ShouldEqual, 200)
  4371  
  4372  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4373  
  4374  			err = json.Unmarshal(resp.Body(), responseStruct)
  4375  			So(err, ShouldBeNil)
  4376  
  4377  			So(responseStruct.Images, ShouldBeEmpty)
  4378  			So(responseStruct.Repos, ShouldNotBeEmpty)
  4379  			So(responseStruct.Layers, ShouldBeEmpty)
  4380  
  4381  			So(len(responseStruct.Repos), ShouldEqual, 3)
  4382  			So(responseStruct.Page.TotalCount, ShouldEqual, 3)
  4383  			So(responseStruct.Page.ItemCount, ShouldEqual, 3)
  4384  		})
  4385  
  4386  		Convey("PageInfo when limit and offset have 0 value", func() {
  4387  			query := `
  4388  			{
  4389  				GlobalSearch(query:"repo", requestedPage:{limit: 0, offset: 0, sortBy:RELEVANCE}){
  4390  					Repos {
  4391  						Name
  4392  					}
  4393  					Page{
  4394  						ItemCount
  4395  						TotalCount
  4396  					}
  4397  				}
  4398  			}`
  4399  
  4400  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4401  			So(resp, ShouldNotBeNil)
  4402  			So(err, ShouldBeNil)
  4403  			So(resp.StatusCode(), ShouldEqual, 200)
  4404  
  4405  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4406  
  4407  			err = json.Unmarshal(resp.Body(), responseStruct)
  4408  			So(err, ShouldBeNil)
  4409  
  4410  			So(responseStruct.Images, ShouldBeEmpty)
  4411  			So(responseStruct.Repos, ShouldNotBeEmpty)
  4412  			So(responseStruct.Layers, ShouldBeEmpty)
  4413  
  4414  			So(len(responseStruct.Repos), ShouldEqual, 3)
  4415  			So(responseStruct.Page.TotalCount, ShouldEqual, 3)
  4416  			So(responseStruct.Page.ItemCount, ShouldEqual, 3)
  4417  		})
  4418  	})
  4419  }
  4420  
  4421  func TestMetaDBWhenSigningImages(t *testing.T) {
  4422  	Convey("SigningImages", t, func() {
  4423  		subpath := "/a"
  4424  
  4425  		dir := t.TempDir()
  4426  		subDir := t.TempDir()
  4427  
  4428  		subRootDir := path.Join(subDir, subpath)
  4429  
  4430  		port := GetFreePort()
  4431  		baseURL := GetBaseURL(port)
  4432  		conf := config.New()
  4433  		conf.HTTP.Port = port
  4434  		conf.Storage.RootDirectory = dir
  4435  		conf.Storage.SubPaths = make(map[string]config.StorageConfig)
  4436  		conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
  4437  		defaultVal := true
  4438  		conf.Extensions = &extconf.ExtensionConfig{
  4439  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  4440  		}
  4441  
  4442  		conf.Extensions.Search.CVE = nil
  4443  
  4444  		ctlr := api.NewController(conf)
  4445  
  4446  		ctlrManager := NewControllerManager(ctlr)
  4447  		ctlrManager.StartAndWait(port)
  4448  		defer ctlrManager.StopServer()
  4449  
  4450  		// push test images to repo 1 image 1
  4451  		image1 := CreateDefaultImage()
  4452  
  4453  		err := UploadImage(image1, baseURL, "repo1", "1.0.1")
  4454  		So(err, ShouldBeNil)
  4455  
  4456  		err = UploadImage(image1, baseURL, "repo1", "2.0.2")
  4457  		So(err, ShouldBeNil)
  4458  
  4459  		manifestBlob, err := json.Marshal(image1.Manifest)
  4460  		So(err, ShouldBeNil)
  4461  
  4462  		manifestDigest := godigest.FromBytes(manifestBlob)
  4463  
  4464  		multiArch := CreateRandomMultiarch()
  4465  
  4466  		err = UploadMultiarchImage(multiArch, baseURL, "repo1", "index")
  4467  		So(err, ShouldBeNil)
  4468  
  4469  		queryImage1 := `
  4470  		{
  4471  			GlobalSearch(query:"repo1:1.0"){
  4472  				Images {
  4473  					RepoName Tag LastUpdated Size IsSigned
  4474  					Manifests{
  4475  						LastUpdated Size
  4476  					}
  4477  				}
  4478  			}
  4479  		}`
  4480  
  4481  		queryImage2 := `
  4482  		{
  4483  			GlobalSearch(query:"repo1:2.0"){
  4484  				Images {
  4485  					RepoName Tag LastUpdated Size IsSigned
  4486  					Manifests { LastUpdated Size  Platform { Os Arch } }
  4487  				}
  4488  			}
  4489  		}`
  4490  
  4491  		queryIndex := `
  4492  		{
  4493  			GlobalSearch(query:"repo1:index"){
  4494  				Images {
  4495  					RepoName Tag LastUpdated Size IsSigned
  4496  					Manifests { LastUpdated Size  Platform { Os Arch } }
  4497  				}
  4498  			}
  4499  		}
  4500  		`
  4501  
  4502  		Convey("Sign with cosign", func() {
  4503  			err = signature.SignImageUsingCosign("repo1:1.0.1", port, false)
  4504  			So(err, ShouldBeNil)
  4505  
  4506  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage1))
  4507  			So(resp, ShouldNotBeNil)
  4508  			So(err, ShouldBeNil)
  4509  			So(resp.StatusCode(), ShouldEqual, 200)
  4510  
  4511  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4512  
  4513  			err = json.Unmarshal(resp.Body(), responseStruct)
  4514  			So(err, ShouldBeNil)
  4515  
  4516  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  4517  
  4518  			// check image 2 is signed also because it has the same manifest
  4519  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage2))
  4520  			So(resp, ShouldNotBeNil)
  4521  			So(err, ShouldBeNil)
  4522  			So(resp.StatusCode(), ShouldEqual, 200)
  4523  
  4524  			responseStruct = &zcommon.GlobalSearchResultResp{}
  4525  
  4526  			err = json.Unmarshal(resp.Body(), responseStruct)
  4527  			So(err, ShouldBeNil)
  4528  
  4529  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  4530  
  4531  			// delete the signature
  4532  			resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" +
  4533  				fmt.Sprintf("sha256-%s.sig", manifestDigest.Encoded()))
  4534  			So(err, ShouldBeNil)
  4535  			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
  4536  
  4537  			// check image 2 is not signed anymore
  4538  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage2))
  4539  			So(resp, ShouldNotBeNil)
  4540  			So(err, ShouldBeNil)
  4541  			So(resp.StatusCode(), ShouldEqual, 200)
  4542  
  4543  			responseStruct = &zcommon.GlobalSearchResultResp{}
  4544  
  4545  			err = json.Unmarshal(resp.Body(), responseStruct)
  4546  			So(err, ShouldBeNil)
  4547  
  4548  			So(responseStruct.Images[0].IsSigned, ShouldBeFalse)
  4549  		})
  4550  
  4551  		Convey("Cover errors when signing with cosign", func() {
  4552  			Convey("imageIsSignature fails", func() {
  4553  				// make image store ignore the wrong format of the input
  4554  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  4555  					PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
  4556  						godigest.Digest, error,
  4557  					) {
  4558  						return "", "", nil
  4559  					},
  4560  					DeleteImageManifestFn: func(repo, reference string, dc bool) error {
  4561  						return ErrTestError
  4562  					},
  4563  				}
  4564  
  4565  				// push bad manifest blob
  4566  				resp, err := resty.R().
  4567  					SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
  4568  					SetBody([]byte("unmashable manifest blob")).
  4569  					Put(baseURL + "/v2/" + "repo" + "/manifests/" + "tag")
  4570  				So(err, ShouldBeNil)
  4571  				So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
  4572  			})
  4573  
  4574  			Convey("image is a signature, AddManifestSignature fails", func() {
  4575  				ctlr.MetaDB = mocks.MetaDBMock{
  4576  					AddManifestSignatureFn: func(repo string, signedManifestDigest godigest.Digest,
  4577  						sm mTypes.SignatureMetadata,
  4578  					) error {
  4579  						return ErrTestError
  4580  					},
  4581  				}
  4582  
  4583  				err := signature.SignImageUsingCosign("repo1:1.0.1", port, false)
  4584  				So(err, ShouldNotBeNil)
  4585  			})
  4586  		})
  4587  
  4588  		Convey("Sign with notation", func() {
  4589  			err = signature.SignImageUsingNotary("repo1:1.0.1", port, true)
  4590  			So(err, ShouldBeNil)
  4591  
  4592  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage1))
  4593  			So(resp, ShouldNotBeNil)
  4594  			So(err, ShouldBeNil)
  4595  			So(resp.StatusCode(), ShouldEqual, 200)
  4596  
  4597  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4598  
  4599  			err = json.Unmarshal(resp.Body(), responseStruct)
  4600  			So(err, ShouldBeNil)
  4601  
  4602  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  4603  		})
  4604  
  4605  		Convey("Sign with notation index", func() {
  4606  			err = signature.SignImageUsingNotary("repo1:index", port, false)
  4607  			So(err, ShouldBeNil)
  4608  
  4609  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryIndex))
  4610  			So(resp, ShouldNotBeNil)
  4611  			So(err, ShouldBeNil)
  4612  			So(resp.StatusCode(), ShouldEqual, 200)
  4613  
  4614  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4615  
  4616  			err = json.Unmarshal(resp.Body(), responseStruct)
  4617  			So(err, ShouldBeNil)
  4618  
  4619  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  4620  		})
  4621  
  4622  		Convey("Sign with cosign index", func() {
  4623  			err = signature.SignImageUsingCosign("repo1:index", port, false)
  4624  			So(err, ShouldBeNil)
  4625  
  4626  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryIndex))
  4627  			So(resp, ShouldNotBeNil)
  4628  			So(err, ShouldBeNil)
  4629  
  4630  			So(resp.StatusCode(), ShouldEqual, 200)
  4631  
  4632  			responseStruct := &zcommon.GlobalSearchResultResp{}
  4633  
  4634  			err = json.Unmarshal(resp.Body(), responseStruct)
  4635  			So(err, ShouldBeNil)
  4636  
  4637  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  4638  		})
  4639  	})
  4640  }
  4641  
  4642  func TestMetaDBWhenPushingImages(t *testing.T) {
  4643  	Convey("Cover errors when pushing", t, func() {
  4644  		dir := t.TempDir()
  4645  
  4646  		port := GetFreePort()
  4647  		baseURL := GetBaseURL(port)
  4648  		conf := config.New()
  4649  		conf.HTTP.Port = port
  4650  		conf.Storage.RootDirectory = dir
  4651  		defaultVal := true
  4652  		conf.Extensions = &extconf.ExtensionConfig{
  4653  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  4654  		}
  4655  
  4656  		ctlr := api.NewController(conf)
  4657  
  4658  		ctlrManager := NewControllerManager(ctlr)
  4659  		ctlrManager.StartAndWait(port)
  4660  		defer ctlrManager.StopServer()
  4661  
  4662  		Convey("SetManifestMeta succeeds but SetRepoReference fails", func() {
  4663  			ctlr.MetaDB = mocks.MetaDBMock{
  4664  				SetRepoReferenceFn: func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error {
  4665  					return ErrTestError
  4666  				},
  4667  			}
  4668  
  4669  			image := CreateRandomImage()
  4670  
  4671  			ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  4672  				NewBlobUploadFn: ctlr.StoreController.DefaultStore.NewBlobUpload,
  4673  				PutBlobChunkFn:  ctlr.StoreController.DefaultStore.PutBlobChunk,
  4674  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
  4675  					return image.ConfigDescriptor.Data, nil
  4676  				},
  4677  			}
  4678  
  4679  			err := UploadImage(image, baseURL, "repo1", "1.0.1")
  4680  			So(err, ShouldNotBeNil)
  4681  		})
  4682  	})
  4683  }
  4684  
  4685  func TestMetaDBIndexOperations(t *testing.T) {
  4686  	Convey("Idex Operations BoltDB", t, func() {
  4687  		dir := t.TempDir()
  4688  
  4689  		port := GetFreePort()
  4690  		baseURL := GetBaseURL(port)
  4691  		conf := config.New()
  4692  		conf.HTTP.Port = port
  4693  		conf.Storage.RootDirectory = dir
  4694  		conf.Storage.GC = false
  4695  		defaultVal := true
  4696  		conf.Extensions = &extconf.ExtensionConfig{
  4697  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  4698  		}
  4699  
  4700  		ctlr := api.NewController(conf)
  4701  
  4702  		ctlrManager := NewControllerManager(ctlr)
  4703  		ctlrManager.StartAndWait(port)
  4704  		defer ctlrManager.StopServer()
  4705  
  4706  		RunMetaDBIndexTests(baseURL, port)
  4707  	})
  4708  }
  4709  
  4710  func RunMetaDBIndexTests(baseURL, port string) {
  4711  	Convey("Push test index", func() {
  4712  		const repo = "repo"
  4713  
  4714  		multiarchImage := CreateRandomMultiarch()
  4715  
  4716  		err := UploadMultiarchImage(multiarchImage, baseURL, repo, "tag1")
  4717  		So(err, ShouldBeNil)
  4718  
  4719  		query := `
  4720  			{
  4721  				GlobalSearch(query:"repo:tag1"){
  4722  					Images {
  4723  						RepoName Tag DownloadCount
  4724  						IsSigned
  4725  						Manifests {
  4726  							Digest
  4727  							ConfigDigest
  4728  							Platform {Os Arch}
  4729  							Layers {Size Digest}
  4730  							LastUpdated
  4731  							Size
  4732  						}
  4733  					}
  4734  				}
  4735  			}`
  4736  
  4737  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4738  		So(resp, ShouldNotBeNil)
  4739  		So(err, ShouldBeNil)
  4740  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  4741  
  4742  		responseStruct := &zcommon.GlobalSearchResultResp{}
  4743  
  4744  		err = json.Unmarshal(resp.Body(), responseStruct)
  4745  		So(err, ShouldBeNil)
  4746  
  4747  		responseImages := responseStruct.GlobalSearchResult.GlobalSearch.Images
  4748  		So(responseImages, ShouldNotBeEmpty)
  4749  		responseImage := responseImages[0]
  4750  		So(len(responseImage.Manifests), ShouldEqual, 3)
  4751  
  4752  		err = signature.SignImageUsingCosign(fmt.Sprintf("repo@%s", multiarchImage.DigestStr()), port, false)
  4753  		So(err, ShouldBeNil)
  4754  
  4755  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4756  		So(resp, ShouldNotBeNil)
  4757  		So(err, ShouldBeNil)
  4758  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  4759  
  4760  		responseStruct = &zcommon.GlobalSearchResultResp{}
  4761  
  4762  		err = json.Unmarshal(resp.Body(), responseStruct)
  4763  		So(err, ShouldBeNil)
  4764  
  4765  		responseImages = responseStruct.GlobalSearchResult.GlobalSearch.Images
  4766  		So(responseImages, ShouldNotBeEmpty)
  4767  		responseImage = responseImages[0]
  4768  
  4769  		So(responseImage.IsSigned, ShouldBeTrue)
  4770  
  4771  		// remove signature
  4772  		cosignTag := "sha256-" + multiarchImage.Digest().Encoded() + ".sig"
  4773  		_, err = resty.R().Delete(baseURL + "/v2/" + "repo" + "/manifests/" + cosignTag)
  4774  		So(err, ShouldBeNil)
  4775  
  4776  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4777  		So(resp, ShouldNotBeNil)
  4778  		So(err, ShouldBeNil)
  4779  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  4780  
  4781  		responseStruct = &zcommon.GlobalSearchResultResp{}
  4782  
  4783  		err = json.Unmarshal(resp.Body(), responseStruct)
  4784  		So(err, ShouldBeNil)
  4785  
  4786  		responseImages = responseStruct.GlobalSearchResult.GlobalSearch.Images
  4787  		So(responseImages, ShouldNotBeEmpty)
  4788  		responseImage = responseImages[0]
  4789  
  4790  		So(responseImage.IsSigned, ShouldBeFalse)
  4791  	})
  4792  	Convey("Index base images", func() {
  4793  		// ---------------- BASE IMAGE -------------------
  4794  		imageAMD64 := CreateImageWith().LayerBlobs([][]byte{
  4795  			{10, 20, 30},
  4796  			{11, 21, 31},
  4797  		}).ImageConfig(ispec.Image{
  4798  			Platform: ispec.Platform{
  4799  				OS:           "linux",
  4800  				Architecture: "amd64",
  4801  			},
  4802  		}).Build()
  4803  
  4804  		imageSomeArch := CreateImageWith().LayerBlobs([][]byte{
  4805  			{18, 28, 38},
  4806  			{12, 22, 32},
  4807  		}).ImageConfig(ispec.Image{
  4808  			Platform: ispec.Platform{
  4809  				OS:           "linux",
  4810  				Architecture: "someArch",
  4811  			},
  4812  		}).Build()
  4813  
  4814  		multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build()
  4815  
  4816  		err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "latest")
  4817  		So(err, ShouldBeNil)
  4818  		// ---------------- BASE IMAGE -------------------
  4819  
  4820  		//  ---------------- SAME LAYERS -------------------
  4821  		image1 := CreateImageWith().LayerBlobs([][]byte{{0, 0, 2}}).ImageConfig(imageSomeArch.Config).Build()
  4822  
  4823  		image2 := CreateImageWith().LayerBlobs(imageAMD64.Layers).ImageConfig(imageAMD64.Config).Build()
  4824  
  4825  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  4826  
  4827  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-same-layers", "index-one-arch-same-layers")
  4828  		So(err, ShouldBeNil)
  4829  		//  ---------------- SAME LAYERS -------------------
  4830  
  4831  		//  ---------------- LESS LAYERS -------------------
  4832  		image1 = CreateImageWith().LayerBlobs([][]byte{
  4833  			{3, 2, 2},
  4834  			{5, 2, 5},
  4835  		}).ImageConfig(imageSomeArch.Config).Build()
  4836  
  4837  		image2 = CreateImageWith().LayerBlobs([][]byte{
  4838  			imageAMD64.Layers[0],
  4839  		}).ImageConfig(imageAMD64.Config).Build()
  4840  
  4841  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  4842  
  4843  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers", "index-one-arch-less-layers")
  4844  		So(err, ShouldBeNil)
  4845  		//  ---------------- LESS LAYERS -------------------
  4846  
  4847  		//  ---------------- LESS LAYERS FALSE -------------------
  4848  		image1 = CreateImageWith().LayerBlobs([][]byte{
  4849  			{3, 2, 2},
  4850  			{5, 2, 5},
  4851  		}).ImageConfig(imageSomeArch.Config).Build()
  4852  
  4853  		auxLayer := imageAMD64.Layers[0]
  4854  		auxLayer[0] = 20
  4855  
  4856  		image2 = CreateImageWith().LayerBlobs([][]byte{
  4857  			auxLayer,
  4858  		}).ImageConfig(imageAMD64.Config).Build()
  4859  
  4860  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  4861  
  4862  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers-false",
  4863  			"index-one-arch-less-layers-false")
  4864  		So(err, ShouldBeNil)
  4865  		//  ---------------- LESS LAYERS FALSE -------------------
  4866  
  4867  		//  ---------------- MORE LAYERS -------------------
  4868  		image1 = CreateImageWith().LayerBlobs([][]byte{
  4869  			{0, 0, 2},
  4870  			{3, 0, 2},
  4871  		}).ImageConfig(imageSomeArch.Config).Build()
  4872  
  4873  		image2 = CreateImageWith().LayerBlobs(
  4874  			append(imageAMD64.Layers, []byte{1, 3, 55}),
  4875  		).ImageConfig(imageAMD64.Config).Build()
  4876  
  4877  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  4878  
  4879  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-more-layers", "index-one-arch-more-layers")
  4880  		So(err, ShouldBeNil)
  4881  		//  ---------------- MORE LAYERS -------------------
  4882  
  4883  		query := `
  4884  				{
  4885  					BaseImageList(image:"test-repo:latest"){
  4886  						Results{
  4887  							RepoName
  4888  							Tag
  4889  							Manifests {
  4890  								Digest
  4891  								ConfigDigest
  4892  								LastUpdated
  4893  								Size
  4894  							}
  4895  							Size
  4896  						}
  4897  					}
  4898  				}`
  4899  
  4900  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  4901  		So(resp, ShouldNotBeNil)
  4902  		So(err, ShouldBeNil)
  4903  
  4904  		So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers"), ShouldBeTrue)
  4905  		So(strings.Contains(string(resp.Body()), "index-one-arch-same-layers"), ShouldBeFalse)
  4906  		So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers-false"), ShouldBeFalse)
  4907  		So(strings.Contains(string(resp.Body()), "index-one-arch-more-layers"), ShouldBeFalse)
  4908  		So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse)
  4909  	})
  4910  
  4911  	Convey("Index base images for digest", func() {
  4912  		// ---------------- BASE IMAGE -------------------
  4913  		imageAMD64 := CreateImageWith().LayerBlobs([][]byte{
  4914  			{10, 20, 30},
  4915  			{11, 21, 31},
  4916  		}).ImageConfig(ispec.Image{
  4917  			Platform: ispec.Platform{
  4918  				OS:           "linux",
  4919  				Architecture: "amd64",
  4920  			},
  4921  		}).Build()
  4922  
  4923  		baseLinuxAMD64Digest := imageAMD64.ManifestDescriptor.Digest
  4924  
  4925  		imageSomeArch := CreateImageWith().LayerBlobs([][]byte{
  4926  			{18, 28, 38},
  4927  			{12, 22, 32},
  4928  		}).ImageConfig(ispec.Image{
  4929  			Platform: ispec.Platform{
  4930  				OS:           "linux",
  4931  				Architecture: "someArch",
  4932  			},
  4933  		}).Build()
  4934  
  4935  		baseLinuxSomeArchDigest := imageSomeArch.ManifestDescriptor.Digest
  4936  
  4937  		multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build()
  4938  
  4939  		err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "index")
  4940  		So(err, ShouldBeNil)
  4941  		// ---------------- BASE IMAGE FOR LINUX AMD64 -------------------
  4942  
  4943  		image := CreateImageWith().LayerBlobs([][]byte{
  4944  			imageAMD64.Layers[0],
  4945  		}).ImageConfig(imageAMD64.Config).Build()
  4946  
  4947  		err = UploadImage(image, baseURL, "test-repo", "less-layers-linux-amd64")
  4948  		So(err, ShouldBeNil)
  4949  
  4950  		// ---------------- BASE IMAGE FOR LINUX SOMEARCH -------------------
  4951  
  4952  		image = CreateImageWith().LayerBlobs([][]byte{
  4953  			imageSomeArch.Layers[0],
  4954  		}).ImageConfig(imageAMD64.Config).Build()
  4955  
  4956  		err = UploadImage(image, baseURL, "test-repo", "less-layers-linux-somearch")
  4957  		So(err, ShouldBeNil)
  4958  
  4959  		// ------- TEST
  4960  
  4961  		query := `
  4962  		{
  4963  			BaseImageList(image:"test-repo:index", digest:"%s"){
  4964  				Results{
  4965  					RepoName
  4966  					Tag
  4967  					Manifests {
  4968  						Digest
  4969  						ConfigDigest
  4970  						LastUpdated
  4971  						Size
  4972  					}
  4973  					Size
  4974  				}
  4975  			}
  4976  		}`
  4977  
  4978  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" +
  4979  			url.QueryEscape(
  4980  				fmt.Sprintf(query, baseLinuxAMD64Digest.String()),
  4981  			),
  4982  		)
  4983  		So(resp, ShouldNotBeNil)
  4984  		So(err, ShouldBeNil)
  4985  
  4986  		So(strings.Contains(string(resp.Body()), "less-layers-linux-amd64"), ShouldEqual, true)
  4987  		So(strings.Contains(string(resp.Body()), "less-layers-linux-somearch"), ShouldEqual, false)
  4988  
  4989  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" +
  4990  			url.QueryEscape(
  4991  				fmt.Sprintf(query, baseLinuxSomeArchDigest.String()),
  4992  			),
  4993  		)
  4994  		So(resp, ShouldNotBeNil)
  4995  		So(err, ShouldBeNil)
  4996  
  4997  		So(strings.Contains(string(resp.Body()), "less-layers-linux-amd64"), ShouldEqual, false)
  4998  		So(strings.Contains(string(resp.Body()), "less-layers-linux-somearch"), ShouldEqual, true)
  4999  	})
  5000  
  5001  	Convey("Index derived images", func() {
  5002  		// ---------------- BASE IMAGE -------------------
  5003  		imageAMD64 := CreateImageWith().LayerBlobs([][]byte{
  5004  			{10, 20, 30},
  5005  			{11, 21, 31},
  5006  		}).ImageConfig(ispec.Image{
  5007  			Platform: ispec.Platform{
  5008  				OS:           "linux",
  5009  				Architecture: "amd64",
  5010  			},
  5011  		}).Build()
  5012  
  5013  		imageSomeArch := CreateImageWith().LayerBlobs([][]byte{
  5014  			{18, 28, 38},
  5015  			{12, 22, 32},
  5016  		}).ImageConfig(ispec.Image{
  5017  			Platform: ispec.Platform{
  5018  				OS:           "linux",
  5019  				Architecture: "someArch",
  5020  			},
  5021  		}).Build()
  5022  
  5023  		multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build()
  5024  
  5025  		err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "latest")
  5026  		So(err, ShouldBeNil)
  5027  		// ---------------- BASE IMAGE -------------------
  5028  
  5029  		//  ---------------- SAME LAYERS -------------------
  5030  		image1 := CreateImageWith().LayerBlobs([][]byte{
  5031  			{0, 0, 2},
  5032  		}).ImageConfig(imageSomeArch.Config).Build()
  5033  
  5034  		image2 := CreateImageWith().LayerBlobs(
  5035  			imageAMD64.Layers,
  5036  		).ImageConfig(imageAMD64.Config).Build()
  5037  
  5038  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  5039  
  5040  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-same-layers", "index-one-arch-same-layers")
  5041  		So(err, ShouldBeNil)
  5042  		//  ---------------- SAME LAYERS -------------------
  5043  
  5044  		//  ---------------- LESS LAYERS -------------------
  5045  		image1 = CreateImageWith().LayerBlobs([][]byte{
  5046  			{3, 2, 2},
  5047  			{5, 2, 5},
  5048  		}).ImageConfig(imageSomeArch.Config).Build()
  5049  
  5050  		image2 = CreateImageWith().LayerBlobs([][]byte{
  5051  			imageAMD64.Layers[0],
  5052  		}).ImageConfig(imageAMD64.Config).Build()
  5053  
  5054  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  5055  
  5056  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers", "index-one-arch-less-layers")
  5057  		So(err, ShouldBeNil)
  5058  		//  ---------------- LESS LAYERS -------------------
  5059  
  5060  		//  ---------------- LESS LAYERS FALSE -------------------
  5061  		image1 = CreateImageWith().LayerBlobs([][]byte{
  5062  			{3, 2, 2},
  5063  			{5, 2, 5},
  5064  		}).ImageConfig(imageSomeArch.Config).Build()
  5065  
  5066  		image2 = CreateImageWith().LayerBlobs([][]byte{
  5067  			{99, 100, 102},
  5068  		}).ImageConfig(imageAMD64.Config).Build()
  5069  
  5070  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  5071  
  5072  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers-false",
  5073  			"index-one-arch-less-layers-false")
  5074  		So(err, ShouldBeNil)
  5075  		//  ---------------- LESS LAYERS FALSE -------------------
  5076  
  5077  		//  ---------------- MORE LAYERS -------------------
  5078  		image1 = CreateImageWith().LayerBlobs([][]byte{
  5079  			{0, 0, 2},
  5080  			{3, 0, 2},
  5081  		}).ImageConfig(imageSomeArch.Config).Build()
  5082  
  5083  		image2 = CreateImageWith().LayerBlobs([][]byte{
  5084  			imageAMD64.Layers[0],
  5085  			imageAMD64.Layers[1],
  5086  			{1, 3, 55},
  5087  		}).ImageConfig(imageAMD64.Config).Build()
  5088  
  5089  		multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build()
  5090  
  5091  		err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-more-layers", "index-one-arch-more-layers")
  5092  		So(err, ShouldBeNil)
  5093  		//  ---------------- MORE LAYERS -------------------
  5094  
  5095  		query := `
  5096  				{
  5097  					DerivedImageList(image:"test-repo:latest"){
  5098  						Results{
  5099  							RepoName
  5100  							Tag
  5101  							Manifests {
  5102  								Digest
  5103  								ConfigDigest
  5104  								LastUpdated
  5105  								Size
  5106  							}
  5107  							Size
  5108  						}
  5109  					}
  5110  				}`
  5111  
  5112  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5113  		So(resp, ShouldNotBeNil)
  5114  		So(err, ShouldBeNil)
  5115  
  5116  		So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers"), ShouldBeFalse)
  5117  		So(strings.Contains(string(resp.Body()), "index-one-arch-same-layers"), ShouldBeFalse)
  5118  		So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers-false"), ShouldBeFalse)
  5119  		So(strings.Contains(string(resp.Body()), "index-one-arch-more-layers"), ShouldBeTrue)
  5120  		So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse)
  5121  	})
  5122  
  5123  	Convey("Index derived images for digest", func() {
  5124  		// ---------------- BASE IMAGE -------------------
  5125  		imageAMD64 := CreateImageWith().LayerBlobs([][]byte{
  5126  			{10, 20, 30},
  5127  			{11, 21, 31},
  5128  		}).ImageConfig(ispec.Image{
  5129  			Platform: ispec.Platform{
  5130  				OS:           "linux",
  5131  				Architecture: "amd64",
  5132  			},
  5133  		}).Build()
  5134  
  5135  		baseLinuxAMD64Digest := imageAMD64.ManifestDescriptor.Digest
  5136  
  5137  		imageSomeArch := CreateImageWith().LayerBlobs([][]byte{
  5138  			{18, 28, 38},
  5139  			{12, 22, 32},
  5140  		}).ImageConfig(ispec.Image{
  5141  			Platform: ispec.Platform{
  5142  				OS:           "linux",
  5143  				Architecture: "someArch",
  5144  			},
  5145  		}).Build()
  5146  
  5147  		baseLinuxSomeArchDigest := imageSomeArch.ManifestDescriptor.Digest
  5148  
  5149  		multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build()
  5150  
  5151  		err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "index")
  5152  		So(err, ShouldBeNil)
  5153  		// ---------------- BASE IMAGE FOR LINUX AMD64 -------------------
  5154  		image := CreateImageWith().LayerBlobs([][]byte{
  5155  			imageAMD64.Layers[0],
  5156  			imageAMD64.Layers[1],
  5157  			{0, 0, 0, 0},
  5158  			{1, 1, 1, 1},
  5159  		}).ImageConfig(imageAMD64.Config).Build()
  5160  
  5161  		err = UploadImage(image, baseURL, "test-repo", "more-layers-linux-amd64")
  5162  		So(err, ShouldBeNil)
  5163  
  5164  		// ---------------- BASE IMAGE FOR LINUX SOMEARCH -------------------
  5165  		image = CreateImageWith().LayerBlobs([][]byte{
  5166  			imageSomeArch.Layers[0],
  5167  			imageSomeArch.Layers[1],
  5168  			{3, 3, 3, 3},
  5169  			{2, 2, 2, 2},
  5170  		}).ImageConfig(imageAMD64.Config).Build()
  5171  
  5172  		err = UploadImage(image, baseURL, "test-repo", "more-layers-linux-somearch")
  5173  		So(err, ShouldBeNil)
  5174  
  5175  		// ------- TEST
  5176  
  5177  		query := `
  5178  		{
  5179  			DerivedImageList(image:"test-repo:index", digest:"%s"){
  5180  				Results{
  5181  					RepoName
  5182  					Tag
  5183  					Manifests {
  5184  						Digest
  5185  						ConfigDigest
  5186  						LastUpdated
  5187  						Size
  5188  					}
  5189  					Size
  5190  				}
  5191  			}
  5192  		}`
  5193  
  5194  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" +
  5195  			url.QueryEscape(
  5196  				fmt.Sprintf(query, baseLinuxAMD64Digest.String()),
  5197  			),
  5198  		)
  5199  		So(resp, ShouldNotBeNil)
  5200  		So(err, ShouldBeNil)
  5201  
  5202  		So(strings.Contains(string(resp.Body()), "more-layers-linux-amd64"), ShouldEqual, true)
  5203  		So(strings.Contains(string(resp.Body()), "more-layers-linux-somearch"), ShouldEqual, false)
  5204  
  5205  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" +
  5206  			url.QueryEscape(
  5207  				fmt.Sprintf(query, baseLinuxSomeArchDigest.String()),
  5208  			),
  5209  		)
  5210  		So(resp, ShouldNotBeNil)
  5211  		So(err, ShouldBeNil)
  5212  
  5213  		So(strings.Contains(string(resp.Body()), "more-layers-linux-amd64"), ShouldEqual, false)
  5214  		So(strings.Contains(string(resp.Body()), "more-layers-linux-somearch"), ShouldEqual, true)
  5215  	})
  5216  }
  5217  
  5218  func TestMetaDBWhenReadingImages(t *testing.T) {
  5219  	Convey("Push test image", t, func() {
  5220  		dir := t.TempDir()
  5221  
  5222  		port := GetFreePort()
  5223  		baseURL := GetBaseURL(port)
  5224  		conf := config.New()
  5225  		conf.HTTP.Port = port
  5226  		conf.Storage.RootDirectory = dir
  5227  		defaultVal := true
  5228  		conf.Extensions = &extconf.ExtensionConfig{
  5229  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  5230  		}
  5231  
  5232  		ctlr := api.NewController(conf)
  5233  
  5234  		ctlrManager := NewControllerManager(ctlr)
  5235  		ctlrManager.StartAndWait(port)
  5236  		defer ctlrManager.StopServer()
  5237  
  5238  		image := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Build()
  5239  
  5240  		err := UploadImage(image, baseURL, "repo1", "1.0.1")
  5241  		So(err, ShouldBeNil)
  5242  
  5243  		Convey("Download 3 times", func() {
  5244  			resp, err := resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1")
  5245  			So(err, ShouldBeNil)
  5246  			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5247  
  5248  			resp, err = resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1")
  5249  			So(err, ShouldBeNil)
  5250  			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5251  
  5252  			resp, err = resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1")
  5253  			So(err, ShouldBeNil)
  5254  			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5255  
  5256  			query := `
  5257  			{
  5258  				GlobalSearch(query:"repo1:1.0"){
  5259  					Images {
  5260  						RepoName Tag DownloadCount
  5261  					}
  5262  				}
  5263  			}`
  5264  
  5265  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5266  			So(resp, ShouldNotBeNil)
  5267  			So(err, ShouldBeNil)
  5268  			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5269  
  5270  			responseStruct := &zcommon.GlobalSearchResultResp{}
  5271  
  5272  			err = json.Unmarshal(resp.Body(), responseStruct)
  5273  			So(err, ShouldBeNil)
  5274  			So(responseStruct.Images, ShouldNotBeEmpty)
  5275  			So(responseStruct.Images[0].DownloadCount, ShouldEqual, 3)
  5276  		})
  5277  
  5278  		Convey("Error when incrementing", func() {
  5279  			ctlr.MetaDB = mocks.MetaDBMock{
  5280  				UpdateStatsOnDownloadFn: func(repo string, tag string) error {
  5281  					return ErrTestError
  5282  				},
  5283  			}
  5284  
  5285  			resp, err := resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1")
  5286  			So(err, ShouldBeNil)
  5287  			So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
  5288  		})
  5289  	})
  5290  }
  5291  
  5292  func TestMetaDBWhenDeletingImages(t *testing.T) {
  5293  	Convey("Setting up zot repo with test images", t, func() {
  5294  		dir := t.TempDir()
  5295  		port := GetFreePort()
  5296  		baseURL := GetBaseURL(port)
  5297  
  5298  		conf := config.New()
  5299  		conf.HTTP.Port = port
  5300  		conf.Storage.RootDirectory = dir
  5301  		conf.Storage.GC = false
  5302  
  5303  		defaultVal := true
  5304  		conf.Extensions = &extconf.ExtensionConfig{
  5305  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  5306  		}
  5307  
  5308  		conf.Extensions.Search.CVE = nil
  5309  
  5310  		ctlr := api.NewController(conf)
  5311  
  5312  		ctlrManager := NewControllerManager(ctlr)
  5313  		ctlrManager.StartAndWait(port)
  5314  		defer ctlrManager.StopServer()
  5315  
  5316  		// push test images to repo 1 image 1
  5317  		image1 := CreateRandomImage()
  5318  
  5319  		err := UploadImage(image1, baseURL, "repo1", "1.0.1")
  5320  		So(err, ShouldBeNil)
  5321  
  5322  		// push test images to repo 1 image 2
  5323  		createdTime2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
  5324  		image2 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  5325  			Created: &createdTime2,
  5326  			History: []ispec.History{
  5327  				{
  5328  					Created: &createdTime2,
  5329  				},
  5330  			},
  5331  		}).Build()
  5332  
  5333  		err = UploadImage(image2, baseURL, "repo1", "1.0.2")
  5334  		So(err, ShouldBeNil)
  5335  
  5336  		query := `
  5337  		{
  5338  			GlobalSearch(query:"repo1:1.0"){
  5339  				Images {
  5340  					RepoName Tag LastUpdated Size IsSigned
  5341  					Manifests{
  5342  						Platform { Os Arch }
  5343  						LastUpdated Size
  5344  					}
  5345  				}
  5346  			}
  5347  		}`
  5348  
  5349  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5350  		So(resp, ShouldNotBeNil)
  5351  		So(err, ShouldBeNil)
  5352  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5353  
  5354  		responseStruct := &zcommon.GlobalSearchResultResp{}
  5355  
  5356  		err = json.Unmarshal(resp.Body(), responseStruct)
  5357  		So(err, ShouldBeNil)
  5358  
  5359  		So(len(responseStruct.Images), ShouldEqual, 2)
  5360  
  5361  		Convey("Delete a normal tag", func() {
  5362  			resp, err := resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1")
  5363  			So(resp, ShouldNotBeNil)
  5364  			So(err, ShouldBeNil)
  5365  			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
  5366  
  5367  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5368  			So(resp, ShouldNotBeNil)
  5369  			So(err, ShouldBeNil)
  5370  			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5371  
  5372  			responseStruct := &zcommon.GlobalSearchResultResp{}
  5373  
  5374  			err = json.Unmarshal(resp.Body(), responseStruct)
  5375  			So(err, ShouldBeNil)
  5376  
  5377  			So(len(responseStruct.Images), ShouldEqual, 1)
  5378  			So(responseStruct.Images[0].Tag, ShouldEqual, "1.0.2")
  5379  		})
  5380  
  5381  		Convey("Delete a cosign signature", func() {
  5382  			repo := "repo1"
  5383  			err := signature.SignImageUsingCosign("repo1:1.0.1", port, false)
  5384  			So(err, ShouldBeNil)
  5385  
  5386  			query := `
  5387  			{
  5388  				GlobalSearch(query:"repo1:1.0.1"){
  5389  					Images {
  5390  						RepoName Tag LastUpdated Size IsSigned
  5391  						Manifests{
  5392  							Platform { Os Arch }
  5393  							LastUpdated Size
  5394  						}
  5395  					}
  5396  				}
  5397  			}`
  5398  
  5399  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5400  			So(resp, ShouldNotBeNil)
  5401  			So(err, ShouldBeNil)
  5402  			So(resp.StatusCode(), ShouldEqual, 200)
  5403  
  5404  			responseStruct := &zcommon.GlobalSearchResultResp{}
  5405  
  5406  			err = json.Unmarshal(resp.Body(), responseStruct)
  5407  			So(err, ShouldBeNil)
  5408  
  5409  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  5410  
  5411  			// get signatur digest
  5412  			log := log.NewLogger("debug", "")
  5413  			metrics := monitoring.NewMetricsServer(false, log)
  5414  			storage := local.NewImageStore(dir, false, false, log, metrics, nil, nil)
  5415  
  5416  			indexBlob, err := storage.GetIndexContent(repo)
  5417  			So(err, ShouldBeNil)
  5418  
  5419  			var indexContent ispec.Index
  5420  
  5421  			err = json.Unmarshal(indexBlob, &indexContent)
  5422  			So(err, ShouldBeNil)
  5423  
  5424  			signatureTag := ""
  5425  
  5426  			for _, manifest := range indexContent.Manifests {
  5427  				tag := manifest.Annotations[ispec.AnnotationRefName]
  5428  
  5429  				if zcommon.IsCosignTag(tag) {
  5430  					signatureTag = tag
  5431  				}
  5432  			}
  5433  
  5434  			// delete the signature using the digest
  5435  			resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + signatureTag)
  5436  			So(resp, ShouldNotBeNil)
  5437  			So(err, ShouldBeNil)
  5438  			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
  5439  
  5440  			// verify isSigned again and it should be false
  5441  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5442  			So(resp, ShouldNotBeNil)
  5443  			So(err, ShouldBeNil)
  5444  			So(resp.StatusCode(), ShouldEqual, 200)
  5445  
  5446  			responseStruct = &zcommon.GlobalSearchResultResp{}
  5447  
  5448  			err = json.Unmarshal(resp.Body(), responseStruct)
  5449  			So(err, ShouldBeNil)
  5450  
  5451  			So(responseStruct.Images[0].IsSigned, ShouldBeFalse)
  5452  		})
  5453  
  5454  		Convey("Delete a notary signature", func() {
  5455  			repo := "repo1"
  5456  			err := signature.SignImageUsingNotary("repo1:1.0.1", port, true)
  5457  			So(err, ShouldBeNil)
  5458  
  5459  			query := `
  5460  			{
  5461  				GlobalSearch(query:"repo1:1.0.1"){
  5462  					Images {
  5463  						RepoName Tag LastUpdated Size IsSigned
  5464  						Manifests{
  5465  							Platform { Os Arch }
  5466  							LastUpdated Size
  5467  						}
  5468  					}
  5469  				}
  5470  			}`
  5471  
  5472  			// test if it's signed
  5473  			resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5474  			So(resp, ShouldNotBeNil)
  5475  			So(err, ShouldBeNil)
  5476  			So(resp.StatusCode(), ShouldEqual, 200)
  5477  
  5478  			responseStruct := &zcommon.GlobalSearchResultResp{}
  5479  
  5480  			err = json.Unmarshal(resp.Body(), responseStruct)
  5481  			So(err, ShouldBeNil)
  5482  
  5483  			So(responseStruct.Images[0].IsSigned, ShouldBeTrue)
  5484  
  5485  			// get signatur digest
  5486  			log := log.NewLogger("debug", "")
  5487  			metrics := monitoring.NewMetricsServer(false, log)
  5488  			storage := local.NewImageStore(dir, false, false, log, metrics, nil, nil)
  5489  
  5490  			indexBlob, err := storage.GetIndexContent(repo)
  5491  			So(err, ShouldBeNil)
  5492  
  5493  			var indexContent ispec.Index
  5494  
  5495  			err = json.Unmarshal(indexBlob, &indexContent)
  5496  			So(err, ShouldBeNil)
  5497  
  5498  			signatureReference := ""
  5499  
  5500  			var sigManifestContent ispec.Manifest
  5501  
  5502  			for _, manifest := range indexContent.Manifests {
  5503  				manifestBlob, _, _, err := storage.GetImageManifest(repo, manifest.Digest.String())
  5504  				So(err, ShouldBeNil)
  5505  				var manifestContent ispec.Manifest
  5506  
  5507  				err = json.Unmarshal(manifestBlob, &manifestContent)
  5508  				So(err, ShouldBeNil)
  5509  
  5510  				if zcommon.GetManifestArtifactType(manifestContent) == notreg.ArtifactTypeNotation {
  5511  					signatureReference = manifest.Digest.String()
  5512  					manifestBlob, _, _, err := storage.GetImageManifest(repo, signatureReference)
  5513  					So(err, ShouldBeNil)
  5514  					err = json.Unmarshal(manifestBlob, &sigManifestContent)
  5515  					So(err, ShouldBeNil)
  5516  				}
  5517  			}
  5518  
  5519  			So(sigManifestContent, ShouldNotBeZeroValue)
  5520  			// check notation signature
  5521  			manifest1Blob, err := json.Marshal(image1.Manifest)
  5522  			So(err, ShouldBeNil)
  5523  			manifest1Digest := godigest.FromBytes(manifest1Blob)
  5524  			So(sigManifestContent.Subject, ShouldNotBeNil)
  5525  			So(sigManifestContent.Subject.Digest.String(), ShouldEqual, manifest1Digest.String())
  5526  
  5527  			// delete the signature using the digest
  5528  			resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + signatureReference)
  5529  			So(resp, ShouldNotBeNil)
  5530  			So(err, ShouldBeNil)
  5531  			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
  5532  
  5533  			// verify isSigned again and it should be false
  5534  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5535  			So(resp, ShouldNotBeNil)
  5536  			So(err, ShouldBeNil)
  5537  			So(resp.StatusCode(), ShouldEqual, 200)
  5538  
  5539  			responseStruct = &zcommon.GlobalSearchResultResp{}
  5540  
  5541  			err = json.Unmarshal(resp.Body(), responseStruct)
  5542  			So(err, ShouldBeNil)
  5543  
  5544  			So(responseStruct.Images[0].IsSigned, ShouldBeFalse)
  5545  		})
  5546  
  5547  		Convey("Delete a referrer", func() {
  5548  			referredImageDescriptor := &ispec.Descriptor{
  5549  				Digest:    image1.ManifestDescriptor.Digest,
  5550  				MediaType: ispec.MediaTypeImageManifest,
  5551  			}
  5552  
  5553  			referrerImage := CreateImageWith().RandomLayers(1, 100).DefaultConfig().
  5554  				Subject(referredImageDescriptor).Build()
  5555  
  5556  			err = UploadImage(referrerImage, baseURL, "repo1", referrerImage.DigestStr())
  5557  			So(err, ShouldBeNil)
  5558  
  5559  			// ------- check referrers for this image
  5560  
  5561  			query := fmt.Sprintf(`
  5562  			{
  5563  				Referrers(repo:"repo1", digest:"%s"){
  5564  					MediaType
  5565  					Digest
  5566  				}
  5567  			}`, image1.ManifestDescriptor.Digest.String())
  5568  
  5569  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5570  			So(resp, ShouldNotBeNil)
  5571  			So(err, ShouldBeNil)
  5572  			So(resp.StatusCode(), ShouldEqual, 200)
  5573  
  5574  			responseStruct := &zcommon.ReferrersResp{}
  5575  
  5576  			err = json.Unmarshal(resp.Body(), responseStruct)
  5577  			So(err, ShouldBeNil)
  5578  
  5579  			So(len(responseStruct.Referrers), ShouldEqual, 1)
  5580  			So(responseStruct.Referrers[0].Digest, ShouldResemble, referrerImage.DigestStr())
  5581  
  5582  			statusCode, err := DeleteImage("repo1", referrerImage.DigestStr(), "badURL")
  5583  			So(err, ShouldNotBeNil)
  5584  			So(statusCode, ShouldEqual, -1)
  5585  
  5586  			// ------- Delete the referrer and see if it disappears from metaDB also
  5587  			statusCode, err = DeleteImage("repo1", referrerImage.DigestStr(), baseURL)
  5588  			So(err, ShouldBeNil)
  5589  			So(statusCode, ShouldEqual, http.StatusAccepted)
  5590  
  5591  			resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5592  			So(resp, ShouldNotBeNil)
  5593  			So(err, ShouldBeNil)
  5594  			So(resp.StatusCode(), ShouldEqual, 200)
  5595  
  5596  			responseStruct = &zcommon.ReferrersResp{}
  5597  
  5598  			err = json.Unmarshal(resp.Body(), responseStruct)
  5599  			So(err, ShouldBeNil)
  5600  
  5601  			So(len(responseStruct.Referrers), ShouldEqual, 0)
  5602  		})
  5603  
  5604  		Convey("Deleting causes errors", func() {
  5605  			Convey("error while backing up the manifest", func() {
  5606  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  5607  					GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
  5608  						return []byte{}, "", "", zerr.ErrRepoNotFound
  5609  					},
  5610  				}
  5611  				resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference")
  5612  				So(resp, ShouldNotBeNil)
  5613  				So(err, ShouldBeNil)
  5614  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  5615  					GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
  5616  						return []byte{}, "", "", zerr.ErrBadManifest
  5617  					},
  5618  				}
  5619  				resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference")
  5620  				So(resp, ShouldNotBeNil)
  5621  				So(err, ShouldBeNil)
  5622  
  5623  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  5624  					GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
  5625  						return []byte{}, "", "", zerr.ErrRepoNotFound
  5626  					},
  5627  				}
  5628  				resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference")
  5629  				So(resp, ShouldNotBeNil)
  5630  				So(err, ShouldBeNil)
  5631  			})
  5632  
  5633  			Convey("imageIsSignature fails", func() {
  5634  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  5635  					PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
  5636  						godigest.Digest, error,
  5637  					) {
  5638  						return "", "", nil
  5639  					},
  5640  					DeleteImageManifestFn: func(repo, reference string, dc bool) error {
  5641  						return nil
  5642  					},
  5643  				}
  5644  
  5645  				resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference")
  5646  				So(resp, ShouldNotBeNil)
  5647  				So(err, ShouldBeNil)
  5648  				So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
  5649  			})
  5650  
  5651  			Convey("image is a signature, DeleteSignature fails", func() {
  5652  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  5653  					NewBlobUploadFn: ctlr.StoreController.DefaultStore.NewBlobUpload,
  5654  					PutBlobChunkFn:  ctlr.StoreController.DefaultStore.PutBlobChunk,
  5655  					GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
  5656  						configBlob, err := json.Marshal(ispec.Image{})
  5657  						So(err, ShouldBeNil)
  5658  
  5659  						return configBlob, nil
  5660  					},
  5661  					PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
  5662  						godigest.Digest, error,
  5663  					) {
  5664  						return "", "", nil
  5665  					},
  5666  					DeleteImageManifestFn: func(repo, reference string, dc bool) error {
  5667  						return nil
  5668  					},
  5669  					GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
  5670  						return []byte("{}"), "1", "1", nil
  5671  					},
  5672  				}
  5673  
  5674  				resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" +
  5675  					"sha256-343ebab94a7674da181c6ea3da013aee4f8cbe357870f8dcaf6268d5343c3474.sig")
  5676  				So(resp, ShouldNotBeNil)
  5677  				So(err, ShouldBeNil)
  5678  				So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
  5679  			})
  5680  
  5681  			Convey("image is a signature, PutImageManifest fails", func() {
  5682  				ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
  5683  					NewBlobUploadFn: ctlr.StoreController.DefaultStore.NewBlobUpload,
  5684  					PutBlobChunkFn:  ctlr.StoreController.DefaultStore.PutBlobChunk,
  5685  					GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
  5686  						configBlob, err := json.Marshal(ispec.Image{})
  5687  						So(err, ShouldBeNil)
  5688  
  5689  						return configBlob, nil
  5690  					},
  5691  					PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
  5692  						godigest.Digest, error,
  5693  					) {
  5694  						return "", "", ErrTestError
  5695  					},
  5696  					DeleteImageManifestFn: func(repo, reference string, dc bool) error {
  5697  						return nil
  5698  					},
  5699  					GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
  5700  						return []byte("{}"), "1", "1", nil
  5701  					},
  5702  				}
  5703  
  5704  				ctlr.MetaDB = mocks.MetaDBMock{
  5705  					RemoveRepoReferenceFn: func(repo, reference string, manifestDigest godigest.Digest,
  5706  					) error {
  5707  						return ErrTestError
  5708  					},
  5709  				}
  5710  
  5711  				resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" +
  5712  					"343ebab94a7674da181c6ea3da013aee4f8cbe357870f8dcaf6268d5343c3474.sig")
  5713  				So(resp, ShouldNotBeNil)
  5714  				So(err, ShouldBeNil)
  5715  				So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
  5716  			})
  5717  		})
  5718  	})
  5719  }
  5720  
  5721  func TestSearchSize(t *testing.T) {
  5722  	Convey("Repo sizes", t, func() {
  5723  		port := GetFreePort()
  5724  		baseURL := GetBaseURL(port)
  5725  
  5726  		conf := config.New()
  5727  		conf.HTTP.Port = port
  5728  		tr := true
  5729  		conf.Extensions = &extconf.ExtensionConfig{
  5730  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &tr}},
  5731  		}
  5732  
  5733  		ctlr := api.NewController(conf)
  5734  		dir := t.TempDir()
  5735  		ctlr.Config.Storage.RootDirectory = dir
  5736  
  5737  		ctlrManager := NewControllerManager(ctlr)
  5738  		ctlrManager.StartAndWait(port)
  5739  		defer ctlrManager.StopServer()
  5740  
  5741  		repoName := "testrepo"
  5742  		uploadedImage := CreateImageWith().RandomLayers(1, 10000).DefaultConfig().Build()
  5743  
  5744  		configSize := uploadedImage.ConfigDescriptor.Size
  5745  		manifestSize := uploadedImage.ManifestDescriptor.Size
  5746  		layersSize := int64(0)
  5747  		for _, l := range uploadedImage.Layers {
  5748  			layersSize += int64(len(l))
  5749  		}
  5750  
  5751  		err := UploadImage(uploadedImage, baseURL, repoName, "latest")
  5752  		So(err, ShouldBeNil)
  5753  
  5754  		query := `
  5755  		{
  5756  			GlobalSearch(query:"testrepo:"){
  5757  				Images {
  5758  					RepoName Tag LastUpdated Size Vendor
  5759  					Manifests{
  5760  						Platform { Os Arch }
  5761  						LastUpdated Size
  5762  					}
  5763  				}
  5764  				Repos {
  5765  					Name LastUpdated Size
  5766  					NewestImage {
  5767  						Manifests{
  5768  							Platform { Os Arch }
  5769  							LastUpdated Size
  5770  						}
  5771  					}
  5772  				}
  5773  			}
  5774  		}`
  5775  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5776  		So(err, ShouldBeNil)
  5777  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5778  		So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue)
  5779  
  5780  		responseStruct := &zcommon.GlobalSearchResultResp{}
  5781  		err = json.Unmarshal(resp.Body(), responseStruct)
  5782  		So(err, ShouldBeNil)
  5783  
  5784  		image := responseStruct.GlobalSearchResult.GlobalSearch.Images[0]
  5785  		So(image.Tag, ShouldResemble, "latest")
  5786  
  5787  		size, err := strconv.Atoi(image.Size)
  5788  		So(err, ShouldBeNil)
  5789  		So(size, ShouldEqual, configSize+layersSize+manifestSize)
  5790  
  5791  		query = `
  5792  		{
  5793  			GlobalSearch(query:"testrepo"){
  5794  				Images {
  5795  					RepoName Tag LastUpdated Size
  5796  					Manifests{
  5797  						Platform { Os Arch }
  5798  						LastUpdated Size
  5799  					}
  5800  				}
  5801  				Repos {
  5802  					Name LastUpdated Size
  5803  					NewestImage {
  5804  						Manifests{
  5805  							Platform { Os Arch }
  5806  							LastUpdated Size
  5807  						}
  5808  					}
  5809  				}
  5810  			}
  5811  		}`
  5812  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5813  		So(err, ShouldBeNil)
  5814  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5815  		So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue)
  5816  
  5817  		responseStruct = &zcommon.GlobalSearchResultResp{}
  5818  		err = json.Unmarshal(resp.Body(), responseStruct)
  5819  		So(err, ShouldBeNil)
  5820  
  5821  		repo := responseStruct.GlobalSearchResult.GlobalSearch.Repos[0]
  5822  		size, err = strconv.Atoi(repo.Size)
  5823  		So(err, ShouldBeNil)
  5824  		So(size, ShouldEqual, configSize+layersSize+manifestSize)
  5825  
  5826  		// add the same image with different tag
  5827  		err = UploadImage(uploadedImage, baseURL, repoName, "10.2.14")
  5828  		So(err, ShouldBeNil)
  5829  
  5830  		// query for images
  5831  		query = `
  5832  		{
  5833  			GlobalSearch(query:"testrepo:"){
  5834  				Images {
  5835  					RepoName Tag LastUpdated Size
  5836  					Manifests{
  5837  						Platform { Os Arch }
  5838  						LastUpdated Size
  5839  					}
  5840  				}
  5841  				Repos {
  5842  					Name LastUpdated Size
  5843  					NewestImage {
  5844  						Manifests{
  5845  							Platform { Os Arch }
  5846  							LastUpdated Size
  5847  						}
  5848  					}
  5849  				}
  5850  				Layers { Digest Size }
  5851  			}
  5852  		}`
  5853  
  5854  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5855  		So(err, ShouldBeNil)
  5856  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5857  		So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue)
  5858  
  5859  		responseStruct = &zcommon.GlobalSearchResultResp{}
  5860  		err = json.Unmarshal(resp.Body(), responseStruct)
  5861  		So(err, ShouldBeNil)
  5862  
  5863  		So(len(responseStruct.Images), ShouldEqual, 2)
  5864  		// check that the repo size is the same
  5865  		// query for repos
  5866  		query = `
  5867  		{
  5868  			GlobalSearch(query:"testrepo"){
  5869  				Images {
  5870  					RepoName Tag LastUpdated Size
  5871  					Manifests{
  5872  						Platform { Os Arch }
  5873  						LastUpdated Size
  5874  					}
  5875  				}
  5876  				Repos {
  5877  					Name LastUpdated Size
  5878  					NewestImage {
  5879  						Manifests{
  5880  							Platform { Os Arch }
  5881  							LastUpdated Size
  5882  						}
  5883  					}
  5884  				}
  5885  				Layers { Digest Size }
  5886  			}
  5887  		}`
  5888  
  5889  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  5890  		So(err, ShouldBeNil)
  5891  		So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  5892  		So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue)
  5893  
  5894  		responseStruct = &zcommon.GlobalSearchResultResp{}
  5895  		err = json.Unmarshal(resp.Body(), responseStruct)
  5896  		So(err, ShouldBeNil)
  5897  
  5898  		repo = responseStruct.GlobalSearchResult.GlobalSearch.Repos[0]
  5899  		size, err = strconv.Atoi(repo.Size)
  5900  		So(err, ShouldBeNil)
  5901  		So(size, ShouldEqual, configSize+layersSize+manifestSize)
  5902  	})
  5903  }
  5904  
  5905  func TestImageSummary(t *testing.T) {
  5906  	Convey("GraphQL query ImageSummary", t, func() {
  5907  		port := GetFreePort()
  5908  		baseURL := GetBaseURL(port)
  5909  		conf := config.New()
  5910  		conf.HTTP.Port = port
  5911  		conf.Storage.RootDirectory = t.TempDir()
  5912  		conf.Storage.GC = false
  5913  
  5914  		defaultVal := true
  5915  		conf.Extensions = &extconf.ExtensionConfig{
  5916  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  5917  		}
  5918  
  5919  		conf.Extensions.Search.CVE = nil
  5920  
  5921  		ctlr := api.NewController(conf)
  5922  
  5923  		gqlQuery := `
  5924  			{
  5925  				Image(image:"%s:%s"){
  5926  					RepoName
  5927  					Tag
  5928  					Digest
  5929  					MediaType
  5930  					Manifests {
  5931  						Digest
  5932  						ConfigDigest
  5933  						LastUpdated
  5934  						Size
  5935  						Platform { Os Arch }
  5936  						Layers { Digest Size }
  5937  						Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  5938  						History {
  5939  							HistoryDescription { Created }
  5940  							Layer { Digest Size }
  5941  						}
  5942  					}
  5943  					LastUpdated
  5944  					Size
  5945  					Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  5946  					Referrers {MediaType ArtifactType Digest Annotations {Key Value}}
  5947  				}
  5948  			}`
  5949  
  5950  		noTagQuery := `
  5951  			{
  5952  				Image(image:"%s"){
  5953  					RepoName,
  5954  					Tag,
  5955  					Digest,
  5956  					MediaType,
  5957  					Manifests {
  5958  						Digest
  5959  						ConfigDigest
  5960  						LastUpdated
  5961  						Size
  5962  						Platform { Os Arch }
  5963  						Layers { Digest Size }
  5964  						Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  5965  						History {
  5966  							HistoryDescription { Created }
  5967  							Layer { Digest Size }
  5968  						}
  5969  					},
  5970  					Size
  5971  				}
  5972  			}`
  5973  
  5974  		gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix)
  5975  
  5976  		ctlrManager := NewControllerManager(ctlr)
  5977  		ctlrManager.StartAndWait(port)
  5978  		defer ctlrManager.StopServer()
  5979  
  5980  		repoName := "test-repo" //nolint:goconst
  5981  		tagTarget := "latest"
  5982  
  5983  		createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
  5984  
  5985  		image := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{
  5986  			History: []ispec.History{{Created: &createdTime}},
  5987  			Platform: ispec.Platform{
  5988  				Architecture: "amd64",
  5989  				OS:           "linux",
  5990  			},
  5991  		}).Build()
  5992  
  5993  		manifestDigest := image.ManifestDescriptor.Digest
  5994  
  5995  		err := UploadImage(image, baseURL, repoName, tagTarget)
  5996  		So(err, ShouldBeNil)
  5997  
  5998  		// ------ Add a referrer
  5999  		referrerImage := CreateImageWith().RandomLayers(1, 100).
  6000  			CustomConfigBlob(
  6001  				[]byte{},
  6002  				"application/test.artifact.type",
  6003  			).
  6004  			Subject(&ispec.Descriptor{
  6005  				Digest:    manifestDigest,
  6006  				MediaType: ispec.MediaTypeImageManifest,
  6007  			}).Annotations(map[string]string{"testAnnotationKey": "testAnnotationValue"}).
  6008  			Build()
  6009  
  6010  		referrerManifestDigest := referrerImage.Digest()
  6011  
  6012  		err = UploadImage(referrerImage, baseURL, repoName, referrerManifestDigest.String())
  6013  		So(err, ShouldBeNil)
  6014  
  6015  		var (
  6016  			imgSummaryResponse zcommon.ImageSummaryResult
  6017  			strQuery           string
  6018  			targetURL          string
  6019  			resp               *resty.Response
  6020  		)
  6021  
  6022  		t.Log("starting test to retrieve image without reference")
  6023  		strQuery = fmt.Sprintf(noTagQuery, repoName)
  6024  		targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
  6025  		contains := false
  6026  
  6027  		resp, err = resty.R().Get(targetURL)
  6028  		So(resp, ShouldNotBeNil)
  6029  		So(err, ShouldBeNil)
  6030  		So(resp.StatusCode(), ShouldEqual, 200)
  6031  		So(resp.Body(), ShouldNotBeNil)
  6032  
  6033  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6034  		So(err, ShouldBeNil)
  6035  		for _, err := range imgSummaryResponse.Errors {
  6036  			result := strings.Contains(err.Message, "no reference provided")
  6037  			if result {
  6038  				contains = result
  6039  			}
  6040  		}
  6041  		So(contains, ShouldBeTrue)
  6042  
  6043  		t.Log("starting Test retrieve image based on image identifier")
  6044  		// gql is parametrized with the repo.
  6045  		strQuery = fmt.Sprintf(gqlQuery, repoName, tagTarget)
  6046  		targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
  6047  
  6048  		resp, err = resty.R().Get(targetURL)
  6049  		So(resp, ShouldNotBeNil)
  6050  		So(err, ShouldBeNil)
  6051  		So(resp.StatusCode(), ShouldEqual, 200)
  6052  		So(resp.Body(), ShouldNotBeNil)
  6053  
  6054  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6055  		So(err, ShouldBeNil)
  6056  		So(imgSummaryResponse, ShouldNotBeNil)
  6057  		So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil)
  6058  		So(imgSummaryResponse.ImageSummary, ShouldNotBeNil)
  6059  		imgSummary := imgSummaryResponse.SingleImageSummary.ImageSummary
  6060  		So(imgSummary.RepoName, ShouldContainSubstring, repoName)
  6061  		So(imgSummary.Tag, ShouldContainSubstring, tagTarget)
  6062  		So(imgSummary.Digest, ShouldContainSubstring, manifestDigest.Encoded())
  6063  		So(imgSummary.MediaType, ShouldContainSubstring, ispec.MediaTypeImageManifest)
  6064  		So(imgSummary.Manifests[0].ConfigDigest, ShouldContainSubstring, image.Manifest.Config.Digest.Encoded())
  6065  		So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded())
  6066  		So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 1)
  6067  		So(imgSummary.Manifests[0].Layers[0].Digest, ShouldContainSubstring,
  6068  			image.Manifest.Layers[0].Digest.Encoded())
  6069  		So(imgSummary.LastUpdated, ShouldEqual, createdTime)
  6070  		So(imgSummary.IsSigned, ShouldEqual, false)
  6071  		So(imgSummary.Manifests[0].Platform.Os, ShouldEqual, "linux")
  6072  		So(imgSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64")
  6073  		So(len(imgSummary.Manifests[0].History), ShouldEqual, 1)
  6074  		So(imgSummary.Manifests[0].History[0].HistoryDescription.Created, ShouldEqual, createdTime)
  6075  		// No vulnerabilities should be detected since trivy is disabled
  6076  		So(imgSummary.Vulnerabilities.Count, ShouldEqual, 0)
  6077  		So(imgSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  6078  		So(imgSummary.Vulnerabilities.LowCount, ShouldEqual, 0)
  6079  		So(imgSummary.Vulnerabilities.MediumCount, ShouldEqual, 0)
  6080  		So(imgSummary.Vulnerabilities.HighCount, ShouldEqual, 0)
  6081  		So(imgSummary.Vulnerabilities.CriticalCount, ShouldEqual, 0)
  6082  		So(imgSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "")
  6083  		So(len(imgSummary.Referrers), ShouldEqual, 1)
  6084  		So(imgSummary.Referrers[0], ShouldResemble, zcommon.Referrer{
  6085  			MediaType:    ispec.MediaTypeImageManifest,
  6086  			ArtifactType: "application/test.artifact.type",
  6087  			Digest:       referrerManifestDigest.String(),
  6088  			Annotations:  []zcommon.Annotation{{Key: "testAnnotationKey", Value: "testAnnotationValue"}},
  6089  		})
  6090  
  6091  		t.Log("starting Test retrieve duplicated image same layers based on image identifier")
  6092  		// gqlEndpoint
  6093  		strQuery = fmt.Sprintf(gqlQuery, "wrong-repo-does-not-exist", "latest")
  6094  		targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
  6095  
  6096  		resp, err = resty.R().Get(targetURL)
  6097  		So(resp, ShouldNotBeNil)
  6098  		So(err, ShouldBeNil)
  6099  		So(resp.StatusCode(), ShouldEqual, 200)
  6100  		So(resp.Body(), ShouldNotBeNil)
  6101  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6102  		So(err, ShouldBeNil)
  6103  		So(imgSummaryResponse, ShouldNotBeNil)
  6104  		So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil)
  6105  		So(imgSummaryResponse.ImageSummary, ShouldNotBeNil)
  6106  
  6107  		So(len(imgSummaryResponse.Errors), ShouldEqual, 1)
  6108  		So(imgSummaryResponse.Errors[0].Message,
  6109  			ShouldContainSubstring, "repo metadata not found for given repo name")
  6110  
  6111  		t.Log("starting Test retrieve image with bad tag")
  6112  		// gql is parametrized with the repo.
  6113  		strQuery = fmt.Sprintf(gqlQuery, repoName, "nonexisttag")
  6114  		targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
  6115  
  6116  		resp, err = resty.R().Get(targetURL)
  6117  		So(resp, ShouldNotBeNil)
  6118  		So(err, ShouldBeNil)
  6119  		So(resp.StatusCode(), ShouldEqual, 200)
  6120  		So(resp.Body(), ShouldNotBeNil)
  6121  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6122  		So(err, ShouldBeNil)
  6123  		So(imgSummaryResponse, ShouldNotBeNil)
  6124  		So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil)
  6125  		So(imgSummaryResponse.ImageSummary, ShouldNotBeNil)
  6126  
  6127  		So(len(imgSummaryResponse.Errors), ShouldEqual, 1)
  6128  		So(imgSummaryResponse.Errors[0].Message,
  6129  			ShouldContainSubstring, "can't find image: test-repo:nonexisttag")
  6130  	})
  6131  
  6132  	Convey("GraphQL query ImageSummary with Vulnerability scan enabled", t, func() {
  6133  		port := GetFreePort()
  6134  		baseURL := GetBaseURL(port)
  6135  		conf := config.New()
  6136  		conf.HTTP.Port = port
  6137  		conf.Storage.RootDirectory = t.TempDir()
  6138  
  6139  		defaultVal := true
  6140  		updateDuration, _ := time.ParseDuration("1h")
  6141  		trivyConfig := &extconf.TrivyConfig{
  6142  			DBRepository: "ghcr.io/project-zot/trivy-db",
  6143  		}
  6144  		cveConfig := &extconf.CVEConfig{
  6145  			UpdateInterval: updateDuration,
  6146  			Trivy:          trivyConfig,
  6147  		}
  6148  		searchConfig := &extconf.SearchConfig{
  6149  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
  6150  			CVE:        cveConfig,
  6151  		}
  6152  		conf.Extensions = &extconf.ExtensionConfig{
  6153  			Search: searchConfig,
  6154  		}
  6155  
  6156  		ctlr := api.NewController(conf)
  6157  
  6158  		gqlQuery := `
  6159  			{
  6160  				Image(image:"%s:%s"){
  6161  					RepoName
  6162  					Tag
  6163  					Manifests {
  6164  						Digest
  6165  						ConfigDigest
  6166  						LastUpdated
  6167  						Size
  6168  						Platform { Os Arch }
  6169  						Layers { Digest Size }
  6170  						Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  6171  						History {
  6172  							HistoryDescription { Created }
  6173  							Layer { Digest Size }
  6174  						}
  6175  					}
  6176  					LastUpdated
  6177  					Size
  6178  					Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  6179  				}
  6180  			}`
  6181  
  6182  		gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix)
  6183  
  6184  		createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
  6185  
  6186  		image := CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{
  6187  			History: []ispec.History{{Created: &createdTime}},
  6188  			Platform: ispec.Platform{
  6189  				Architecture: "amd64",
  6190  				OS:           "linux",
  6191  			},
  6192  		}).Build()
  6193  
  6194  		manifestDigest := image.ManifestDescriptor.Digest
  6195  		configDigest := image.ConfigDescriptor.Digest
  6196  
  6197  		if err := ctlr.Init(); err != nil {
  6198  			panic(err)
  6199  		}
  6200  
  6201  		ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
  6202  
  6203  		go func() {
  6204  			if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) {
  6205  				panic(err)
  6206  			}
  6207  		}()
  6208  
  6209  		defer ctlr.Shutdown()
  6210  
  6211  		WaitTillServerReady(baseURL)
  6212  
  6213  		repoName := "test-repo" //nolint:goconst
  6214  		tagTarget := "latest"
  6215  		err := UploadImage(image, baseURL, repoName, tagTarget)
  6216  		So(err, ShouldBeNil)
  6217  		var (
  6218  			imgSummaryResponse zcommon.ImageSummaryResult
  6219  			strQuery           string
  6220  			targetURL          string
  6221  			resp               *resty.Response
  6222  		)
  6223  
  6224  		t.Log("starting Test retrieve image based on image identifier")
  6225  		// gql is parametrized with the repo.
  6226  		strQuery = fmt.Sprintf(gqlQuery, repoName, tagTarget)
  6227  		targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery))
  6228  
  6229  		resp, err = resty.R().Get(targetURL)
  6230  		So(resp, ShouldNotBeNil)
  6231  		So(err, ShouldBeNil)
  6232  		So(resp.StatusCode(), ShouldEqual, 200)
  6233  		So(resp.Body(), ShouldNotBeNil)
  6234  
  6235  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6236  		So(err, ShouldBeNil)
  6237  		So(imgSummaryResponse, ShouldNotBeNil)
  6238  		So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil)
  6239  		So(imgSummaryResponse.ImageSummary, ShouldNotBeNil)
  6240  
  6241  		imgSummary := imgSummaryResponse.ImageSummary
  6242  		So(imgSummary.RepoName, ShouldContainSubstring, repoName)
  6243  		So(imgSummary.Tag, ShouldContainSubstring, tagTarget)
  6244  		So(imgSummary.Manifests[0].ConfigDigest, ShouldContainSubstring, configDigest.Encoded())
  6245  		So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded())
  6246  		So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 3)
  6247  		So(imgSummary.Manifests[0].Layers[0].Digest, ShouldContainSubstring,
  6248  			godigest.FromBytes(image.Layers[0]).Encoded())
  6249  		So(imgSummary.LastUpdated, ShouldEqual, createdTime)
  6250  		So(imgSummary.IsSigned, ShouldEqual, false)
  6251  		So(imgSummary.Manifests[0].Platform.Os, ShouldEqual, "linux")
  6252  		So(imgSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64")
  6253  		So(len(imgSummary.Manifests[0].History), ShouldEqual, 1)
  6254  		So(imgSummary.Manifests[0].History[0].HistoryDescription.Created, ShouldEqual, createdTime)
  6255  		So(imgSummary.Vulnerabilities.Count, ShouldEqual, 4)
  6256  		So(imgSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0)
  6257  		So(imgSummary.Vulnerabilities.LowCount, ShouldEqual, 1)
  6258  		So(imgSummary.Vulnerabilities.MediumCount, ShouldEqual, 1)
  6259  		So(imgSummary.Vulnerabilities.HighCount, ShouldEqual, 1)
  6260  		So(imgSummary.Vulnerabilities.CriticalCount, ShouldEqual, 1)
  6261  		// There are 0 vulnerabilities this data used in tests
  6262  		So(imgSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
  6263  	})
  6264  
  6265  	Convey("GraphQL query for Artifact Type", t, func() {
  6266  		port := GetFreePort()
  6267  		baseURL := GetBaseURL(port)
  6268  		conf := config.New()
  6269  		conf.HTTP.Port = port
  6270  		conf.Storage.RootDirectory = t.TempDir()
  6271  		conf.Storage.GC = false
  6272  
  6273  		defaultVal := true
  6274  		conf.Extensions = &extconf.ExtensionConfig{
  6275  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
  6276  		}
  6277  
  6278  		conf.Extensions.Search.CVE = nil
  6279  
  6280  		ctlr := api.NewController(conf)
  6281  
  6282  		query := `
  6283  			{
  6284  				Image(image:"repo:art%d"){
  6285  					RepoName
  6286  					Tag
  6287  					Manifests {
  6288  						Digest
  6289  						ArtifactType
  6290  					}
  6291  					Size
  6292  				}
  6293  			}`
  6294  
  6295  		queryImg1 := fmt.Sprintf(query, 1)
  6296  		queryImg2 := fmt.Sprintf(query, 2)
  6297  
  6298  		var imgSummaryResponse zcommon.ImageSummaryResult
  6299  
  6300  		ctlrManager := NewControllerManager(ctlr)
  6301  		ctlrManager.StartAndWait(port)
  6302  		defer ctlrManager.StopServer()
  6303  
  6304  		// upload the images
  6305  		artType1 := "application/test.signature.v1"
  6306  		artType2 := "application/test.signature.v2"
  6307  
  6308  		img1 := CreateImageWith().RandomLayers(1, 20).EmptyConfig().ArtifactType(artType1).Build()
  6309  
  6310  		err := UploadImage(img1, baseURL, "repo", "art1")
  6311  		So(err, ShouldBeNil)
  6312  
  6313  		img2 := CreateImageWith().RandomLayers(1, 20).DefaultConfig().ArtifactType(artType2).Build()
  6314  
  6315  		err = UploadImage(img2, baseURL, "repo", "art2")
  6316  		So(err, ShouldBeNil)
  6317  
  6318  		// GET image 1
  6319  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImg1))
  6320  		So(resp, ShouldNotBeNil)
  6321  		So(err, ShouldBeNil)
  6322  		So(resp.StatusCode(), ShouldEqual, 200)
  6323  		So(resp.Body(), ShouldNotBeNil)
  6324  
  6325  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6326  		So(err, ShouldBeNil)
  6327  
  6328  		imgSum := imgSummaryResponse.SingleImageSummary.ImageSummary
  6329  		So(len(imgSum.Manifests), ShouldEqual, 1)
  6330  		So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType1)
  6331  
  6332  		// GET image 2
  6333  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImg2))
  6334  		So(resp, ShouldNotBeNil)
  6335  		So(err, ShouldBeNil)
  6336  		So(resp.StatusCode(), ShouldEqual, 200)
  6337  		So(resp.Body(), ShouldNotBeNil)
  6338  
  6339  		err = json.Unmarshal(resp.Body(), &imgSummaryResponse)
  6340  		So(err, ShouldBeNil)
  6341  
  6342  		imgSum = imgSummaryResponse.SingleImageSummary.ImageSummary
  6343  		So(len(imgSum.Manifests), ShouldEqual, 1)
  6344  		So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType2)
  6345  
  6346  		// Expanded repo info test
  6347  
  6348  		queryExpRepoInfo := `{
  6349  			ExpandedRepoInfo(repo:"test1"){
  6350  				Images {
  6351  					Tag
  6352  					Manifests {
  6353  						Digest
  6354  						ArtifactType
  6355  					}
  6356  				}
  6357  			}
  6358  		}`
  6359  
  6360  		var expandedRepoInfoResp zcommon.ExpandedRepoInfoResp
  6361  
  6362  		resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" +
  6363  			url.QueryEscape(queryExpRepoInfo))
  6364  		So(resp, ShouldNotBeNil)
  6365  		So(err, ShouldBeNil)
  6366  		So(resp.StatusCode(), ShouldEqual, 200)
  6367  		So(resp.Body(), ShouldNotBeNil)
  6368  
  6369  		err = json.Unmarshal(resp.Body(), &expandedRepoInfoResp)
  6370  		So(err, ShouldBeNil)
  6371  
  6372  		imgSums := expandedRepoInfoResp.ExpandedRepoInfo.RepoInfo.ImageSummaries
  6373  
  6374  		for _, imgSum := range imgSums {
  6375  			switch imgSum.Digest {
  6376  			case img1.ManifestDescriptor.Digest.String():
  6377  				So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType1)
  6378  			case img2.ManifestDescriptor.Digest.String():
  6379  				So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType2)
  6380  			}
  6381  		}
  6382  	})
  6383  }
  6384  
  6385  func TestUploadingArtifactsWithDifferentMediaType(t *testing.T) {
  6386  	Convey("", t, func() {
  6387  		port := GetFreePort()
  6388  		baseURL := GetBaseURL(port)
  6389  		conf := config.New()
  6390  		conf.HTTP.Port = port
  6391  		conf.Storage.RootDirectory = t.TempDir()
  6392  		conf.Storage.GC = false
  6393  
  6394  		defaultVal := true
  6395  		conf.Extensions = &extconf.ExtensionConfig{
  6396  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, CVE: nil},
  6397  		}
  6398  		conf.Log = &config.LogConfig{Level: "debug", Output: "/dev/null"}
  6399  
  6400  		ctlr := api.NewController(conf)
  6401  
  6402  		ctlrManager := NewControllerManager(ctlr)
  6403  		ctlrManager.StartAndWait(port)
  6404  		defer ctlrManager.StopServer()
  6405  
  6406  		const customMediaType = "application/custom.media.type+json"
  6407  
  6408  		imageWithIncompatibleConfig := CreateImageWith().DefaultLayers().
  6409  			CustomConfigBlob([]byte(`{"author": {"key": "val"}}`), customMediaType).Build()
  6410  
  6411  		defaultImage := CreateDefaultImage()
  6412  
  6413  		var configContent ispec.Image
  6414  		err := json.Unmarshal(imageWithIncompatibleConfig.ConfigDescriptor.Data, &configContent)
  6415  		So(err, ShouldNotBeNil)
  6416  
  6417  		err = UploadImage(imageWithIncompatibleConfig, baseURL, "repo", "incompatible-image")
  6418  		So(err, ShouldBeNil)
  6419  
  6420  		err = UploadImage(defaultImage, baseURL, "repo", "default-image")
  6421  		So(err, ShouldBeNil)
  6422  
  6423  		query := `
  6424  			{
  6425  				GlobalSearch(query:"repo:incompatible-image"){
  6426  					Images {
  6427  						RepoName Tag
  6428  						Manifests {
  6429  							Digest ConfigDigest
  6430  						}
  6431  					}
  6432  				}
  6433  			}`
  6434  
  6435  		resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
  6436  		So(resp, ShouldNotBeNil)
  6437  		So(err, ShouldBeNil)
  6438  		So(resp.StatusCode(), ShouldEqual, 200)
  6439  
  6440  		responseStruct := &zcommon.GlobalSearchResultResp{}
  6441  
  6442  		err = json.Unmarshal(resp.Body(), responseStruct)
  6443  		So(err, ShouldBeNil)
  6444  
  6445  		So(len(responseStruct.Images), ShouldEqual, 1)
  6446  		So(responseStruct.Images[0].Manifests[0].Digest, ShouldResemble,
  6447  			imageWithIncompatibleConfig.ManifestDescriptor.Digest.String())
  6448  		So(responseStruct.Images[0].Manifests[0].ConfigDigest, ShouldResemble,
  6449  			imageWithIncompatibleConfig.ConfigDescriptor.Digest.String())
  6450  	})
  6451  }
  6452  
  6453  func TestReadUploadDeleteDynamoDB(t *testing.T) {
  6454  	tskip.SkipDynamo(t)
  6455  
  6456  	uuid, err := guuid.NewV4()
  6457  	if err != nil {
  6458  		panic(err)
  6459  	}
  6460  
  6461  	cacheTablename := "BlobTable" + uuid.String()
  6462  	repoMetaTablename := "RepoMetadataTable" + uuid.String()
  6463  	versionTablename := "Version" + uuid.String()
  6464  	userDataTablename := "UserDataTable" + uuid.String()
  6465  	apiKeyTablename := "ApiKeyTable" + uuid.String()
  6466  	imageMetaTablename := "ImageMeta" + uuid.String()
  6467  	repoBlobsTablename := "RepoBlobs" + uuid.String()
  6468  
  6469  	cacheDriverParams := map[string]interface{}{
  6470  		"name":                   "dynamoDB",
  6471  		"endpoint":               os.Getenv("DYNAMODBMOCK_ENDPOINT"),
  6472  		"region":                 "us-east-2",
  6473  		"cachetablename":         cacheTablename,
  6474  		"repometatablename":      repoMetaTablename,
  6475  		"imagemetatablename":     imageMetaTablename,
  6476  		"repoblobsinfotablename": repoBlobsTablename,
  6477  		"userdatatablename":      userDataTablename,
  6478  		"apikeytablename":        apiKeyTablename,
  6479  		"versiontablename":       versionTablename,
  6480  	}
  6481  
  6482  	port := GetFreePort()
  6483  	baseURL := GetBaseURL(port)
  6484  	conf := config.New()
  6485  	conf.HTTP.Port = port
  6486  	conf.Storage.RootDirectory = t.TempDir()
  6487  	conf.Storage.GC = false
  6488  	conf.Storage.CacheDriver = cacheDriverParams
  6489  	conf.Storage.RemoteCache = true
  6490  	conf.Log = &config.LogConfig{Level: "debug", Output: "/dev/null"}
  6491  
  6492  	defaultVal := true
  6493  	conf.Extensions = &extconf.ExtensionConfig{
  6494  		Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, CVE: nil},
  6495  	}
  6496  
  6497  	ctlr := api.NewController(conf)
  6498  	ctlrManager := NewControllerManager(ctlr)
  6499  
  6500  	ctlrManager.StartAndWait(port)
  6501  	defer ctlrManager.StopServer()
  6502  
  6503  	RunReadUploadDeleteTests(t, baseURL)
  6504  }
  6505  
  6506  func TestReadUploadDeleteBoltDB(t *testing.T) {
  6507  	port := GetFreePort()
  6508  	baseURL := GetBaseURL(port)
  6509  	conf := config.New()
  6510  	conf.HTTP.Port = port
  6511  	conf.Storage.RootDirectory = t.TempDir()
  6512  	conf.Storage.GC = false
  6513  	conf.Log = &config.LogConfig{Level: "debug", Output: "/dev/null"}
  6514  
  6515  	defaultVal := true
  6516  	conf.Extensions = &extconf.ExtensionConfig{
  6517  		Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, CVE: nil},
  6518  	}
  6519  
  6520  	ctlr := api.NewController(conf)
  6521  	ctlrManager := NewControllerManager(ctlr)
  6522  
  6523  	ctlrManager.StartAndWait(port)
  6524  	defer ctlrManager.StopServer()
  6525  
  6526  	RunReadUploadDeleteTests(t, baseURL)
  6527  }
  6528  
  6529  func RunReadUploadDeleteTests(t *testing.T, baseURL string) {
  6530  	t.Helper()
  6531  
  6532  	repo1 := "repo1"
  6533  	image := CreateRandomImage()
  6534  	tag1 := "tag1"
  6535  
  6536  	imageWithoutTag := CreateRandomImage()
  6537  
  6538  	usedImages := []repoRef{
  6539  		{repo1, tag1},
  6540  		{repo1, imageWithoutTag.DigestStr()},
  6541  	}
  6542  
  6543  	Convey("Push-Read-Delete", t, func() {
  6544  		results := GlobalSearchGQL("", baseURL)
  6545  		So(len(results.Images), ShouldEqual, 0)
  6546  		So(len(results.Repos), ShouldEqual, 0)
  6547  
  6548  		Convey("Push an image without tag", func() {
  6549  			err := UploadImage(imageWithoutTag, baseURL, repo1, imageWithoutTag.DigestStr())
  6550  			So(err, ShouldBeNil)
  6551  
  6552  			results := GlobalSearchGQL("", baseURL)
  6553  			So(len(results.Repos), ShouldEqual, 0)
  6554  
  6555  			Convey("Add tag and delete it", func() {
  6556  				err := UploadImage(image, baseURL, repo1, tag1)
  6557  				So(err, ShouldBeNil)
  6558  
  6559  				results := GlobalSearchGQL("", baseURL)
  6560  				So(len(results.Repos), ShouldEqual, 1)
  6561  
  6562  				status, err := DeleteImage(repo1, tag1, baseURL)
  6563  				So(status, ShouldEqual, http.StatusAccepted)
  6564  				So(err, ShouldBeNil)
  6565  
  6566  				results = GlobalSearchGQL("", baseURL)
  6567  				So(len(results.Repos), ShouldEqual, 0)
  6568  			})
  6569  		})
  6570  		Convey("Push a random image", func() {
  6571  			err := UploadImage(image, baseURL, repo1, tag1)
  6572  			So(err, ShouldBeNil)
  6573  
  6574  			results := GlobalSearchGQL("", baseURL)
  6575  			So(len(results.Repos), ShouldEqual, 1)
  6576  
  6577  			Convey("Delete the image pushed", func() {
  6578  				status, err := DeleteImage(repo1, tag1, baseURL)
  6579  				So(status, ShouldEqual, http.StatusAccepted)
  6580  				So(err, ShouldBeNil)
  6581  
  6582  				results := GlobalSearchGQL("", baseURL)
  6583  				So(len(results.Repos), ShouldEqual, 0)
  6584  
  6585  				Convey("Push an image without tag", func() {
  6586  					err := UploadImage(imageWithoutTag, baseURL, repo1, imageWithoutTag.DigestStr())
  6587  					So(err, ShouldBeNil)
  6588  
  6589  					results := GlobalSearchGQL("", baseURL)
  6590  					So(len(results.Repos), ShouldEqual, 0)
  6591  				})
  6592  			})
  6593  			Convey("Delete the image pushed multiple times", func() {
  6594  				for i := 0; i < 3; i++ {
  6595  					status, err := DeleteImage(repo1, tag1, baseURL)
  6596  					So(status, ShouldBeIn, []int{http.StatusAccepted, http.StatusNotFound, http.StatusBadRequest})
  6597  					So(err, ShouldBeNil)
  6598  
  6599  					results := GlobalSearchGQL("", baseURL)
  6600  					So(len(results.Repos), ShouldEqual, 0)
  6601  				}
  6602  			})
  6603  			Convey("Upload same image multiple times", func() {
  6604  				for i := 0; i < 3; i++ {
  6605  					err := UploadImage(image, baseURL, repo1, tag1)
  6606  					So(err, ShouldBeNil)
  6607  				}
  6608  
  6609  				results := GlobalSearchGQL("", baseURL)
  6610  				So(len(results.Repos), ShouldEqual, 1)
  6611  			})
  6612  		})
  6613  
  6614  		deleteUsedImages(usedImages, baseURL)
  6615  	})
  6616  
  6617  	// Images with create time
  6618  	repoLatest := "repo-latest"
  6619  
  6620  	afterImage := CreateImageWith().DefaultLayers().
  6621  		ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).Build()
  6622  	tagAfter := "after"
  6623  
  6624  	middleImage := CreateImageWith().DefaultLayers().
  6625  		ImageConfig(ispec.Image{Created: DateRef(2005, 1, 1, 1, 1, 1, 0, time.UTC)}).Build()
  6626  	tagMiddle := "middle"
  6627  
  6628  	beforeImage := CreateImageWith().DefaultLayers().
  6629  		ImageConfig(ispec.Image{Created: DateRef(2000, 1, 1, 1, 1, 1, 0, time.UTC)}).Build()
  6630  	tagBefore := "before"
  6631  
  6632  	imageWithoutTag = CreateImageWith().DefaultLayers().
  6633  		ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).Build()
  6634  
  6635  	imageWithoutCreateTime := CreateImageWith().DefaultLayers().
  6636  		ImageConfig(ispec.Image{Created: nil}).Build()
  6637  	tagWithoutTime := "without-time"
  6638  
  6639  	usedImages = []repoRef{
  6640  		{repoLatest, tagAfter},
  6641  		{repoLatest, tagMiddle},
  6642  		{repoLatest, tagBefore},
  6643  		{repoLatest, tagWithoutTime},
  6644  		{repoLatest, imageWithoutTag.DigestStr()},
  6645  	}
  6646  
  6647  	Convey("Last Updated Image", t, func() {
  6648  		results := GlobalSearchGQL("", baseURL)
  6649  		So(len(results.Images), ShouldEqual, 0)
  6650  		So(len(results.Repos), ShouldEqual, 0)
  6651  
  6652  		Convey("Without time", func() {
  6653  			err := UploadImage(imageWithoutCreateTime, baseURL, repoLatest, tagWithoutTime)
  6654  			So(err, ShouldBeNil)
  6655  
  6656  			results := GlobalSearchGQL("", baseURL)
  6657  			So(len(results.Repos), ShouldEqual, 1)
  6658  			So(results.Repos[0].NewestImage.Digest, ShouldResemble, imageWithoutCreateTime.DigestStr())
  6659  
  6660  			Convey("Add an image with create time and delete it", func() {
  6661  				err := UploadImage(beforeImage, baseURL, repoLatest, tagBefore)
  6662  				So(err, ShouldBeNil)
  6663  
  6664  				results := GlobalSearchGQL("", baseURL)
  6665  				So(len(results.Repos), ShouldEqual, 1)
  6666  				So(results.Repos[0].NewestImage.Digest, ShouldResemble, beforeImage.DigestStr())
  6667  
  6668  				status, err := DeleteImage(repoLatest, tagBefore, baseURL)
  6669  				So(status, ShouldEqual, http.StatusAccepted)
  6670  				So(err, ShouldBeNil)
  6671  
  6672  				results = GlobalSearchGQL("", baseURL)
  6673  				So(len(results.Repos), ShouldEqual, 1)
  6674  				So(results.Repos[0].NewestImage.Digest, ShouldResemble, imageWithoutCreateTime.DigestStr())
  6675  			})
  6676  		})
  6677  		Convey("Upload middle image", func() {
  6678  			err := UploadImage(middleImage, baseURL, repoLatest, tagMiddle)
  6679  			So(err, ShouldBeNil)
  6680  
  6681  			results := GlobalSearchGQL("", baseURL)
  6682  			So(len(results.Repos), ShouldEqual, 1)
  6683  			So(results.Repos[0].NewestImage.Digest, ShouldResemble, middleImage.DigestStr())
  6684  
  6685  			Convey("Upload an image created before", func() {
  6686  				err := UploadImage(beforeImage, baseURL, repoLatest, tagBefore)
  6687  				So(err, ShouldBeNil)
  6688  
  6689  				results := GlobalSearchGQL("", baseURL)
  6690  				So(len(results.Repos), ShouldEqual, 1)
  6691  				So(results.Repos[0].NewestImage.Digest, ShouldResemble, middleImage.DigestStr())
  6692  
  6693  				Convey("Upload an image created after", func() {
  6694  					err := UploadImage(afterImage, baseURL, repoLatest, tagAfter)
  6695  					So(err, ShouldBeNil)
  6696  
  6697  					results := GlobalSearchGQL("", baseURL)
  6698  					So(len(results.Repos), ShouldEqual, 1)
  6699  					So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr())
  6700  
  6701  					Convey("Delete middle then after", func() {
  6702  						status, err := DeleteImage(repoLatest, tagMiddle, baseURL)
  6703  						So(status, ShouldEqual, http.StatusAccepted)
  6704  						So(err, ShouldBeNil)
  6705  
  6706  						results := GlobalSearchGQL("", baseURL)
  6707  						So(len(results.Repos), ShouldEqual, 1)
  6708  						So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr())
  6709  
  6710  						status, err = DeleteImage(repoLatest, tagAfter, baseURL)
  6711  						So(status, ShouldEqual, http.StatusAccepted)
  6712  						So(err, ShouldBeNil)
  6713  
  6714  						results = GlobalSearchGQL("", baseURL)
  6715  						So(len(results.Repos), ShouldEqual, 1)
  6716  						So(results.Repos[0].NewestImage.Digest, ShouldResemble, beforeImage.DigestStr())
  6717  					})
  6718  				})
  6719  			})
  6720  			Convey("Upload an image created after", func() {
  6721  				err := UploadImage(afterImage, baseURL, repoLatest, tagAfter)
  6722  				So(err, ShouldBeNil)
  6723  
  6724  				results := GlobalSearchGQL("", baseURL)
  6725  				So(len(results.Repos), ShouldEqual, 1)
  6726  				So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr())
  6727  
  6728  				Convey("Add newer image without tag", func() {
  6729  					err := UploadImage(imageWithoutTag, baseURL, repoLatest, imageWithoutTag.DigestStr())
  6730  					So(err, ShouldBeNil)
  6731  
  6732  					results := GlobalSearchGQL("", baseURL)
  6733  					So(len(results.Repos), ShouldEqual, 1)
  6734  					So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr())
  6735  				})
  6736  
  6737  				Convey("Delete afterImage", func() {
  6738  					status, err := DeleteImage(repoLatest, tagAfter, baseURL)
  6739  					So(status, ShouldEqual, http.StatusAccepted)
  6740  					So(err, ShouldBeNil)
  6741  
  6742  					results := GlobalSearchGQL("", baseURL)
  6743  					So(len(results.Repos), ShouldEqual, 1)
  6744  					So(results.Repos[0].NewestImage.Digest, ShouldResemble, middleImage.DigestStr())
  6745  				})
  6746  			})
  6747  		})
  6748  
  6749  		deleteUsedImages(usedImages, baseURL)
  6750  	})
  6751  }
  6752  
  6753  type repoRef struct {
  6754  	Repo string
  6755  	Tag  string
  6756  }
  6757  
  6758  func deleteUsedImages(repoTags []repoRef, baseURL string) {
  6759  	for _, image := range repoTags {
  6760  		status, err := DeleteImage(image.Repo, image.Tag, baseURL)
  6761  		So(status, ShouldBeIn, []int{http.StatusAccepted, http.StatusNotFound, http.StatusBadRequest})
  6762  		So(err, ShouldBeNil)
  6763  	}
  6764  }
  6765  
  6766  func GlobalSearchGQL(query, baseURL string) *zcommon.GlobalSearchResultResp {
  6767  	queryStr := `
  6768  	{
  6769  		GlobalSearch(query:"` + query + `"){
  6770  			Images {
  6771  				RepoName Tag Digest MediaType Size DownloadCount LastUpdated IsSigned
  6772  				Description Licenses Labels Title Source Documentation Authors Vendor
  6773  				Manifests {
  6774  					Digest ConfigDigest LastUpdated Size IsSigned
  6775  					DownloadCount
  6776  					SignatureInfo {Tool IsTrusted Author}
  6777  					Platform {Os Arch}
  6778  					Layers {Size Digest}
  6779  					History {
  6780  						Layer { Size Digest }
  6781  						HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  6782  					}
  6783  					Vulnerabilities {Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity}
  6784  					Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}}
  6785  				}
  6786  				Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}}
  6787  				Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  6788  				SignatureInfo {Tool IsTrusted Author}
  6789  			}
  6790  			Repos {
  6791  				Name LastUpdated Size DownloadCount StarCount IsBookmarked IsStarred
  6792  				Platforms { Os Arch }
  6793  				Vendors
  6794  				NewestImage {
  6795  					RepoName Tag Digest MediaType Size DownloadCount LastUpdated IsSigned
  6796  					Description Licenses Labels Title Source Documentation Authors Vendor
  6797  					Manifests {
  6798  						Digest ConfigDigest LastUpdated Size IsSigned
  6799  						DownloadCount
  6800  						SignatureInfo {Tool IsTrusted Author}
  6801  						Platform {Os Arch}
  6802  						Layers {Size Digest}
  6803  						History {
  6804  							Layer { Size Digest }
  6805  							HistoryDescription { Author Comment Created CreatedBy EmptyLayer }
  6806  						}
  6807  						Vulnerabilities {Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity}
  6808  						Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}}
  6809  					}
  6810  					Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}}
  6811  					Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity }
  6812  					SignatureInfo {Tool IsTrusted Author}
  6813  				}
  6814  			}
  6815  		}
  6816  	}`
  6817  
  6818  	resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryStr))
  6819  	So(resp, ShouldNotBeNil)
  6820  	So(err, ShouldBeNil)
  6821  	So(resp.StatusCode(), ShouldEqual, 200)
  6822  
  6823  	responseStruct := &zcommon.GlobalSearchResultResp{}
  6824  
  6825  	err = json.Unmarshal(resp.Body(), responseStruct)
  6826  	So(err, ShouldBeNil)
  6827  
  6828  	return responseStruct
  6829  }