0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/extensions/search/digest_test.go
LaurentiuNiculae 9cc990d7ca
feat(repodb): add user related information to repodb (#1317)
Initial code was contributed by Bogdan BIVOLARU <104334+bogdanbiv@users.noreply.github.com>
Moved implementation from a separate db to repodb by Andrei Aaron <aaaron@luxoft.com>

Not done yet:
- run/test dynamodb implementation, only boltdb was tested
- add additional coverage for existing functionality
- add web-based APIs to toggle the stars/bookmarks on/off

Initially graphql mutation was discussed for the missing API but
we decided REST endpoints would be better suited for configuration



feat(userdb): complete functionality for userdb integration

- dynamodb rollback changes to user starred repos in case increasing the total star count fails
- dynamodb increment/decrement repostars in repometa when user stars/unstars a repo
- dynamodb check anonymous user permissions are working as intendend
- common test handle anonymous users
- RepoMeta2RepoSummary set IsStarred and IsBookmarked



feat(userdb): rest api calls for toggling stars/bookmarks on/off



test(userdb): blackbox tests



test(userdb): move preferences tests in a different file with specific build tags



feat(repodb): add is-starred and is-bookmarked fields to repo-meta

- removed duplicated logic for determining if a repo is starred/bookmarked

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
Co-authored-by: Andrei Aaron <aaaron@luxoft.com>
2023-04-24 11:13:15 -07:00

377 lines
10 KiB
Go

//go:build search
// +build search
package search_test
import (
"encoding/json"
"net/url"
"os"
"testing"
"time"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "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/meta/repodb"
. "zotregistry.io/zot/pkg/test"
)
type ImgResponseForDigest struct {
ImgListForDigest ImgListForDigest `json:"data"`
Errors []ErrorGQL `json:"errors"`
}
//nolint:tagliatelle // graphQL schema
type ImgListForDigest struct {
PaginatedImagesResultForDigest `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 PaginatedImagesResultForDigest struct {
Results []ImgInfo `json:"results"`
Page repodb.PageInfo `json:"page"`
}
func TestDigestSearchHTTP(t *testing.T) {
Convey("Test image search by digest scanning", t, func() {
rootDir := t.TempDir()
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()
createdTime1 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
layers1 := [][]byte{
{3, 2, 2},
}
image1, err := GetImageWithComponents(
ispec.Image{
Created: &createdTime1,
History: []ispec.History{
{
Created: &createdTime1,
},
},
},
layers1,
)
So(err, ShouldBeNil)
const ver001 = "0.0.1"
image1.Reference = ver001
err = UploadImage(
image1,
baseURL,
"zot-cve-test",
)
So(err, ShouldBeNil)
createdTime2 := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
image2, err := GetImageWithComponents(
ispec.Image{
History: []ispec.History{{Created: &createdTime2}},
Platform: ispec.Platform{
Architecture: "amd64",
OS: "linux",
},
},
[][]byte{
{0, 0, 2},
},
)
So(err, ShouldBeNil)
image2.Reference = ver001
manifestDigest, err := image2.Digest()
So(err, ShouldBeNil)
err = UploadImage(image2, baseURL, "zot-test")
So(err, ShouldBeNil)
configBlob, err := json.Marshal(image2.Config)
So(err, ShouldBeNil)
configDigest := godigest.FromBytes(configBlob)
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
query := `{
ImageListForDigest(id:"sha") {
Results {
RepoName Tag
Manifests {
Digest ConfigDigest Size
Layers { Digest }
}
Size
}
}
}`
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
)
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.Results), ShouldEqual, 2)
So(responseStruct.ImgListForDigest.Results[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:"` + manifestDigest.Encoded() + `")
{Results{RepoName Tag Manifests {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.Results), ShouldEqual, 1)
So(responseStruct.ImgListForDigest.Results[0].RepoName, ShouldEqual, "zot-test")
So(responseStruct.ImgListForDigest.Results[0].Tag, ShouldEqual, "0.0.1")
gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + configDigest.Encoded() + `")
{Results{RepoName Tag Manifests {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.Results), ShouldEqual, 1)
So(responseStruct.ImgListForDigest.Results[0].RepoName, ShouldEqual, "zot-test")
So(responseStruct.ImgListForDigest.Results[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
layerDigest1 := godigest.FromBytes((layers1[0]))
gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + layerDigest1.Encoded() + `")
{Results{RepoName Tag Manifests {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.Results), ShouldEqual, 1)
So(responseStruct2.ImgListForDigest.Results[0].RepoName, ShouldEqual, "zot-cve-test")
So(responseStruct2.ImgListForDigest.Results[0].Tag, ShouldEqual, "0.0.1")
// Call should return {"data":{"ImageListForDigest":[]}}
// "1111111" should match 0 images
query = `
{
ImageListForDigest(id:"1111111") {
Results {
RepoName Tag
Manifests {
Digest ConfigDigest Size
Layers { Digest }
}
}
}
}`
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
)
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.Results), ShouldEqual, 0)
// Call should return {"errors": [{....}]", data":null}}
query = `{
ImageListForDigest(id:"1111111") {
Results {
RepoName Tag343s
}
}`
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
)
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 := t.TempDir()
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 := t.TempDir()
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()
config, layers, manifest, err := GetImageComponents(100)
So(err, ShouldBeNil)
err = PushTestImage("a/zot-cve-test", "0.0.1", baseURL,
manifest, config, layers)
So(err, ShouldBeNil)
err = PushTestImage("a/zot-test", "0.0.1", baseURL,
manifest, config, layers)
So(err, ShouldBeNil)
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)
query := `{
ImageListForDigest(id:"sha") {
Results {
RepoName Tag
Manifests {
Digest ConfigDigest Size
Layers { Digest }
}
}
}
}`
resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
)
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.Results), 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)
})
}