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