0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-30 22:34:13 -05:00
zot/pkg/extensions/search/digest/digest_test.go
Catalin-George Hofnar 4170d2adbc
refactor(cache): rewrote/refactored cachedb functionality to use interface (#667)
Moved boltdb to a driver implementation for such interface
Added CreateCacheDatabaseDriver in controller
Fixed default directory creation (boltDB will only create the file, not the dir
Added coverage tests
Added example config for boltdb
Re-added caching on subpaths, rewrote CreateCacheDatabaseDriver
Fix tests
Made cacheDriver argument mandatory for NewImageStore, added more validation, added defaults
Moved cache interface to own file, removed useRelPaths from config
Got rid of cache config, refactored
Moved cache to own package and folder
Renamed + removed cache factory to backend, replaced CloudCache to RemoteCache
Moved storage constants back to storage package
moved cache interface and factory to storage package, changed remoteCache defaulting

Signed-off-by: Catalin Hofnar <catalin.hofnar@gmail.com>
2022-11-02 15:53:08 -07:00

408 lines
12 KiB
Go

//go:build search
// +build search
//nolint:gochecknoinits
package digestinfo_test
import (
"context"
"encoding/json"
"net/url"
"os"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/monitoring"
digestinfo "zotregistry.io/zot/pkg/extensions/search/digest"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/storage/local"
. "zotregistry.io/zot/pkg/test"
)
type ImgResponseForDigest struct {
ImgListForDigest ImgListForDigest `json:"data"`
Errors []ErrorGQL `json:"errors"`
}
//nolint:tagliatelle // graphQL schema
type ImgListForDigest struct {
Images []ImgInfo `json:"ImageListForDigest"`
}
//nolint:tagliatelle // graphQL schema
type ImgInfo struct {
RepoName string `json:"RepoName"`
Tag string `json:"Tag"`
ConfigDigest string `json:"ConfigDigest"`
Digest string `json:"Digest"`
Size string `json:"Size"`
}
type ErrorGQL struct {
Message string `json:"message"`
Path []string `json:"path"`
}
func testSetup(t *testing.T) (string, string, *digestinfo.DigestInfo) {
t.Helper()
dir := t.TempDir()
subDir := t.TempDir()
rootDir := dir
subRootDir := subDir
// Test images used/copied:
// IMAGE NAME TAG DIGEST CONFIG LAYERS SIZE
// zot-test 0.0.1 2bacca16 adf3bb6c 76MB
// 2d473b07 76MB
// zot-cve-test 0.0.1 63a795ca 8dd57e17 75MB
// 7a0437f0 75MB
err := os.Mkdir(subDir+"/a", 0o700)
if err != nil {
panic(err)
}
err = CopyFiles("../../../../test/data", rootDir)
if err != nil {
panic(err)
}
err = CopyFiles("../../../../test/data", subDir+"/a/")
if err != nil {
panic(err)
}
log := log.NewLogger("debug", "")
metrics := monitoring.NewMetricsServer(false, log)
storeController := storage.StoreController{
DefaultStore: local.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil, nil),
}
digestInfo := digestinfo.NewDigestInfo(storeController, log)
return rootDir, subRootDir, digestInfo
}
func TestDigestInfo(t *testing.T) {
Convey("Test image tag", t, func() {
_, _, digestInfo := testSetup(t)
// Search by manifest digest
imageTags, err := digestInfo.GetImageTagsByDigest("zot-cve-test",
GetTestBlobDigest("zot-cve-test", "manifest").Encoded())
So(err, ShouldBeNil)
So(len(imageTags), ShouldEqual, 1)
So(imageTags[0].Tag, ShouldEqual, "0.0.1")
// Search by config digest
imageTags, err = digestInfo.GetImageTagsByDigest("zot-test", GetTestBlobDigest("zot-test", "config").Encoded())
So(err, ShouldBeNil)
So(len(imageTags), ShouldEqual, 1)
So(imageTags[0].Tag, ShouldEqual, "0.0.1")
// Search by layer digest
imageTags, err = digestInfo.GetImageTagsByDigest("zot-cve-test", GetTestBlobDigest("zot-cve-test", "layer").Encoded())
So(err, ShouldBeNil)
So(len(imageTags), ShouldEqual, 1)
So(imageTags[0].Tag, ShouldEqual, "0.0.1")
// Search by non-existent image
imageTags, err = digestInfo.GetImageTagsByDigest("zot-tes", GetTestBlobDigest("zot-test", "manifest").Encoded())
So(err, ShouldNotBeNil)
So(len(imageTags), ShouldEqual, 0)
// Search by non-existent digest
imageTags, err = digestInfo.GetImageTagsByDigest("zot-test", "111")
So(err, ShouldBeNil)
So(len(imageTags), ShouldEqual, 0)
})
}
func TestDigestSearchHTTP(t *testing.T) {
Convey("Test image search by digest scanning", t, func() {
rootDir, _, _ := testSetup(t)
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = rootDir
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
}
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)
}()
resp, err := resty.R().Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, err = resty.R().Get(baseURL + constants.FullSearchPrefix)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
// "sha" should match all digests in all images
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"sha")` +
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
var responseStruct ImgResponseForDigest
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Errors), ShouldEqual, 0)
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 2)
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}}
// GetTestBlobDigest("zot-test", "manifest").Encoded() should match the manifest of 1 image
gqlQuery := url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-test", "manifest").Encoded() + `")
{RepoName Tag Digest ConfigDigest Size Layers { Digest }}}`)
targetURL := baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery
resp, err = resty.R().Get(targetURL)
So(string(resp.Body()), ShouldNotBeNil)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Errors), ShouldEqual, 0)
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-test")
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
// GetTestBlobDigest("zot-test", "config").Encoded() should match the config of 1 image.
gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-test", "config").Encoded() + `")
{RepoName Tag Digest ConfigDigest Size Layers { Digest }}}`)
targetURL = baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery
resp, err = resty.R().Get(targetURL)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Errors), ShouldEqual, 0)
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-test")
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}}
// GetTestBlobDigest("zot-cve-test", "layer").Encoded() should match the layer of 1 image
gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-cve-test", "layer").Encoded() + `")
{RepoName Tag Digest ConfigDigest Size Layers { Digest }}}`)
targetURL = baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery
resp, err = resty.R().Get(
targetURL,
)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
var responseStruct2 ImgResponseForDigest
err = json.Unmarshal(resp.Body(), &responseStruct2)
So(err, ShouldBeNil)
So(len(responseStruct2.Errors), ShouldEqual, 0)
So(len(responseStruct2.ImgListForDigest.Images), ShouldEqual, 1)
So(responseStruct2.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-cve-test")
So(responseStruct2.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
// Call should return {"data":{"ImageListForDigest":[]}}
// "1111111" should match 0 images
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"1111111")` +
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Errors), ShouldEqual, 0)
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 0)
// Call should return {"errors": [{....}]", data":null}}
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"1111111")` +
`{RepoName%20Tag343s}}`,
)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Errors), ShouldEqual, 1)
})
}
func TestDigestSearchHTTPSubPaths(t *testing.T) {
Convey("Test image search by digest scanning using storage subpaths", t, func() {
_, subRootDir, _ := testSetup(t)
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
}
ctlr := api.NewController(conf)
globalDir, err := os.MkdirTemp("", "digest_test")
if err != nil {
panic(err)
}
defer os.RemoveAll(globalDir)
ctlr.Config.Storage.RootDirectory = globalDir
subPathMap := make(map[string]config.StorageConfig)
subPathMap["/a"] = config.StorageConfig{RootDirectory: subRootDir}
ctlr.Config.Storage.SubPaths = subPathMap
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)
}()
resp, err := resty.R().Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, err = resty.R().Get(baseURL + constants.FullSearchPrefix)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"sha")` +
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
var responseStruct ImgResponseForDigest
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Errors), ShouldEqual, 0)
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 2)
})
}
func TestDigestSearchDisabled(t *testing.T) {
Convey("Test disabling image search", t, func() {
var disabled bool
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = t.TempDir()
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &disabled}},
}
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)
}()
resp, err := resty.R().Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, err = resty.R().Get(baseURL + constants.FullSearchPrefix)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404)
})
}