mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
70a60b4660
Signed-off-by: Nicol Draghici <idraghic@cisco.com> fix: Increase coverage when copying files Signed-off-by: Nicol Draghici <idraghic@cisco.com>
352 lines
11 KiB
Go
352 lines
11 KiB
Go
//go:build search
|
|
// +build search
|
|
|
|
//nolint:gochecknoinits
|
|
package digestinfo_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
|
|
. "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)
|
|
}
|
|
|
|
CopyTestFiles("../../../../test/data", rootDir)
|
|
|
|
CopyTestFiles("../../../../test/data", subDir+"/a/")
|
|
|
|
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)
|
|
ctrlManager := NewControllerManager(ctlr)
|
|
|
|
ctrlManager.StartAndWait(port)
|
|
|
|
// shut down server
|
|
defer ctrlManager.StopServer()
|
|
|
|
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
|
|
ctrlManager := NewControllerManager(ctlr)
|
|
|
|
ctrlManager.StartAndWait(port)
|
|
|
|
// shut down server
|
|
defer ctrlManager.StopServer()
|
|
|
|
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)
|
|
ctrlManager := NewControllerManager(ctlr)
|
|
|
|
ctrlManager.StartAndWait(port)
|
|
|
|
// shut down server
|
|
defer ctrlManager.StopServer()
|
|
|
|
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)
|
|
})
|
|
}
|