mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 04:17:55 +08:00
freeform querry api
Signed-off-by: Laurentiu Niculae <themelopeus@gmail.com>
This commit is contained in:
committed by
Ramkumar Chinchani
parent
a31869f270
commit
7e3d063319
@@ -6,8 +6,10 @@ package common_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -30,12 +32,15 @@ import (
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
. "zotregistry.io/zot/pkg/test"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
const (
|
||||
graphqlQueryPrefix = constants.ExtSearchPrefix
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("test error")
|
||||
|
||||
// nolint:gochecknoglobals
|
||||
var (
|
||||
rootDir string
|
||||
@@ -52,6 +57,50 @@ type ExpandedRepoInfoResp struct {
|
||||
Errors []ErrorGQL `json:"errors"`
|
||||
}
|
||||
|
||||
type GlobalSearchResultResp struct {
|
||||
GlobalSearchResult GlobalSearchResult `json:"data"`
|
||||
Errors []ErrorGQL `json:"errors"`
|
||||
}
|
||||
|
||||
type GlobalSearchResult struct {
|
||||
GlobalSearch GlobalSearch `json:"globalSearch"`
|
||||
}
|
||||
type GlobalSearch struct {
|
||||
Images []ImageSummary `json:"images"`
|
||||
Repos []RepoSummary `json:"repos"`
|
||||
Layers []LayerSummary `json:"layers"`
|
||||
}
|
||||
|
||||
type ImageSummary struct {
|
||||
RepoName string `json:"repoName"`
|
||||
Tag string `json:"tag"`
|
||||
LastUpdated time.Time `json:"lastUpdated"`
|
||||
Size string `json:"size"`
|
||||
Platform OsArch `json:"platform"`
|
||||
Vendor string `json:"vendor"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
type RepoSummary struct {
|
||||
Name string `json:"name"`
|
||||
LastUpdated time.Time `json:"lastUpdated"`
|
||||
Size string `json:"size"`
|
||||
Platforms []OsArch `json:"platforms"`
|
||||
Vendors []string `json:"vendors"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
type LayerSummary struct {
|
||||
Size string `json:"size"`
|
||||
Digest string `json:"digest"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
type OsArch struct {
|
||||
Os string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
}
|
||||
|
||||
type ExpandedRepoInfo struct {
|
||||
RepoInfo common.RepoInfo `json:"expandedRepoInfo"`
|
||||
}
|
||||
@@ -210,7 +259,7 @@ func TestImageFormat(t *testing.T) {
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
defaultStore := storage.NewImageStore(dbDir, false, storage.DefaultGCDelay, false, false, log, metrics)
|
||||
storeController := storage.StoreController{DefaultStore: defaultStore}
|
||||
olu := common.NewOciLayoutUtils(storeController, log)
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log)
|
||||
|
||||
isValidImage, err := olu.IsValidImageFormat("zot-test")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -671,3 +720,404 @@ func TestUtilsMethod(t *testing.T) {
|
||||
So(dir, ShouldEqual, subRootDir)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGlobalSearch(t *testing.T) {
|
||||
Convey("Test utils", t, func() {
|
||||
subpath := "/a"
|
||||
|
||||
err := testSetup(t, subpath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.RootDirectory = rootDir
|
||||
conf.Storage.SubPaths = make(map[string]config.StorageConfig)
|
||||
conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
|
||||
defaultVal := true
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{Enable: &defaultVal},
|
||||
}
|
||||
|
||||
conf.Extensions.Search.CVE = nil
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
go func() {
|
||||
// this blocks
|
||||
if err := ctlr.Run(context.Background()); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait till ready
|
||||
for {
|
||||
_, err := resty.R().Get(baseURL)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// shut down server
|
||||
|
||||
defer func() {
|
||||
ctx := context.Background()
|
||||
_ = ctlr.Server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
query := `
|
||||
{
|
||||
GlobalSearch(query:""){
|
||||
Images {
|
||||
RepoName
|
||||
Tag
|
||||
LastUpdated
|
||||
Size
|
||||
Score
|
||||
}
|
||||
Repos {
|
||||
Name
|
||||
LastUpdated
|
||||
Size
|
||||
Platforms {
|
||||
Os
|
||||
Arch
|
||||
}
|
||||
Vendors
|
||||
Score
|
||||
}
|
||||
Layers {
|
||||
Digest
|
||||
Size
|
||||
}
|
||||
}
|
||||
}`
|
||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct := &GlobalSearchResultResp{}
|
||||
|
||||
err = json.Unmarshal(resp.Body(), responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(responseStruct.GlobalSearchResult.GlobalSearch.Images, ShouldNotBeNil)
|
||||
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Images), ShouldNotBeEmpty)
|
||||
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Repos), ShouldNotBeEmpty)
|
||||
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Layers), ShouldNotBeEmpty)
|
||||
|
||||
// GetRepositories fail
|
||||
|
||||
err = os.Chmod(rootDir, 0o333)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
responseStruct = &GlobalSearchResultResp{}
|
||||
err = json.Unmarshal(resp.Body(), responseStruct)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(responseStruct.Errors, ShouldNotBeEmpty)
|
||||
err = os.Chmod(rootDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
manifestDigest := "sha256:adf3bb6cc81f8bd6a9d5233be5f0c1a4f1e3ed1cf5bbdfad7708cc8d4099b741"
|
||||
|
||||
Convey("GetImageManifestSize fail", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
size := olu.GetImageManifestSize("", "")
|
||||
So(size, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImageConfigSize: fail GetImageBlobManifest", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
size := olu.GetImageConfigSize("", "")
|
||||
So(size, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImageConfigSize: config GetBlobContent fail", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
if digest == manifestDigest {
|
||||
return []byte{}, ErrTestError
|
||||
}
|
||||
|
||||
return []byte(
|
||||
`
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.oci.image.config.v1+json",
|
||||
"digest": manifestDigest,
|
||||
"size": 1476
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest": "sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc",
|
||||
"size": 76097157
|
||||
}
|
||||
]
|
||||
}`), nil
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
size := olu.GetImageConfigSize("", "")
|
||||
So(size, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetRepoLastUpdated: config GetBlobContent fail", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetIndexContentFn: func(repo string) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err := olu.GetRepoLastUpdated("")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("GetImageLastUpdated: GetImageBlobManifest fails", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
time := olu.GetImageLastUpdated("", "")
|
||||
So(time, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImageLastUpdated: GetImageInfo fails", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
if digest == manifestDigest {
|
||||
return []byte{}, ErrTestError
|
||||
}
|
||||
|
||||
return []byte(
|
||||
`
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.oci.image.config.v1+json",
|
||||
"digest": manifestDigest,
|
||||
"size": 1476
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest": "sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc",
|
||||
"size": 76097157
|
||||
}
|
||||
]
|
||||
}`), nil
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
time := olu.GetImageLastUpdated("", "")
|
||||
So(time, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImageLastUpdated: GetImageInfo history is empty", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
if digest == manifestDigest {
|
||||
return []byte(
|
||||
`
|
||||
{
|
||||
"created": "2020-11-14T00:20:04.644613188Z",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"config": {
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/bash"
|
||||
],
|
||||
"Labels": {
|
||||
}
|
||||
},
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02"
|
||||
]
|
||||
},
|
||||
"history": [
|
||||
]
|
||||
}
|
||||
`), nil
|
||||
}
|
||||
|
||||
return []byte(
|
||||
`
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.oci.image.config.v1+json",
|
||||
"digest": manifestDigest,
|
||||
"size": 1476
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest": "sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc",
|
||||
"size": 76097157
|
||||
}
|
||||
]
|
||||
}`), nil
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
time := olu.GetImageLastUpdated("", "")
|
||||
So(time, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImagePlatform: GetImageBlobManifest fails", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
os, arch := olu.GetImagePlatform("", "")
|
||||
So(os, ShouldBeZeroValue)
|
||||
So(arch, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImagePlatform: GetImageInfo fails", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
if digest == manifestDigest {
|
||||
return []byte{}, ErrTestError
|
||||
}
|
||||
|
||||
return []byte(
|
||||
`
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.oci.image.config.v1+json",
|
||||
"digest": manifestDigest,
|
||||
"size": 1476
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest": "sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc",
|
||||
"size": 76097157
|
||||
}
|
||||
]
|
||||
}`), nil
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
os, arch := olu.GetImagePlatform("", "")
|
||||
So(os, ShouldBeZeroValue)
|
||||
So(arch, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImageVendor: GetImageBlobManifest fails", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
vendor := olu.GetImageVendor("", "")
|
||||
So(vendor, ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("GetImageVendor: GetImageInfo fails", t, func() {
|
||||
mockStoreController := mocks.MockedImageStore{
|
||||
GetBlobContentFn: func(repo, digest string) ([]byte, error) {
|
||||
if digest == manifestDigest {
|
||||
return []byte{}, ErrTestError
|
||||
}
|
||||
|
||||
return []byte(
|
||||
`
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"config": {
|
||||
"mediaType": "application/vnd.oci.image.config.v1+json",
|
||||
"digest": manifestDigest,
|
||||
"size": 1476
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest": "sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc",
|
||||
"size": 76097157
|
||||
}
|
||||
]
|
||||
}`), nil
|
||||
},
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
vendor := olu.GetImageVendor("", "")
|
||||
So(vendor, ShouldBeZeroValue)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,8 +20,23 @@ import (
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
)
|
||||
|
||||
type OciLayoutUtils interface {
|
||||
GetImageManifests(image string) ([]ispec.Descriptor, error)
|
||||
GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error)
|
||||
GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error)
|
||||
IsValidImageFormat(image string) (bool, error)
|
||||
GetImageTagsWithTimestamp(repo string) ([]TagInfo, error)
|
||||
GetImageLastUpdated(repo string, manifestDigest godigest.Digest) time.Time
|
||||
GetImagePlatform(repo string, manifestDigest godigest.Digest) (string, string)
|
||||
GetImageVendor(repo string, manifestDigest godigest.Digest) string
|
||||
GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64
|
||||
GetImageConfigSize(repo string, manifestDigest godigest.Digest) int64
|
||||
GetRepoLastUpdated(repo string) (time.Time, error)
|
||||
GetExpandedRepoInfo(name string) (RepoInfo, error)
|
||||
}
|
||||
|
||||
// OciLayoutInfo ...
|
||||
type OciLayoutUtils struct {
|
||||
type BaseOciLayoutUtils struct {
|
||||
Log log.Logger
|
||||
StoreController storage.StoreController
|
||||
}
|
||||
@@ -42,13 +57,13 @@ type Layer struct {
|
||||
Digest string `json:"digest"`
|
||||
}
|
||||
|
||||
// NewOciLayoutUtils initializes a new OciLayoutUtils object.
|
||||
func NewOciLayoutUtils(storeController storage.StoreController, log log.Logger) *OciLayoutUtils {
|
||||
return &OciLayoutUtils{Log: log, StoreController: storeController}
|
||||
// NewBaseOciLayoutUtils initializes a new OciLayoutUtils object.
|
||||
func NewBaseOciLayoutUtils(storeController storage.StoreController, log log.Logger) *BaseOciLayoutUtils {
|
||||
return &BaseOciLayoutUtils{Log: log, StoreController: storeController}
|
||||
}
|
||||
|
||||
// Below method will return image path including root dir, root dir is determined by splitting.
|
||||
func (olu OciLayoutUtils) GetImageManifests(image string) ([]ispec.Descriptor, error) {
|
||||
func (olu BaseOciLayoutUtils) GetImageManifests(image string) ([]ispec.Descriptor, error) {
|
||||
imageStore := olu.StoreController.GetImageStore(image)
|
||||
|
||||
buf, err := imageStore.GetIndexContent(image)
|
||||
@@ -76,7 +91,7 @@ func (olu OciLayoutUtils) GetImageManifests(image string) ([]ispec.Descriptor, e
|
||||
}
|
||||
|
||||
//nolint: interfacer
|
||||
func (olu OciLayoutUtils) GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error) {
|
||||
func (olu BaseOciLayoutUtils) GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error) {
|
||||
var blobIndex v1.Manifest
|
||||
|
||||
imageStore := olu.StoreController.GetImageStore(imageDir)
|
||||
@@ -98,7 +113,7 @@ func (olu OciLayoutUtils) GetImageBlobManifest(imageDir string, digest godigest.
|
||||
}
|
||||
|
||||
//nolint: interfacer
|
||||
func (olu OciLayoutUtils) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error) {
|
||||
func (olu BaseOciLayoutUtils) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error) {
|
||||
var imageInfo ispec.Image
|
||||
|
||||
imageStore := olu.StoreController.GetImageStore(imageDir)
|
||||
@@ -119,7 +134,7 @@ func (olu OciLayoutUtils) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Ima
|
||||
return imageInfo, err
|
||||
}
|
||||
|
||||
func (olu OciLayoutUtils) IsValidImageFormat(image string) (bool, error) {
|
||||
func (olu BaseOciLayoutUtils) IsValidImageFormat(image string) (bool, error) {
|
||||
imageDir, inputTag := GetImageDirAndTag(image)
|
||||
|
||||
manifests, err := olu.GetImageManifests(imageDir)
|
||||
@@ -158,7 +173,7 @@ func (olu OciLayoutUtils) IsValidImageFormat(image string) (bool, error) {
|
||||
}
|
||||
|
||||
// GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository.
|
||||
func (olu OciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, error) {
|
||||
func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, error) {
|
||||
tagsInfo := make([]TagInfo, 0)
|
||||
|
||||
manifests, err := olu.GetImageManifests(repo)
|
||||
@@ -203,7 +218,7 @@ func (olu OciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, err
|
||||
}
|
||||
|
||||
// check notary signature corresponding to repo name, manifest digest and mediatype.
|
||||
func (olu OciLayoutUtils) checkNotarySignature(name string, digest godigest.Digest) bool {
|
||||
func (olu BaseOciLayoutUtils) checkNotarySignature(name string, digest godigest.Digest) bool {
|
||||
imageStore := olu.StoreController.GetImageStore(name)
|
||||
mediaType := notreg.ArtifactTypeNotation
|
||||
|
||||
@@ -219,7 +234,7 @@ func (olu OciLayoutUtils) checkNotarySignature(name string, digest godigest.Dige
|
||||
}
|
||||
|
||||
// check cosign signature corresponding to manifest.
|
||||
func (olu OciLayoutUtils) checkCosignSignature(name string, digest godigest.Digest) bool {
|
||||
func (olu BaseOciLayoutUtils) checkCosignSignature(name string, digest godigest.Digest) bool {
|
||||
imageStore := olu.StoreController.GetImageStore(name)
|
||||
|
||||
// if manifest is signed using cosign mechanism, cosign adds a new manifest.
|
||||
@@ -240,7 +255,7 @@ func (olu OciLayoutUtils) checkCosignSignature(name string, digest godigest.Dige
|
||||
// checks if manifest is signed or not
|
||||
// checks for notary or cosign signature
|
||||
// if cosign signature found it does not looks for notary signature.
|
||||
func (olu OciLayoutUtils) checkManifestSignature(name string, digest godigest.Digest) bool {
|
||||
func (olu BaseOciLayoutUtils) checkManifestSignature(name string, digest godigest.Digest) bool {
|
||||
if !olu.checkCosignSignature(name, digest) {
|
||||
return olu.checkNotarySignature(name, digest)
|
||||
}
|
||||
@@ -248,7 +263,112 @@ func (olu OciLayoutUtils) checkManifestSignature(name string, digest godigest.Di
|
||||
return true
|
||||
}
|
||||
|
||||
func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
|
||||
func (olu BaseOciLayoutUtils) GetImageLastUpdated(repo string, manifestDigest godigest.Digest) time.Time {
|
||||
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("unable to read image blob")
|
||||
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
imageInfo, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("unable to read image info")
|
||||
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
var timeStamp time.Time
|
||||
|
||||
if len(imageInfo.History) != 0 {
|
||||
timeStamp = *imageInfo.History[0].Created
|
||||
} else {
|
||||
timeStamp = time.Time{}
|
||||
}
|
||||
|
||||
return timeStamp
|
||||
}
|
||||
|
||||
func (olu BaseOciLayoutUtils) GetImagePlatform(repo string, manifestDigest godigest.Digest) (
|
||||
string, string,
|
||||
) {
|
||||
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
imageConfig, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("extension api: error reading image config")
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return imageConfig.OS, imageConfig.Architecture
|
||||
}
|
||||
|
||||
func (olu BaseOciLayoutUtils) GetImageVendor(repo string, manifestDigest godigest.Digest) string {
|
||||
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
imageConfig, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("extension api: error reading image config")
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return imageConfig.Config.Labels["vendor"]
|
||||
}
|
||||
|
||||
func (olu BaseOciLayoutUtils) GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64 {
|
||||
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
return imageBlobManifest.Config.Size
|
||||
}
|
||||
|
||||
func (olu BaseOciLayoutUtils) GetImageConfigSize(repo string, manifestDigest godigest.Digest) int64 {
|
||||
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
||||
|
||||
return 0
|
||||
}
|
||||
imageStore := olu.StoreController.GetImageStore(repo)
|
||||
|
||||
buf, err := imageStore.GetBlobContent(repo, imageBlobManifest.Config.Digest.String())
|
||||
if err != nil {
|
||||
olu.Log.Error().Err(err).Msg("error when getting blob content")
|
||||
|
||||
return int64(len(buf))
|
||||
}
|
||||
|
||||
return int64(len(buf))
|
||||
}
|
||||
|
||||
func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (time.Time, error) {
|
||||
tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
|
||||
if err != nil || len(tagsInfo) == 0 {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
latestTag := GetLatestTag(tagsInfo)
|
||||
|
||||
return latestTag.Timestamp, nil
|
||||
}
|
||||
|
||||
func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
|
||||
repo := RepoInfo{}
|
||||
|
||||
manifests := make([]Manifest, 0)
|
||||
|
||||
@@ -78,7 +78,7 @@ func ScanImage(ctx *cli.Context) (report.Report, error) {
|
||||
|
||||
func GetCVEInfo(storeController storage.StoreController, log log.Logger) (*CveInfo, error) {
|
||||
cveController := CveTrivyController{}
|
||||
layoutUtils := common.NewOciLayoutUtils(storeController, log)
|
||||
layoutUtils := common.NewBaseOciLayoutUtils(storeController, log)
|
||||
|
||||
subCveConfig := make(map[string]*TrivyCtx)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func testSetup() error {
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: storage.NewImageStore(dir, false, storage.DefaultGCDelay, false, false, log, metrics)}
|
||||
|
||||
layoutUtils := common.NewOciLayoutUtils(storeController, log)
|
||||
layoutUtils := common.NewBaseOciLayoutUtils(storeController, log)
|
||||
|
||||
cve = &cveinfo.CveInfo{Log: log, StoreController: storeController, LayoutUtils: layoutUtils}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ type CveInfo struct {
|
||||
Log log.Logger
|
||||
CveTrivyController CveTrivyController
|
||||
StoreController storage.StoreController
|
||||
LayoutUtils *common.OciLayoutUtils
|
||||
LayoutUtils *common.BaseOciLayoutUtils
|
||||
}
|
||||
|
||||
type CveTrivyController struct {
|
||||
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
// DigestInfo implements searching by manifes/config/layer digest.
|
||||
type DigestInfo struct {
|
||||
Log log.Logger
|
||||
LayoutUtils *common.OciLayoutUtils
|
||||
LayoutUtils *common.BaseOciLayoutUtils
|
||||
}
|
||||
|
||||
// NewDigestInfo initializes a new DigestInfo object.
|
||||
func NewDigestInfo(storeController storage.StoreController, log log.Logger) *DigestInfo {
|
||||
layoutUtils := common.NewOciLayoutUtils(storeController, log)
|
||||
layoutUtils := common.NewBaseOciLayoutUtils(storeController, log)
|
||||
|
||||
return &DigestInfo{Log: log, LayoutUtils: layoutUtils}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,12 @@ type CVEResultForImage struct {
|
||||
CVEList []*Cve `json:"CVEList"`
|
||||
}
|
||||
|
||||
type GlobalSearchResult struct {
|
||||
Images []*ImageSummary `json:"Images"`
|
||||
Repos []*RepoSummary `json:"Repos"`
|
||||
Layers []*LayerSummary `json:"Layers"`
|
||||
}
|
||||
|
||||
type ImageInfo struct {
|
||||
Name *string `json:"Name"`
|
||||
Latest *string `json:"Latest"`
|
||||
@@ -30,6 +36,17 @@ type ImageInfo struct {
|
||||
Labels *string `json:"Labels"`
|
||||
}
|
||||
|
||||
type ImageSummary struct {
|
||||
RepoName *string `json:"RepoName"`
|
||||
Tag *string `json:"Tag"`
|
||||
LastUpdated *time.Time `json:"LastUpdated"`
|
||||
IsSigned *bool `json:"IsSigned"`
|
||||
Size *string `json:"Size"`
|
||||
Platform *OsArch `json:"Platform"`
|
||||
Vendor *string `json:"Vendor"`
|
||||
Score *int `json:"Score"`
|
||||
}
|
||||
|
||||
type ImgResultForCve struct {
|
||||
Name *string `json:"Name"`
|
||||
Tags []*string `json:"Tags"`
|
||||
@@ -49,6 +66,12 @@ type LayerInfo struct {
|
||||
Digest *string `json:"Digest"`
|
||||
}
|
||||
|
||||
type LayerSummary struct {
|
||||
Size *string `json:"Size"`
|
||||
Digest *string `json:"Digest"`
|
||||
Score *int `json:"Score"`
|
||||
}
|
||||
|
||||
type ManifestInfo struct {
|
||||
Digest *string `json:"Digest"`
|
||||
Tag *string `json:"Tag"`
|
||||
@@ -56,6 +79,11 @@ type ManifestInfo struct {
|
||||
Layers []*LayerInfo `json:"Layers"`
|
||||
}
|
||||
|
||||
type OsArch struct {
|
||||
Os *string `json:"Os"`
|
||||
Arch *string `json:"Arch"`
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
Name *string `json:"Name"`
|
||||
InstalledVersion *string `json:"InstalledVersion"`
|
||||
@@ -66,6 +94,15 @@ type RepoInfo struct {
|
||||
Manifests []*ManifestInfo `json:"Manifests"`
|
||||
}
|
||||
|
||||
type RepoSummary struct {
|
||||
Name *string `json:"Name"`
|
||||
LastUpdated *time.Time `json:"LastUpdated"`
|
||||
Size *string `json:"Size"`
|
||||
Platforms []*OsArch `json:"Platforms"`
|
||||
Vendors []*string `json:"Vendors"`
|
||||
Score *int `json:"Score"`
|
||||
}
|
||||
|
||||
type TagInfo struct {
|
||||
Name *string `json:"Name"`
|
||||
Digest *string `json:"Digest"`
|
||||
|
||||
@@ -5,7 +5,9 @@ package search
|
||||
// It serves as dependency injection for your app, add any dependencies you require here.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"zotregistry.io/zot/pkg/log" // nolint: gci
|
||||
@@ -123,7 +125,7 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*
|
||||
r.log.Info().Msg("no repositories found")
|
||||
}
|
||||
|
||||
layoutUtils := common.NewOciLayoutUtils(r.storeController, r.log)
|
||||
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
||||
|
||||
for _, repo := range repoList {
|
||||
tagsInfo, err := layoutUtils.GetImageTagsWithTimestamp(repo)
|
||||
@@ -186,6 +188,180 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func cleanQuerry(query string) string {
|
||||
query = strings.ToLower(query)
|
||||
query = strings.Replace(query, ":", " ", 1)
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils, log log.Logger) (
|
||||
[]*gql_generated.RepoSummary, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary,
|
||||
) {
|
||||
repos := []*gql_generated.RepoSummary{}
|
||||
images := []*gql_generated.ImageSummary{}
|
||||
layers := []*gql_generated.LayerSummary{}
|
||||
|
||||
for _, repo := range repoList {
|
||||
repo := repo
|
||||
|
||||
// map used for dedube if 2 images reference the same blob
|
||||
repoLayerBlob2Size := make(map[string]int64, 10)
|
||||
|
||||
// made up of all manifests, configs and image layers
|
||||
repoSize := int64(0)
|
||||
|
||||
lastUpdate, err := olu.GetRepoLastUpdated(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("can't find latest update timestamp for repo: %s", repo)
|
||||
}
|
||||
|
||||
tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("can't get tags info for repo: %s", repo)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
repoInfo, err := olu.GetExpandedRepoInfo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("can't get repo info for repo: %s", repo)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
repoPlatforms := make([]*gql_generated.OsArch, 0, len(tagsInfo))
|
||||
repoVendors := make([]*string, 0, len(repoInfo.Manifests))
|
||||
|
||||
for i, manifest := range repoInfo.Manifests {
|
||||
imageLayersSize := int64(0)
|
||||
manifestSize := olu.GetImageManifestSize(repo, godigest.Digest(tagsInfo[i].Digest))
|
||||
configSize := olu.GetImageConfigSize(repo, godigest.Digest(tagsInfo[i].Digest))
|
||||
|
||||
for _, layer := range manifest.Layers {
|
||||
layer := layer
|
||||
|
||||
layerSize, err := strconv.ParseInt(layer.Size, 10, 64)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("invalid layer size")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
repoLayerBlob2Size[layer.Digest] = layerSize
|
||||
imageLayersSize += layerSize
|
||||
|
||||
// if we have a tag we won't match a layer
|
||||
if tag != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if index := strings.Index(layer.Digest, name); index != -1 {
|
||||
layers = append(layers, &gql_generated.LayerSummary{
|
||||
Digest: &layer.Digest,
|
||||
Size: &layer.Size,
|
||||
Score: &index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
imageSize := imageLayersSize + manifestSize + configSize
|
||||
repoSize += manifestSize + configSize
|
||||
|
||||
index := strings.Index(repo, name)
|
||||
matchesTag := strings.HasPrefix(manifest.Tag, tag)
|
||||
|
||||
if index != -1 {
|
||||
tag := manifest.Tag
|
||||
size := strconv.Itoa(int(imageSize))
|
||||
vendor := olu.GetImageVendor(repo, godigest.Digest(tagsInfo[i].Digest))
|
||||
lastUpdated := olu.GetImageLastUpdated(repo, godigest.Digest(tagsInfo[i].Digest))
|
||||
|
||||
isSigned := manifest.IsSigned
|
||||
// update matching score
|
||||
score := calculateImageMatchingScore(repo, index, matchesTag)
|
||||
|
||||
os, arch := olu.GetImagePlatform(repo, godigest.Digest(tagsInfo[i].Digest))
|
||||
osArch := &gql_generated.OsArch{
|
||||
Os: &os,
|
||||
Arch: &arch,
|
||||
}
|
||||
|
||||
repoPlatforms = append(repoPlatforms, osArch)
|
||||
repoVendors = append(repoVendors, &vendor)
|
||||
|
||||
images = append(images, &gql_generated.ImageSummary{
|
||||
RepoName: &repo,
|
||||
Tag: &tag,
|
||||
LastUpdated: &lastUpdated,
|
||||
IsSigned: &isSigned,
|
||||
Size: &size,
|
||||
Platform: osArch,
|
||||
Vendor: &vendor,
|
||||
Score: &score,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for layerBlob := range repoLayerBlob2Size {
|
||||
repoSize += repoLayerBlob2Size[layerBlob]
|
||||
}
|
||||
|
||||
if index := strings.Index(repo, name); index != -1 {
|
||||
repoSize := strconv.FormatInt(repoSize, 10)
|
||||
|
||||
repos = append(repos, &gql_generated.RepoSummary{
|
||||
Name: &repo,
|
||||
LastUpdated: &lastUpdate,
|
||||
Size: &repoSize,
|
||||
Platforms: repoPlatforms,
|
||||
Vendors: repoVendors,
|
||||
Score: &index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(repos, func(i, j int) bool {
|
||||
return *repos[i].Score < *repos[j].Score
|
||||
})
|
||||
|
||||
sort.Slice(images, func(i, j int) bool {
|
||||
return *images[i].Score < *images[j].Score
|
||||
})
|
||||
|
||||
sort.Slice(layers, func(i, j int) bool {
|
||||
return *layers[i].Score < *layers[j].Score
|
||||
})
|
||||
|
||||
return repos, images, layers
|
||||
}
|
||||
|
||||
// calcalculateImageMatchingScore iterated from the index of the matched string in the
|
||||
// artifact name until the beginning of the string or until delimitator "/".
|
||||
// The distance represents the score of the match.
|
||||
//
|
||||
// Example:
|
||||
// query: image
|
||||
// repos: repo/test/myimage
|
||||
// Score will be 2.
|
||||
func calculateImageMatchingScore(artefactName string, index int, matchesTag bool) int {
|
||||
score := 0
|
||||
|
||||
for index >= 1 {
|
||||
if artefactName[index-1] == '/' {
|
||||
break
|
||||
}
|
||||
index--
|
||||
score++
|
||||
}
|
||||
|
||||
if !matchesTag {
|
||||
score += 10
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func getGraphqlCompatibleTags(fixedTags []common.TagInfo) []*gql_generated.TagInfo {
|
||||
finalTagList := make([]*gql_generated.TagInfo, 0)
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package search //nolint
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("TestError")
|
||||
|
||||
func TestGlobalSearch(t *testing.T) {
|
||||
Convey("globalSearch", t, func() {
|
||||
Convey("GetRepoLastUpdated fail", func() {
|
||||
mockOlum := mocks.OciLayoutUtilsMock{
|
||||
GetRepoLastUpdatedFn: func(repo string) (time.Time, error) {
|
||||
return time.Time{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
globalSearch([]string{"repo1"}, "name", "tag", mockOlum, log.NewLogger("debug", ""))
|
||||
})
|
||||
|
||||
Convey("GetImageTagsWithTimestamp fail", func() {
|
||||
mockOlum := mocks.OciLayoutUtilsMock{
|
||||
GetImageTagsWithTimestampFn: func(repo string) ([]common.TagInfo, error) {
|
||||
return []common.TagInfo{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
globalSearch([]string{"repo1"}, "name", "tag", mockOlum, log.NewLogger("debug", ""))
|
||||
})
|
||||
|
||||
Convey("GetExpandedRepoInfo fail", func() {
|
||||
mockOlum := mocks.OciLayoutUtilsMock{
|
||||
GetExpandedRepoInfoFn: func(name string) (common.RepoInfo, error) {
|
||||
return common.RepoInfo{}, ErrTestError
|
||||
},
|
||||
}
|
||||
|
||||
globalSearch([]string{"repo1"}, "name", "tag", mockOlum, log.NewLogger("debug", ""))
|
||||
})
|
||||
|
||||
Convey("Bad layer digest in manifest", func() {
|
||||
mockOlum := mocks.OciLayoutUtilsMock{
|
||||
GetExpandedRepoInfoFn: func(name string) (common.RepoInfo, error) {
|
||||
return common.RepoInfo{
|
||||
Manifests: []common.Manifest{
|
||||
{
|
||||
Tag: "latest",
|
||||
Layers: []common.Layer{
|
||||
{
|
||||
Size: "this is a bad size format",
|
||||
Digest: "digest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
GetImageManifestSizeFn: func(repo string, manifestDigest godigest.Digest) int64 {
|
||||
return 100
|
||||
},
|
||||
GetImageConfigSizeFn: func(repo string, manifestDigest godigest.Digest) int64 {
|
||||
return 100
|
||||
},
|
||||
GetImageTagsWithTimestampFn: func(repo string) ([]common.TagInfo, error) {
|
||||
return []common.TagInfo{
|
||||
{
|
||||
Name: "test",
|
||||
Digest: "test",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
globalSearch([]string{"repo1"}, "name", "tag", mockOlum, log.NewLogger("debug", ""))
|
||||
})
|
||||
|
||||
Convey("Tag given, no layer match", func() {
|
||||
mockOlum := mocks.OciLayoutUtilsMock{
|
||||
GetExpandedRepoInfoFn: func(name string) (common.RepoInfo, error) {
|
||||
return common.RepoInfo{
|
||||
Manifests: []common.Manifest{
|
||||
{
|
||||
Tag: "latest",
|
||||
Layers: []common.Layer{
|
||||
{
|
||||
Size: "100",
|
||||
Digest: "sha256:855b1556a45637abf05c63407437f6f305b4627c4361fb965a78e5731999c0c7",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
GetImageManifestSizeFn: func(repo string, manifestDigest godigest.Digest) int64 {
|
||||
return 100
|
||||
},
|
||||
GetImageConfigSizeFn: func(repo string, manifestDigest godigest.Digest) int64 {
|
||||
return 100
|
||||
},
|
||||
GetImageTagsWithTimestampFn: func(repo string) ([]common.TagInfo, error) {
|
||||
return []common.TagInfo{
|
||||
{
|
||||
Name: "test",
|
||||
Digest: "test",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
globalSearch([]string{"repo1"}, "name", "tag", mockOlum, log.NewLogger("debug", ""))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestMatching(t *testing.T) {
|
||||
pine := "pine"
|
||||
|
||||
Convey("Perfect Matching", t, func() {
|
||||
query := "alpine"
|
||||
score := calculateImageMatchingScore("alpine", strings.Index("alpine", query), true)
|
||||
So(score, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Partial Matching", t, func() {
|
||||
query := pine
|
||||
score := calculateImageMatchingScore("alpine", strings.Index("alpine", query), true)
|
||||
So(score, ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Complex Partial Matching", t, func() {
|
||||
query := pine
|
||||
score := calculateImageMatchingScore("repo/test/alpine", strings.Index("alpine", query), true)
|
||||
So(score, ShouldEqual, 2)
|
||||
|
||||
query = pine
|
||||
score = calculateImageMatchingScore("repo/alpine/test", strings.Index("alpine", query), true)
|
||||
So(score, ShouldEqual, 2)
|
||||
|
||||
query = pine
|
||||
score = calculateImageMatchingScore("alpine/repo/test", strings.Index("alpine", query), true)
|
||||
So(score, ShouldEqual, 2)
|
||||
|
||||
query = pine
|
||||
score = calculateImageMatchingScore("alpine/repo/test", strings.Index("alpine", query), false)
|
||||
So(score, ShouldEqual, 12)
|
||||
})
|
||||
}
|
||||
@@ -66,6 +66,50 @@ type LayerInfo {
|
||||
Digest: String
|
||||
}
|
||||
|
||||
# Search results in all repos/images/layers
|
||||
# There will be other more structures for more detailed information
|
||||
type GlobalSearchResult {
|
||||
Images: [ImageSummary]
|
||||
Repos: [RepoSummary]
|
||||
Layers: [LayerSummary]
|
||||
}
|
||||
|
||||
# Brief on a specific image to be used in queries returning a list of images
|
||||
# We define an image as a pairing or a repo and a tag belonging to that repo
|
||||
type ImageSummary {
|
||||
RepoName: String
|
||||
Tag: String
|
||||
LastUpdated: Time
|
||||
IsSigned: Boolean
|
||||
Size: String
|
||||
Platform: OsArch
|
||||
Vendor: String
|
||||
Score: Int
|
||||
}
|
||||
|
||||
# Brief on a specific repo to be used in queries returning a list of repos
|
||||
type RepoSummary {
|
||||
Name: String
|
||||
LastUpdated: Time
|
||||
Size: String
|
||||
Platforms: [OsArch]
|
||||
Vendors: [String]
|
||||
Score: Int
|
||||
}
|
||||
|
||||
# Currently the same as LayerInfo, we can refactor later
|
||||
# For detailed information on the layer a ImageListForDigest call can be made
|
||||
type LayerSummary {
|
||||
Size: String # Int64 is not supported.
|
||||
Digest: String
|
||||
Score: Int
|
||||
}
|
||||
|
||||
type OsArch {
|
||||
Os: String
|
||||
Arch: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
CVEListForImage(image: String!) :CVEResultForImage
|
||||
ImageListForCVE(id: String!) :[ImgResultForCVE]
|
||||
@@ -73,4 +117,5 @@ type Query {
|
||||
ImageListForDigest(id: String!) :[ImgResultForDigest]
|
||||
ImageListWithLatestTag:[ImageInfo]
|
||||
ExpandedRepoInfo(repo: String!):RepoInfo
|
||||
GlobalSearch(query: String!): GlobalSearchResult
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_gene
|
||||
|
||||
// ExpandedRepoInfo is the resolver for the ExpandedRepoInfo field.
|
||||
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql_generated.RepoInfo, error) {
|
||||
olu := common.NewOciLayoutUtils(r.storeController, r.log)
|
||||
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
||||
|
||||
origRepoInfo, err := olu.GetExpandedRepoInfo(repo)
|
||||
if err != nil {
|
||||
@@ -364,6 +364,35 @@ func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql
|
||||
return repoInfo, nil
|
||||
}
|
||||
|
||||
// GlobalSearch is the resolver for the GlobalSearch field.
|
||||
func (r *queryResolver) GlobalSearch(ctx context.Context, query string) (*gql_generated.GlobalSearchResult, error) {
|
||||
query = cleanQuerry(query)
|
||||
defaultStore := r.storeController.DefaultStore
|
||||
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
||||
|
||||
var name, tag string
|
||||
|
||||
_, err := fmt.Sscanf(query, "%s %s", &name, &tag)
|
||||
if err != nil {
|
||||
name = query
|
||||
}
|
||||
|
||||
repoList, err := defaultStore.GetRepositories()
|
||||
if err != nil {
|
||||
r.log.Error().Err(err).Msg("unable to search repositories")
|
||||
|
||||
return &gql_generated.GlobalSearchResult{}, err
|
||||
}
|
||||
|
||||
repos, images, layers := globalSearch(repoList, name, tag, olu, r.log)
|
||||
|
||||
return &gql_generated.GlobalSearchResult{
|
||||
Images: images,
|
||||
Repos: repos,
|
||||
Layers: layers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Query returns gql_generated.QueryResolver implementation.
|
||||
func (r *Resolver) Query() gql_generated.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user