0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

freeform querry api

Signed-off-by: Laurentiu Niculae <themelopeus@gmail.com>
This commit is contained in:
Laurentiu Niculae 2022-07-12 15:58:04 +03:00 committed by Ramkumar Chinchani
parent a31869f270
commit 7e3d063319
20 changed files with 3597 additions and 562 deletions

View file

@ -4,3 +4,4 @@ ignore:
- "./pkg/extensions/minimal.go"
- "./pkg/cli/minimal.go"
- "./cmd/zb/*.go"
- "./pkg/test/mocks/*.go"

8
go.mod
View file

@ -361,16 +361,16 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220524220425-1d687d428aca // indirect
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
golang.org/x/tools v0.1.11 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/api v0.81.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect

14
go.sum
View file

@ -141,8 +141,6 @@ github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtE
github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
@ -2792,8 +2790,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -3099,8 +3098,9 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -3241,15 +3241,17 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=

File diff suppressed because it is too large Load diff

View file

@ -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)
})
}

View file

@ -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)

View file

@ -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)

View file

@ -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}

View file

@ -13,7 +13,7 @@ type CveInfo struct {
Log log.Logger
CveTrivyController CveTrivyController
StoreController storage.StoreController
LayoutUtils *common.OciLayoutUtils
LayoutUtils *common.BaseOciLayoutUtils
}
type CveTrivyController struct {

View file

@ -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

View file

@ -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"`

View file

@ -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)

View file

@ -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)
})
}

View file

@ -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
}

View file

@ -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} }

View file

@ -101,8 +101,8 @@ func createObjectsStore(rootDir string, cacheDir string, dedupe bool) (
}
type FileInfoMock struct {
isDirFn func() bool
sizeFn func() int64
IsDirFn func() bool
SizeFn func() int64
}
func (f *FileInfoMock) Path() string {
@ -110,8 +110,8 @@ func (f *FileInfoMock) Path() string {
}
func (f *FileInfoMock) Size() int64 {
if f != nil && f.sizeFn != nil {
return f.sizeFn()
if f != nil && f.SizeFn != nil {
return f.SizeFn()
}
return int64(fileInfoSize)
@ -122,18 +122,18 @@ func (f *FileInfoMock) ModTime() time.Time {
}
func (f *FileInfoMock) IsDir() bool {
if f != nil && f.isDirFn != nil {
return f.isDirFn()
if f != nil && f.IsDirFn != nil {
return f.IsDirFn()
}
return true
}
type FileWriterMock struct {
writeFn func([]byte) (int, error)
cancelFn func() error
commitFn func() error
closeFn func() error
WriteFn func([]byte) (int, error)
CancelFn func() error
CommitFn func() error
CloseFn func() error
}
func (f *FileWriterMock) Size() int64 {
@ -141,117 +141,117 @@ func (f *FileWriterMock) Size() int64 {
}
func (f *FileWriterMock) Cancel() error {
if f != nil && f.cancelFn != nil {
return f.cancelFn()
if f != nil && f.CancelFn != nil {
return f.CancelFn()
}
return nil
}
func (f *FileWriterMock) Commit() error {
if f != nil && f.commitFn != nil {
return f.commitFn()
if f != nil && f.CommitFn != nil {
return f.CommitFn()
}
return nil
}
func (f *FileWriterMock) Write(p []byte) (int, error) {
if f != nil && f.writeFn != nil {
return f.writeFn(p)
if f != nil && f.WriteFn != nil {
return f.WriteFn(p)
}
return 10, nil
}
func (f *FileWriterMock) Close() error {
if f != nil && f.closeFn != nil {
return f.closeFn()
if f != nil && f.CloseFn != nil {
return f.CloseFn()
}
return nil
}
type StorageDriverMock struct {
nameFn func() string
getContentFn func(ctx context.Context, path string) ([]byte, error)
putContentFn func(ctx context.Context, path string, content []byte) error
readerFn func(ctx context.Context, path string, offset int64) (io.ReadCloser, error)
writerFn func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error)
statFn func(ctx context.Context, path string) (driver.FileInfo, error)
listFn func(ctx context.Context, path string) ([]string, error)
moveFn func(ctx context.Context, sourcePath, destPath string) error
deleteFn func(ctx context.Context, path string) error
walkFn func(ctx context.Context, path string, f driver.WalkFn) error
NameFn func() string
GetContentFn func(ctx context.Context, path string) ([]byte, error)
PutContentFn func(ctx context.Context, path string, content []byte) error
ReaderFn func(ctx context.Context, path string, offset int64) (io.ReadCloser, error)
WriterFn func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error)
StatFn func(ctx context.Context, path string) (driver.FileInfo, error)
ListFn func(ctx context.Context, path string) ([]string, error)
MoveFn func(ctx context.Context, sourcePath, destPath string) error
DeleteFn func(ctx context.Context, path string) error
WalkFn func(ctx context.Context, path string, f driver.WalkFn) error
}
func (s *StorageDriverMock) Name() string {
if s != nil && s.nameFn != nil {
return s.nameFn()
if s != nil && s.NameFn != nil {
return s.NameFn()
}
return ""
}
func (s *StorageDriverMock) GetContent(ctx context.Context, path string) ([]byte, error) {
if s != nil && s.getContentFn != nil {
return s.getContentFn(ctx, path)
if s != nil && s.GetContentFn != nil {
return s.GetContentFn(ctx, path)
}
return []byte{}, nil
}
func (s *StorageDriverMock) PutContent(ctx context.Context, path string, content []byte) error {
if s != nil && s.putContentFn != nil {
return s.putContentFn(ctx, path, content)
if s != nil && s.PutContentFn != nil {
return s.PutContentFn(ctx, path, content)
}
return nil
}
func (s *StorageDriverMock) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
if s != nil && s.readerFn != nil {
return s.readerFn(ctx, path, offset)
if s != nil && s.ReaderFn != nil {
return s.ReaderFn(ctx, path, offset)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (s *StorageDriverMock) Writer(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
if s != nil && s.writerFn != nil {
return s.writerFn(ctx, path, isAppend)
if s != nil && s.WriterFn != nil {
return s.WriterFn(ctx, path, isAppend)
}
return &FileWriterMock{}, nil
}
func (s *StorageDriverMock) Stat(ctx context.Context, path string) (driver.FileInfo, error) {
if s != nil && s.statFn != nil {
return s.statFn(ctx, path)
if s != nil && s.StatFn != nil {
return s.StatFn(ctx, path)
}
return &FileInfoMock{}, nil
}
func (s *StorageDriverMock) List(ctx context.Context, path string) ([]string, error) {
if s != nil && s.listFn != nil {
return s.listFn(ctx, path)
if s != nil && s.ListFn != nil {
return s.ListFn(ctx, path)
}
return []string{"a"}, nil
}
func (s *StorageDriverMock) Move(ctx context.Context, sourcePath, destPath string) error {
if s != nil && s.moveFn != nil {
return s.moveFn(ctx, sourcePath, destPath)
if s != nil && s.MoveFn != nil {
return s.MoveFn(ctx, sourcePath, destPath)
}
return nil
}
func (s *StorageDriverMock) Delete(ctx context.Context, path string) error {
if s != nil && s.deleteFn != nil {
return s.deleteFn(ctx, path)
if s != nil && s.DeleteFn != nil {
return s.DeleteFn(ctx, path)
}
return nil
@ -262,8 +262,8 @@ func (s *StorageDriverMock) URLFor(ctx context.Context, path string, options map
}
func (s *StorageDriverMock) Walk(ctx context.Context, path string, f driver.WalkFn) error {
if s != nil && s.walkFn != nil {
return s.walkFn(ctx, path, f)
if s != nil && s.WalkFn != nil {
return s.WalkFn(ctx, path, f)
}
return nil
@ -458,31 +458,31 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test storage driver errors", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
listFn: func(ctx context.Context, path string) ([]string, error) {
ListFn: func(ctx context.Context, path string) ([]string, error) {
return []string{testImage}, errS3
},
moveFn: func(ctx context.Context, sourcePath, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath, destPath string) error {
return errS3
},
getContentFn: func(ctx context.Context, path string) ([]byte, error) {
GetContentFn: func(ctx context.Context, path string) ([]byte, error) {
return []byte{}, errS3
},
putContentFn: func(ctx context.Context, path string, content []byte) error {
PutContentFn: func(ctx context.Context, path string, content []byte) error {
return errS3
},
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{}, errS3
},
readerFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
ReaderFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), errS3
},
walkFn: func(ctx context.Context, path string, f driver.WalkFn) error {
WalkFn: func(ctx context.Context, path string, f driver.WalkFn) error {
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{}, errS3
},
deleteFn: func(ctx context.Context, path string) error {
DeleteFn: func(ctx context.Context, path string) error {
return errS3
},
})
@ -532,7 +532,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
listFn: func(ctx context.Context, path string) ([]string, error) {
ListFn: func(ctx context.Context, path string) ([]string, error) {
return []string{testImage, testImage}, errS3
},
})
@ -541,10 +541,10 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
So(err, ShouldNotBeNil)
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
listFn: func(ctx context.Context, path string) ([]string, error) {
ListFn: func(ctx context.Context, path string) ([]string, error) {
return []string{testImage, testImage}, nil
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return nil, errS3
},
})
@ -555,10 +555,10 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test ValidateRepo2", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
listFn: func(ctx context.Context, path string) ([]string, error) {
ListFn: func(ctx context.Context, path string) ([]string, error) {
return []string{"test/test/oci-layout", "test/test/index.json"}, nil
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{}, nil
},
})
@ -568,13 +568,13 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test ValidateRepo3", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
listFn: func(ctx context.Context, path string) ([]string, error) {
ListFn: func(ctx context.Context, path string) ([]string, error) {
return []string{"test/test/oci-layout", "test/test/index.json"}, nil
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{}, nil
},
getContentFn: func(ctx context.Context, path string) ([]byte, error) {
GetContentFn: func(ctx context.Context, path string) ([]byte, error) {
return []byte{}, errS3
},
})
@ -585,13 +585,13 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test ValidateRepo4", t, func(c C) {
ociLayout := []byte(`{"imageLayoutVersion": "9.9.9"}`)
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
listFn: func(ctx context.Context, path string) ([]string, error) {
ListFn: func(ctx context.Context, path string) ([]string, error) {
return []string{"test/test/oci-layout", "test/test/index.json"}, nil
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{}, nil
},
getContentFn: func(ctx context.Context, path string) ([]byte, error) {
GetContentFn: func(ctx context.Context, path string) ([]byte, error) {
return ociLayout, nil
},
})
@ -601,7 +601,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test GetRepositories", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
walkFn: func(ctx context.Context, path string, f driver.WalkFn) error {
WalkFn: func(ctx context.Context, path string, f driver.WalkFn) error {
return f(new(FileInfoMock))
},
})
@ -612,7 +612,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test DeleteImageManifest", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
getContentFn: func(ctx context.Context, path string) ([]byte, error) {
GetContentFn: func(ctx context.Context, path string) ([]byte, error) {
return []byte{}, errS3
},
})
@ -628,7 +628,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test NewBlobUpload", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
putContentFn: func(ctx context.Context, path string, content []byte) error {
PutContentFn: func(ctx context.Context, path string, content []byte) error {
return errS3
},
})
@ -638,7 +638,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test GetBlobUpload", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{}, errS3
},
})
@ -648,7 +648,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test PutBlobChunkStreamed", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{}, errS3
},
})
@ -658,8 +658,8 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test PutBlobChunkStreamed2", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{writeFn: func(b []byte) (int, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{WriteFn: func(b []byte) (int, error) {
return 0, errS3
}}, nil
},
@ -670,7 +670,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test PutBlobChunk", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{}, errS3
},
})
@ -680,12 +680,12 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test PutBlobChunk2", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{
writeFn: func(b []byte) (int, error) {
WriteFn: func(b []byte) (int, error) {
return 0, errS3
},
cancelFn: func() error {
CancelFn: func() error {
return errS3
},
}, nil
@ -697,9 +697,9 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test PutBlobChunk3", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{
writeFn: func(b []byte) (int, error) {
WriteFn: func(b []byte) (int, error) {
return 0, errS3
},
}, nil
@ -711,9 +711,9 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test FinishBlobUpload", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{
commitFn: func() error {
CommitFn: func() error {
return errS3
},
}, nil
@ -726,9 +726,9 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test FinishBlobUpload2", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{
closeFn: func() error {
CloseFn: func() error {
return errS3
},
}, nil
@ -741,7 +741,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test FinishBlobUpload3", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
readerFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
ReaderFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
return nil, errS3
},
})
@ -752,7 +752,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test FinishBlobUpload4", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
moveFn: func(ctx context.Context, sourcePath, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath, destPath string) error {
return errS3
},
})
@ -763,7 +763,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test FullBlobUpload", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{}, errS3
},
})
@ -781,7 +781,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test FullBlobUpload3", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
moveFn: func(ctx context.Context, sourcePath, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath, destPath string) error {
return errS3
},
})
@ -792,7 +792,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test GetBlob", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
readerFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
ReaderFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), errS3
},
})
@ -803,7 +803,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test DeleteBlob", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
deleteFn: func(ctx context.Context, path string) error {
DeleteFn: func(ctx context.Context, path string) error {
return errS3
},
})
@ -814,7 +814,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
Convey("Test GetReferrers", t, func(c C) {
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
deleteFn: func(ctx context.Context, path string) error {
DeleteFn: func(ctx context.Context, path string) error {
return errS3
},
})
@ -1128,10 +1128,10 @@ func TestS3DedupeErr(t *testing.T) {
So(err, ShouldNotBeNil)
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
moveFn: func(ctx context.Context, sourcePath string, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath string, destPath string) error {
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return driver.FileInfoInternal{}, errS3
},
})
@ -1148,7 +1148,7 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test DedupeBlob - error on second store.Stat()", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
if path == "dst2" {
return driver.FileInfoInternal{}, errS3
}
@ -1168,10 +1168,10 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test DedupeBlob - error on store.PutContent()", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
putContentFn: func(ctx context.Context, path string, content []byte) error {
PutContentFn: func(ctx context.Context, path string, content []byte) error {
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return nil, nil
},
})
@ -1187,10 +1187,10 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test DedupeBlob - error on store.Delete()", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
deleteFn: func(ctx context.Context, path string) error {
DeleteFn: func(ctx context.Context, path string) error {
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return nil, nil
},
})
@ -1206,13 +1206,13 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test copyBlob() - error on initRepo()", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
putContentFn: func(ctx context.Context, path string, content []byte) error {
PutContentFn: func(ctx context.Context, path string, content []byte) error {
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return driver.FileInfoInternal{}, errS3
},
writerFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
WriterFn: func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
return &FileWriterMock{}, errS3
},
})
@ -1230,10 +1230,10 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test copyBlob() - error on store.PutContent()", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
putContentFn: func(ctx context.Context, path string, content []byte) error {
PutContentFn: func(ctx context.Context, path string, content []byte) error {
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return driver.FileInfoInternal{}, errS3
},
})
@ -1251,7 +1251,7 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test copyBlob() - error on store.Stat()", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return driver.FileInfoInternal{}, errS3
},
})
@ -1290,7 +1290,7 @@ func TestS3DedupeErr(t *testing.T) {
So(err, ShouldBeNil)
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
if strings.Contains(path, "repo1/dst1") {
return driver.FileInfoInternal{}, driver.PathNotFoundError{}
}
@ -1327,14 +1327,14 @@ func TestS3DedupeErr(t *testing.T) {
So(err, ShouldBeNil)
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{
sizeFn: func() int64 {
SizeFn: func() int64 {
return 0
},
}, nil
},
readerFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
ReaderFn: func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
if strings.Contains(path, "repo1/dst1") {
return ioutil.NopCloser(strings.NewReader("")), errS3
}
@ -1356,14 +1356,14 @@ func TestS3DedupeErr(t *testing.T) {
blobPath := path.Join(testDir, "repo/blobs/sha256", hash)
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
moveFn: func(ctx context.Context, sourcePath, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath, destPath string) error {
if destPath == blobPath {
return nil
}
return errS3
},
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
if path != blobPath {
return nil, errS3
}
@ -1385,7 +1385,7 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test FullBlobUpload", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
moveFn: func(ctx context.Context, sourcePath, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath, destPath string) error {
return errS3
},
})
@ -1397,7 +1397,7 @@ func TestS3DedupeErr(t *testing.T) {
Convey("Test FinishBlobUpload", t, func(c C) {
tdir := t.TempDir()
imgStore = createMockStorage(testDir, tdir, true, &StorageDriverMock{
moveFn: func(ctx context.Context, sourcePath, destPath string) error {
MoveFn: func(ctx context.Context, sourcePath, destPath string) error {
return errS3
},
})
@ -1419,7 +1419,7 @@ func TestInjectDedupe(t *testing.T) {
Convey("Inject errors in DedupeBlob function", t, func() {
imgStore := createMockStorage(testDir, tdir, true, &StorageDriverMock{
statFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
StatFn: func(ctx context.Context, path string) (driver.FileInfo, error) {
return &FileInfoMock{}, errS3
},
})

View file

@ -0,0 +1,289 @@
package mocks
import (
"io"
"time"
"github.com/opencontainers/go-digest"
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
)
type MockedImageStore struct {
DirExistsFn func(d string) bool
RootDirFn func() string
InitRepoFn func(name string) error
ValidateRepoFn func(name string) (bool, error)
GetRepositoriesFn func() ([]string, error)
GetImageTagsFn func(repo string) ([]string, error)
GetImageManifestFn func(repo string, reference string) ([]byte, string, string, error)
PutImageManifestFn func(repo string, reference string, mediaType string, body []byte) (string, error)
DeleteImageManifestFn func(repo string, reference string) error
BlobUploadPathFn func(repo string, uuid string) string
NewBlobUploadFn func(repo string) (string, error)
GetBlobUploadFn func(repo string, uuid string) (int64, error)
BlobUploadInfoFn func(repo string, uuid string) (int64, error)
PutBlobChunkStreamedFn func(repo string, uuid string, body io.Reader) (int64, error)
PutBlobChunkFn func(repo string, uuid string, from int64, to int64, body io.Reader) (int64, error)
FinishBlobUploadFn func(repo string, uuid string, body io.Reader, digest string) error
FullBlobUploadFn func(repo string, body io.Reader, digest string) (string, int64, error)
DedupeBlobFn func(src string, dstDigest digest.Digest, dst string) error
DeleteBlobUploadFn func(repo string, uuid string) error
BlobPathFn func(repo string, digest digest.Digest) string
CheckBlobFn func(repo string, digest string) (bool, int64, error)
GetBlobFn func(repo string, digest string, mediaType string) (io.Reader, int64, error)
DeleteBlobFn func(repo string, digest string) error
GetIndexContentFn func(repo string) ([]byte, error)
GetBlobContentFn func(repo, digest string) ([]byte, error)
GetReferrersFn func(repo, digest string, mediaType string) ([]artifactspec.Descriptor, error)
URLForPathFn func(path string) (string, error)
RunGCRepoFn func(repo string)
}
func (is MockedImageStore) Lock(t *time.Time) {
}
func (is MockedImageStore) Unlock(t *time.Time) {
}
func (is MockedImageStore) RUnlock(t *time.Time) {
}
func (is MockedImageStore) RLock(t *time.Time) {
}
func (is MockedImageStore) DirExists(d string) bool {
if is.DirExistsFn != nil {
return is.DirExistsFn(d)
}
return true
}
func (is MockedImageStore) RootDir() string {
if is.RootDirFn != nil {
return is.RootDirFn()
}
return ""
}
func (is MockedImageStore) InitRepo(name string) error {
if is.InitRepoFn != nil {
return is.InitRepoFn(name)
}
return nil
}
func (is MockedImageStore) ValidateRepo(name string) (bool, error) {
if is.ValidateRepoFn != nil {
return is.ValidateRepoFn(name)
}
return true, nil
}
func (is MockedImageStore) GetRepositories() ([]string, error) {
if is.GetRepositoriesFn != nil {
return is.GetRepositoriesFn()
}
return []string{}, nil
}
func (is MockedImageStore) GetImageManifest(repo string, reference string) ([]byte, string, string, error) {
if is.GetImageManifestFn != nil {
return is.GetImageManifestFn(repo, reference)
}
return []byte{}, "", "", nil
}
func (is MockedImageStore) PutImageManifest(
repo string,
reference string,
mediaType string,
body []byte,
) (string, error) {
if is.PutImageManifestFn != nil {
return is.PutImageManifestFn(repo, reference, mediaType, body)
}
return "", nil
}
func (is MockedImageStore) GetImageTags(name string) ([]string, error) {
if is.GetImageTagsFn != nil {
return is.GetImageTagsFn(name)
}
return []string{}, nil
}
func (is MockedImageStore) DeleteImageManifest(name string, reference string) error {
if is.DeleteImageManifestFn != nil {
return is.DeleteImageManifestFn(name, reference)
}
return nil
}
func (is MockedImageStore) NewBlobUpload(repo string) (string, error) {
if is.NewBlobUploadFn != nil {
return is.NewBlobUploadFn(repo)
}
return "", nil
}
func (is MockedImageStore) GetBlobUpload(repo string, uuid string) (int64, error) {
if is.GetBlobUploadFn != nil {
return is.GetBlobUploadFn(repo, uuid)
}
return 0, nil
}
func (is MockedImageStore) BlobUploadInfo(repo string, uuid string) (int64, error) {
if is.BlobUploadInfoFn != nil {
return is.BlobUploadInfoFn(repo, uuid)
}
return 0, nil
}
func (is MockedImageStore) BlobUploadPath(repo string, uuid string) string {
if is.BlobUploadPathFn != nil {
return is.BlobUploadPathFn(repo, uuid)
}
return ""
}
func (is MockedImageStore) PutBlobChunkStreamed(repo string, uuid string, body io.Reader) (int64, error) {
if is.PutBlobChunkStreamedFn != nil {
return is.PutBlobChunkStreamedFn(repo, uuid, body)
}
return 0, nil
}
func (is MockedImageStore) PutBlobChunk(
repo string,
uuid string,
from int64,
to int64,
body io.Reader,
) (int64, error) {
if is.PutBlobChunkFn != nil {
return is.PutBlobChunkFn(repo, uuid, from, to, body)
}
return 0, nil
}
func (is MockedImageStore) FinishBlobUpload(repo string, uuid string, body io.Reader, digest string) error {
if is.FinishBlobUploadFn != nil {
return is.FinishBlobUploadFn(repo, uuid, body, digest)
}
return nil
}
func (is MockedImageStore) FullBlobUpload(repo string, body io.Reader, digest string) (string, int64, error) {
if is.FullBlobUploadFn != nil {
return is.FullBlobUploadFn(repo, body, digest)
}
return "", 0, nil
}
func (is MockedImageStore) DedupeBlob(src string, dstDigest digest.Digest, dst string) error {
if is.DedupeBlobFn != nil {
return is.DedupeBlobFn(src, dstDigest, dst)
}
return nil
}
func (is MockedImageStore) DeleteBlob(repo string, digest string) error {
if is.DeleteBlobFn != nil {
return is.DeleteBlobFn(repo, digest)
}
return nil
}
func (is MockedImageStore) BlobPath(repo string, digest digest.Digest) string {
if is.BlobPathFn != nil {
return is.BlobPathFn(repo, digest)
}
return ""
}
func (is MockedImageStore) CheckBlob(repo string, digest string) (bool, int64, error) {
if is.CheckBlobFn != nil {
return is.CheckBlobFn(repo, digest)
}
return true, 0, nil
}
func (is MockedImageStore) GetBlob(repo string, digest string, mediaType string) (io.Reader, int64, error) {
if is.GetBlobFn != nil {
return is.GetBlobFn(repo, digest, mediaType)
}
return &io.LimitedReader{}, 0, nil
}
func (is MockedImageStore) DeleteBlobUpload(repo string, digest string) error {
if is.DeleteBlobUploadFn != nil {
return is.DeleteBlobUploadFn(repo, digest)
}
return nil
}
func (is MockedImageStore) GetIndexContent(repo string) ([]byte, error) {
if is.GetIndexContentFn != nil {
return is.GetIndexContentFn(repo)
}
return []byte{}, nil
}
func (is MockedImageStore) GetBlobContent(repo string, digest string) ([]byte, error) {
if is.GetBlobContentFn != nil {
return is.GetBlobContentFn(repo, digest)
}
return []byte{}, nil
}
func (is MockedImageStore) GetReferrers(
repo string,
digest string,
mediaType string,
) ([]artifactspec.Descriptor, error) {
if is.GetReferrersFn != nil {
return is.GetReferrersFn(repo, digest, mediaType)
}
return []artifactspec.Descriptor{}, nil
}
func (is MockedImageStore) URLForPath(path string) (string, error) {
if is.URLForPathFn != nil {
return is.URLForPathFn(path)
}
return "", nil
}
func (is MockedImageStore) RunGCRepo(repo string) {
if is.RunGCRepoFn != nil {
is.RunGCRepoFn(repo)
}
}

121
pkg/test/mocks/oci_mock.go Normal file
View file

@ -0,0 +1,121 @@
package mocks
import (
"time"
v1 "github.com/google/go-containerregistry/pkg/v1"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"zotregistry.io/zot/pkg/extensions/search/common"
)
type OciLayoutUtilsMock struct {
GetImageManifestsFn func(image string) ([]ispec.Descriptor, error)
GetImageBlobManifestFn func(imageDir string, digest godigest.Digest) (v1.Manifest, error)
GetImageInfoFn func(imageDir string, hash v1.Hash) (ispec.Image, error)
IsValidImageFormatFn func(image string) (bool, error)
GetImageTagsWithTimestampFn func(repo string) ([]common.TagInfo, error)
GetImageLastUpdatedFn func(repo string, manifestDigest godigest.Digest) time.Time
GetImagePlatformFn func(repo string, manifestDigest godigest.Digest) (string, string)
GetImageVendorFn func(repo string, manifestDigest godigest.Digest) string
GetImageManifestSizeFn func(repo string, manifestDigest godigest.Digest) int64
GetImageConfigSizeFn func(repo string, manifestDigest godigest.Digest) int64
GetRepoLastUpdatedFn func(repo string) (time.Time, error)
GetExpandedRepoInfoFn func(name string) (common.RepoInfo, error)
}
func (olum OciLayoutUtilsMock) GetImageManifests(image string) ([]ispec.Descriptor, error) {
if olum.GetImageBlobManifestFn != nil {
return olum.GetImageManifestsFn(image)
}
return []ispec.Descriptor{}, nil
}
func (olum OciLayoutUtilsMock) GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error) {
if olum.GetImageBlobManifestFn != nil {
return olum.GetImageBlobManifestFn(imageDir, digest)
}
return v1.Manifest{}, nil
}
func (olum OciLayoutUtilsMock) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error) {
if olum.GetImageInfoFn != nil {
return olum.GetImageInfoFn(imageDir, hash)
}
return ispec.Image{}, nil
}
func (olum OciLayoutUtilsMock) IsValidImageFormat(image string) (bool, error) {
if olum.IsValidImageFormatFn != nil {
return olum.IsValidImageFormatFn(image)
}
return true, nil
}
func (olum OciLayoutUtilsMock) GetImageTagsWithTimestamp(repo string) ([]common.TagInfo, error) {
if olum.GetImageTagsWithTimestampFn != nil {
return olum.GetImageTagsWithTimestampFn(repo)
}
return []common.TagInfo{}, nil
}
func (olum OciLayoutUtilsMock) GetImageLastUpdated(repo string, manifestDigest godigest.Digest) time.Time {
if olum.GetImageLastUpdatedFn != nil {
return olum.GetImageLastUpdatedFn(repo, manifestDigest)
}
return time.Time{}
}
func (olum OciLayoutUtilsMock) GetImagePlatform(repo string, manifestDigest godigest.Digest) (string, string) {
if olum.GetImagePlatformFn != nil {
return olum.GetImagePlatformFn(repo, manifestDigest)
}
return "", ""
}
func (olum OciLayoutUtilsMock) GetImageVendor(repo string, manifestDigest godigest.Digest) string {
if olum.GetImageVendorFn != nil {
return olum.GetImageVendorFn(repo, manifestDigest)
}
return ""
}
func (olum OciLayoutUtilsMock) GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64 {
if olum.GetImageManifestSizeFn != nil {
return olum.GetImageManifestSizeFn(repo, manifestDigest)
}
return 0
}
func (olum OciLayoutUtilsMock) GetImageConfigSize(repo string, manifestDigest godigest.Digest) int64 {
if olum.GetImageConfigSizeFn != nil {
return olum.GetImageConfigSizeFn(repo, manifestDigest)
}
return 0
}
func (olum OciLayoutUtilsMock) GetRepoLastUpdated(repo string) (time.Time, error) {
if olum.GetRepoLastUpdatedFn != nil {
return olum.GetRepoLastUpdatedFn(repo)
}
return time.Time{}, nil
}
func (olum OciLayoutUtilsMock) GetExpandedRepoInfo(name string) (common.RepoInfo, error) {
if olum.GetExpandedRepoInfoFn != nil {
return olum.GetExpandedRepoInfoFn(name)
}
return common.RepoInfo{}, nil
}

View file

@ -0,0 +1,186 @@
package mocks
import (
"context"
"io"
"io/ioutil"
"strings"
"time"
"github.com/docker/distribution/registry/storage/driver"
)
type StorageDriverMock struct {
NameFn func() string
GetContentFn func(ctx context.Context, path string) ([]byte, error)
PutContentFn func(ctx context.Context, path string, content []byte) error
ReaderFn func(ctx context.Context, path string, offset int64) (io.ReadCloser, error)
WriterFn func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error)
StatFn func(ctx context.Context, path string) (driver.FileInfo, error)
ListFn func(ctx context.Context, path string) ([]string, error)
MoveFn func(ctx context.Context, sourcePath, destPath string) error
DeleteFn func(ctx context.Context, path string) error
WalkFn func(ctx context.Context, path string, f driver.WalkFn) error
}
// nolint: gochecknoglobals
var (
fileWriterSize = 12
fileInfoSize = 10
)
func (s *StorageDriverMock) Name() string {
if s != nil && s.NameFn != nil {
return s.NameFn()
}
return ""
}
func (s *StorageDriverMock) GetContent(ctx context.Context, path string) ([]byte, error) {
if s != nil && s.GetContentFn != nil {
return s.GetContentFn(ctx, path)
}
return []byte{}, nil
}
func (s *StorageDriverMock) PutContent(ctx context.Context, path string, content []byte) error {
if s != nil && s.PutContentFn != nil {
return s.PutContentFn(ctx, path, content)
}
return nil
}
func (s *StorageDriverMock) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
if s != nil && s.ReaderFn != nil {
return s.ReaderFn(ctx, path, offset)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (s *StorageDriverMock) Writer(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
if s != nil && s.WriterFn != nil {
return s.WriterFn(ctx, path, isAppend)
}
return &FileWriterMock{}, nil
}
func (s *StorageDriverMock) Stat(ctx context.Context, path string) (driver.FileInfo, error) {
if s != nil && s.StatFn != nil {
return s.StatFn(ctx, path)
}
return &FileInfoMock{}, nil
}
func (s *StorageDriverMock) List(ctx context.Context, path string) ([]string, error) {
if s != nil && s.ListFn != nil {
return s.ListFn(ctx, path)
}
return []string{"a"}, nil
}
func (s *StorageDriverMock) Move(ctx context.Context, sourcePath, destPath string) error {
if s != nil && s.MoveFn != nil {
return s.MoveFn(ctx, sourcePath, destPath)
}
return nil
}
func (s *StorageDriverMock) Delete(ctx context.Context, path string) error {
if s != nil && s.DeleteFn != nil {
return s.DeleteFn(ctx, path)
}
return nil
}
func (s *StorageDriverMock) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) {
return "", nil
}
func (s *StorageDriverMock) Walk(ctx context.Context, path string, f driver.WalkFn) error {
if s != nil && s.WalkFn != nil {
return s.WalkFn(ctx, path, f)
}
return nil
}
type FileInfoMock struct {
IsDirFn func() bool
SizeFn func() int64
}
func (f *FileInfoMock) Path() string {
return ""
}
func (f *FileInfoMock) Size() int64 {
if f != nil && f.SizeFn != nil {
return f.SizeFn()
}
return int64(fileInfoSize)
}
func (f *FileInfoMock) ModTime() time.Time {
return time.Now()
}
func (f *FileInfoMock) IsDir() bool {
if f != nil && f.IsDirFn != nil {
return f.IsDirFn()
}
return true
}
type FileWriterMock struct {
WriteFn func([]byte) (int, error)
CancelFn func() error
CommitFn func() error
CloseFn func() error
}
func (f *FileWriterMock) Size() int64 {
return int64(fileWriterSize)
}
func (f *FileWriterMock) Cancel() error {
if f != nil && f.CancelFn != nil {
return f.CancelFn()
}
return nil
}
func (f *FileWriterMock) Commit() error {
if f != nil && f.CommitFn != nil {
return f.CommitFn()
}
return nil
}
func (f *FileWriterMock) Write(p []byte) (int, error) {
if f != nil && f.WriteFn != nil {
return f.WriteFn(p)
}
return 10, nil
}
func (f *FileWriterMock) Close() error {
if f != nil && f.CloseFn != nil {
return f.CloseFn()
}
return nil
}