feat(repodb): DerivedImageList and BaseImageList make use of RepoDB (#1135)
- derivedImageList and baseImageList now use FilterTags to obtain results, each with its own filter function - images that have the exact same manifest as the one provided as a parameter are no longer considered base images or derived images - both calls can be made with specific pagination parameters, and the response will include PageInfo Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro> fix(tests): fix one of the pagination tests The results were not reliable as the 2 returned tags were sorted by created date/time which was not set, resulting in an unpredictable order Signed-off-by: Andrei Aaron <andaaron@cisco.com> (cherry picked from commit be504200a1127371422aeb0e5c0219e2a1ead20a) (cherry picked from commit ed8d797e639f262a63840120afe92da7db9a7600) Signed-off-by: Andrei Aaron <aaaron@luxoft.com> Signed-off-by: Andrei Aaron <andaaron@cisco.com> Signed-off-by: Andrei Aaron <aaaron@luxoft.com> Co-authored-by: Alex Stan <alexandrustan96@yahoo.ro>
This commit is contained in:
parent
be4b8c6243
commit
feb7328f50
@ -483,7 +483,7 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
cm.StartAndWait(conf.HTTP.Port)
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
defer cm.StopServer()
|
defer cm.StopServer()
|
||||||
|
|
||||||
err := uploadManifest(url)
|
err := uploadManifestDerivedBase(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -493,7 +493,7 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
Convey("Test from real server", t, func() {
|
Convey("Test from real server", t, func() {
|
||||||
Convey("Test derived images list working", func() {
|
Convey("Test derived images list working", func() {
|
||||||
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
||||||
args := []string{"imagetest", "--derived-images", "repo7:test:1.0"}
|
args := []string{"imagetest", "--derived-images", "repo7:test:2.0"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
cmd := NewImageCommand(new(searchService))
|
cmd := NewImageCommand(new(searchService))
|
||||||
@ -507,19 +507,11 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
actual := strings.TrimSpace(str)
|
actual := strings.TrimSpace(str)
|
||||||
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE")
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B")
|
So(actual, ShouldContainSubstring, "repo7 test:1.0 2694fdb0 false 824B")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test derived images fail", func() {
|
Convey("Test derived images list fails", func() {
|
||||||
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
args := []string{"imagetest", "--derived-images", "repo7:test:missing"}
|
||||||
err = os.Chmod(ctlr.Config.Storage.RootDirectory, 0o000)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err := os.Chmod(ctlr.Config.Storage.RootDirectory, 0o755)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
}()
|
|
||||||
args := []string{"imagetest", "--derived-images", "repo7:test:1.0"}
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
cmd := NewImageCommand(new(searchService))
|
cmd := NewImageCommand(new(searchService))
|
||||||
@ -533,7 +525,7 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
|
|
||||||
Convey("Test derived images list cannot print", func() {
|
Convey("Test derived images list cannot print", func() {
|
||||||
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
||||||
args := []string{"imagetest", "--derived-images", "repo7:test:1.0", "-o", "random"}
|
args := []string{"imagetest", "--derived-images", "repo7:test:2.0", "-o", "random"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
cmd := NewImageCommand(new(searchService))
|
cmd := NewImageCommand(new(searchService))
|
||||||
@ -564,7 +556,7 @@ func TestBaseImageList(t *testing.T) {
|
|||||||
cm.StartAndWait(conf.HTTP.Port)
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
defer cm.StopServer()
|
defer cm.StopServer()
|
||||||
|
|
||||||
err := uploadManifest(url)
|
err := uploadManifestDerivedBase(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -588,19 +580,11 @@ func TestBaseImageList(t *testing.T) {
|
|||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
actual := strings.TrimSpace(str)
|
actual := strings.TrimSpace(str)
|
||||||
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE")
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B")
|
So(actual, ShouldContainSubstring, "repo7 test:2.0 3fc80493 false 494B")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test base images fail", func() {
|
Convey("Test base images list fail", func() {
|
||||||
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
args := []string{"imagetest", "--base-images", "repo7:test:missing"}
|
||||||
err = os.Chmod(ctlr.Config.Storage.RootDirectory, 0o000)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err := os.Chmod(ctlr.Config.Storage.RootDirectory, 0o755)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
}()
|
|
||||||
args := []string{"imagetest", "--base-images", "repo7:test:1.0"}
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
cmd := NewImageCommand(new(searchService))
|
cmd := NewImageCommand(new(searchService))
|
||||||
@ -1481,6 +1465,98 @@ func uploadManifest(url string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uploadManifestDerivedBase(url string) error {
|
||||||
|
// create a blob/layer
|
||||||
|
_, _ = resty.R().Post(url + "/v2/repo7/blobs/uploads/")
|
||||||
|
|
||||||
|
content1 := []byte("this is a blob5.0")
|
||||||
|
content2 := []byte("this is a blob5.1")
|
||||||
|
content3 := []byte("this is a blob5.2")
|
||||||
|
digest1 := godigest.FromBytes(content1)
|
||||||
|
digest2 := godigest.FromBytes(content2)
|
||||||
|
digest3 := godigest.FromBytes(content3)
|
||||||
|
_, _ = resty.R().SetQueryParam("digest", digest1.String()).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content1).Post(url + "/v2/repo7/blobs/uploads/")
|
||||||
|
_, _ = resty.R().SetQueryParam("digest", digest2.String()).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content2).Post(url + "/v2/repo7/blobs/uploads/")
|
||||||
|
_, _ = resty.R().SetQueryParam("digest", digest3.String()).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content3).Post(url + "/v2/repo7/blobs/uploads/")
|
||||||
|
|
||||||
|
// upload image config blob
|
||||||
|
resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/")
|
||||||
|
loc := test.Location(url, resp)
|
||||||
|
cblob, cdigest := test.GetImageConfig()
|
||||||
|
|
||||||
|
_, _ = resty.R().
|
||||||
|
SetContentLength(true).
|
||||||
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
|
||||||
|
SetHeader("Content-Type", "application/octet-stream").
|
||||||
|
SetQueryParam("digest", cdigest.String()).
|
||||||
|
SetBody(cblob).
|
||||||
|
Put(loc)
|
||||||
|
|
||||||
|
// create a manifest
|
||||||
|
manifest := ispec.Manifest{
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: cdigest,
|
||||||
|
Size: int64(len(cblob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: digest1,
|
||||||
|
Size: int64(len(content1)),
|
||||||
|
}, {
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: digest2,
|
||||||
|
Size: int64(len(content2)),
|
||||||
|
}, {
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: digest3,
|
||||||
|
Size: int64(len(content3)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manifest.SchemaVersion = 2
|
||||||
|
|
||||||
|
content, err := json.Marshal(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||||
|
SetBody(content).Put(url + "/v2/repo7/manifests/test:1.0")
|
||||||
|
|
||||||
|
content1 = []byte("this is a blob5.0")
|
||||||
|
digest1 = godigest.FromBytes(content1)
|
||||||
|
// create a manifest with one common layer blob
|
||||||
|
manifest = ispec.Manifest{
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: cdigest,
|
||||||
|
Size: int64(len(cblob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: digest1,
|
||||||
|
Size: int64(len(content1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manifest.SchemaVersion = 2
|
||||||
|
|
||||||
|
content, err = json.Marshal(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||||
|
SetBody(content).Put(url + "/v2/repo7/manifests/test:2.0")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type mockService struct{}
|
type mockService struct{}
|
||||||
|
|
||||||
func (service mockService) getRepos(ctx context.Context, config searchConfig, username,
|
func (service mockService) getRepos(ctx context.Context, config searchConfig, username,
|
||||||
@ -1501,7 +1577,7 @@ func (service mockService) getDerivedImageListGQL(ctx context.Context, config se
|
|||||||
derivedImage string,
|
derivedImage string,
|
||||||
) (*imageListStructForDerivedImagesGQL, error) {
|
) (*imageListStructForDerivedImagesGQL, error) {
|
||||||
imageListGQLResponse := &imageListStructForDerivedImagesGQL{}
|
imageListGQLResponse := &imageListStructForDerivedImagesGQL{}
|
||||||
imageListGQLResponse.Data.ImageList = []imageStruct{
|
imageListGQLResponse.Data.ImageList.Results = []imageStruct{
|
||||||
{
|
{
|
||||||
RepoName: "dummyImageName",
|
RepoName: "dummyImageName",
|
||||||
Tag: "tag",
|
Tag: "tag",
|
||||||
@ -1519,7 +1595,7 @@ func (service mockService) getBaseImageListGQL(ctx context.Context, config searc
|
|||||||
derivedImage string,
|
derivedImage string,
|
||||||
) (*imageListStructForBaseImagesGQL, error) {
|
) (*imageListStructForBaseImagesGQL, error) {
|
||||||
imageListGQLResponse := &imageListStructForBaseImagesGQL{}
|
imageListGQLResponse := &imageListStructForBaseImagesGQL{}
|
||||||
imageListGQLResponse.Data.ImageList = []imageStruct{
|
imageListGQLResponse.Data.ImageList.Results = []imageStruct{
|
||||||
{
|
{
|
||||||
RepoName: "dummyImageName",
|
RepoName: "dummyImageName",
|
||||||
Tag: "tag",
|
Tag: "tag",
|
||||||
|
@ -241,7 +241,7 @@ func (search derivedImageListSearcherGQL) search(config searchConfig) (bool, err
|
|||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := printResult(config, imageList.Data.ImageList); err != nil {
|
if err := printResult(config, imageList.Data.ImageList.Results); err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +266,7 @@ func (search baseImageListSearcherGQL) search(config searchConfig) (bool, error)
|
|||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := printResult(config, imageList.Data.ImageList); err != nil {
|
if err := printResult(config, imageList.Data.ImageList.Results); err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,14 +71,16 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
|
|||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
DerivedImageList(image:"%s"){
|
DerivedImageList(image:"%s"){
|
||||||
RepoName,
|
Results{
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
Layers {Size Digest},
|
ConfigDigest,
|
||||||
LastUpdated,
|
Layers {Size Digest},
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`, derivedImage)
|
}`, derivedImage)
|
||||||
|
|
||||||
@ -98,14 +100,16 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
|
|||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
BaseImageList(image:"%s"){
|
BaseImageList(image:"%s"){
|
||||||
RepoName,
|
Results{
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
Layers {Size Digest},
|
ConfigDigest,
|
||||||
LastUpdated,
|
Layers {Size Digest},
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`, baseImage)
|
}`, baseImage)
|
||||||
|
|
||||||
@ -862,6 +866,13 @@ type imageStruct struct {
|
|||||||
IsSigned bool `json:"isSigned"`
|
IsSigned bool `json:"isSigned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DerivedImageList struct {
|
||||||
|
Results []imageStruct `json:"results"`
|
||||||
|
}
|
||||||
|
type BaseImageList struct {
|
||||||
|
Results []imageStruct `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
type imageListStructGQL struct {
|
type imageListStructGQL struct {
|
||||||
Errors []errorGraphQL `json:"errors"`
|
Errors []errorGraphQL `json:"errors"`
|
||||||
Data struct {
|
Data struct {
|
||||||
@ -879,14 +890,14 @@ type imageListStructForDigestGQL struct {
|
|||||||
type imageListStructForDerivedImagesGQL struct {
|
type imageListStructForDerivedImagesGQL struct {
|
||||||
Errors []errorGraphQL `json:"errors"`
|
Errors []errorGraphQL `json:"errors"`
|
||||||
Data struct {
|
Data struct {
|
||||||
ImageList []imageStruct `json:"DerivedImageList"` //nolint:tagliatelle
|
ImageList DerivedImageList `json:"DerivedImageList"` //nolint:tagliatelle
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type imageListStructForBaseImagesGQL struct {
|
type imageListStructForBaseImagesGQL struct {
|
||||||
Errors []errorGraphQL `json:"errors"`
|
Errors []errorGraphQL `json:"errors"`
|
||||||
Data struct {
|
Data struct {
|
||||||
ImageList []imageStruct `json:"BaseImageList"` //nolint:tagliatelle
|
ImageList BaseImageList `json:"BaseImageList"` //nolint:tagliatelle
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +127,11 @@ type PaginatedReposResult struct {
|
|||||||
Page repodb.PageInfo `json:"page"`
|
Page repodb.PageInfo `json:"page"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PaginatedImagesResult struct {
|
||||||
|
Results []common.ImageSummary `json:"results"`
|
||||||
|
Page repodb.PageInfo `json:"page"`
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
type RepoListWithNewestImage struct {
|
type RepoListWithNewestImage struct {
|
||||||
PaginatedReposResult `json:"RepoListWithNewestImage"`
|
PaginatedReposResult `json:"RepoListWithNewestImage"`
|
||||||
@ -1504,6 +1509,7 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
{10, 10, 10, 10},
|
{10, 10, 10, 10},
|
||||||
{10, 10, 10, 11},
|
{10, 10, 10, 11},
|
||||||
{11, 11, 10, 10},
|
{11, 11, 10, 10},
|
||||||
|
{11, 10, 10, 10},
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest = ispec.Manifest{
|
manifest = ispec.Manifest{
|
||||||
@ -1558,39 +1564,129 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
)
|
)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
query := `
|
manifest = ispec.Manifest{
|
||||||
{
|
Versioned: specs.Versioned{
|
||||||
DerivedImageList(image:"test-repo:latest"){
|
SchemaVersion: 2,
|
||||||
RepoName,
|
},
|
||||||
Tag,
|
Config: ispec.Descriptor{
|
||||||
Digest,
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
ConfigDigest,
|
Digest: configDigest,
|
||||||
LastUpdated,
|
Size: int64(len(configBlob)),
|
||||||
IsSigned,
|
},
|
||||||
Size
|
Layers: []ispec.Descriptor{
|
||||||
}
|
{
|
||||||
}`
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[1]),
|
||||||
|
Size: int64(len(layers[1])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[2]),
|
||||||
|
Size: int64(len(layers[2])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[3]),
|
||||||
|
Size: int64(len(layers[3])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[4]),
|
||||||
|
Size: int64(len(layers[4])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[5]),
|
||||||
|
Size: int64(len(layers[5])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
repoName = "all-layers"
|
||||||
So(resp, ShouldNotBeNil)
|
|
||||||
So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeTrue) //nolint:goconst
|
err = UploadImage(
|
||||||
So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse)
|
Image{
|
||||||
So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeTrue)
|
Manifest: manifest,
|
||||||
|
Config: config,
|
||||||
|
Layers: layers,
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
baseURL,
|
||||||
|
repoName,
|
||||||
|
)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
|
||||||
|
Convey("non paginated query", func() {
|
||||||
|
query := `
|
||||||
|
{
|
||||||
|
DerivedImageList(image:"test-repo:latest"){
|
||||||
|
Results{
|
||||||
|
RepoName,
|
||||||
|
Tag,
|
||||||
|
Digest,
|
||||||
|
ConfigDigest,
|
||||||
|
LastUpdated,
|
||||||
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
|
||||||
|
So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeTrue)
|
||||||
|
So(strings.Contains(string(resp.Body()), "all-layers"), ShouldBeTrue)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("paginated query", func() {
|
||||||
|
query := `
|
||||||
|
{
|
||||||
|
DerivedImageList(image:"test-repo:latest", requestedPage:{limit: 1, offset: 0, sortBy:ALPHABETIC_ASC}){
|
||||||
|
Results{
|
||||||
|
RepoName,
|
||||||
|
Tag,
|
||||||
|
Digest,
|
||||||
|
ConfigDigest,
|
||||||
|
LastUpdated,
|
||||||
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
|
||||||
|
So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "all-layers"), ShouldBeTrue)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Inexistent repository", t, func() {
|
Convey("Inexistent repository", t, func() {
|
||||||
query := `
|
query := `
|
||||||
{
|
{
|
||||||
DerivedImageList(image:"inexistent-image:latest"){
|
DerivedImageList(image:"inexistent-image:latest"){
|
||||||
RepoName,
|
Results {
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
LastUpdated,
|
ConfigDigest,
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -1603,13 +1699,15 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
query := `
|
query := `
|
||||||
{
|
{
|
||||||
DerivedImageList(image:"inexistent-image"){
|
DerivedImageList(image:"inexistent-image"){
|
||||||
RepoName,
|
Results {
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
LastUpdated,
|
ConfigDigest,
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -1628,28 +1726,6 @@ func TestDerivedImageList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
So(contains, ShouldBeTrue)
|
So(contains, ShouldBeTrue)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Failed to get manifest", t, func() {
|
|
||||||
err := os.Mkdir(path.Join(rootDir, "fail-image"), 0o000)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
query := `
|
|
||||||
{
|
|
||||||
DerivedImageList(image:"fail-image:latest"){
|
|
||||||
RepoName,
|
|
||||||
Tag,
|
|
||||||
Digest,
|
|
||||||
ConfigDigest,
|
|
||||||
LastUpdated,
|
|
||||||
IsSigned,
|
|
||||||
Size
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
|
||||||
So(strings.Contains(string(resp.Body()), "permission denied"), ShouldBeTrue)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:dupl
|
//nolint:dupl
|
||||||
@ -1676,19 +1752,25 @@ func TestDerivedImageListNoRepos(t *testing.T) {
|
|||||||
|
|
||||||
query := `
|
query := `
|
||||||
{
|
{
|
||||||
DerivedImageList(image:"test-image:latest"){
|
DerivedImageList(image:"test-image"){
|
||||||
RepoName,
|
Results{
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
LastUpdated,
|
ConfigDigest,
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||||
So(strings.Contains(string(resp.Body()), "{\"data\":{\"DerivedImageList\":[]}}"), ShouldBeTrue)
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
So(strings.Contains(string(resp.Body()), "no reference provided"), ShouldBeTrue)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1915,6 +1997,43 @@ func TestBaseImageList(t *testing.T) {
|
|||||||
)
|
)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// create image with one layer, which is also present in the given image
|
||||||
|
layers = [][]byte{
|
||||||
|
{10, 11, 10, 11},
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest = ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
repoName = "one-layer"
|
||||||
|
|
||||||
|
err = UploadImage(
|
||||||
|
Image{
|
||||||
|
Manifest: manifest,
|
||||||
|
Config: config,
|
||||||
|
Layers: layers,
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
baseURL,
|
||||||
|
repoName,
|
||||||
|
)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
// create image with less layers than the given image, but one layer isn't in the given image
|
// create image with less layers than the given image, but one layer isn't in the given image
|
||||||
layers = [][]byte{
|
layers = [][]byte{
|
||||||
{10, 11, 10, 11},
|
{10, 11, 10, 11},
|
||||||
@ -2062,42 +2181,78 @@ func TestBaseImageList(t *testing.T) {
|
|||||||
)
|
)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
query := `
|
Convey("non paginated query", func() {
|
||||||
{
|
query := `
|
||||||
BaseImageList(image:"test-repo:latest"){
|
{
|
||||||
RepoName,
|
BaseImageList(image:"test-repo:latest"){
|
||||||
Tag,
|
Results{
|
||||||
Digest,
|
RepoName,
|
||||||
ConfigDigest,
|
Tag,
|
||||||
LastUpdated,
|
Digest,
|
||||||
IsSigned,
|
ConfigDigest,
|
||||||
Size
|
LastUpdated,
|
||||||
}
|
IsSigned,
|
||||||
}`
|
Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeTrue) //nolint:goconst
|
So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue)
|
||||||
So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue)
|
So(strings.Contains(string(resp.Body()), "one-layer"), ShouldBeTrue)
|
||||||
So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse)
|
So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
|
||||||
So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
|
So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse)
|
||||||
So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse)
|
So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
|
||||||
So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image
|
So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse)
|
||||||
So(err, ShouldBeNil)
|
So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("paginated query", func() {
|
||||||
|
query := `
|
||||||
|
{
|
||||||
|
BaseImageList(image:"test-repo:latest", requestedPage:{limit: 1, offset: 0, sortBy:RELEVANCE}){
|
||||||
|
Results{
|
||||||
|
RepoName,
|
||||||
|
Tag,
|
||||||
|
Digest,
|
||||||
|
ConfigDigest,
|
||||||
|
LastUpdated,
|
||||||
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue)
|
||||||
|
So(strings.Contains(string(resp.Body()), "one-layer"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst
|
||||||
|
So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse)
|
||||||
|
So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Nonexistent repository", t, func() {
|
Convey("Nonexistent repository", t, func() {
|
||||||
query := `
|
query := `
|
||||||
{
|
{
|
||||||
BaseImageList(image:"nonexistent-image:latest"){
|
BaseImageList(image:"nonexistent-image:latest"){
|
||||||
RepoName,
|
Results{
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
LastUpdated,
|
ConfigDigest,
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -2110,13 +2265,15 @@ func TestBaseImageList(t *testing.T) {
|
|||||||
query := `
|
query := `
|
||||||
{
|
{
|
||||||
BaseImageList(image:"nonexistent-image"){
|
BaseImageList(image:"nonexistent-image"){
|
||||||
RepoName,
|
Results{
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
LastUpdated,
|
ConfigDigest,
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -2135,28 +2292,6 @@ func TestBaseImageList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
So(contains, ShouldBeTrue)
|
So(contains, ShouldBeTrue)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Failed to get manifest", t, func() {
|
|
||||||
err := os.Mkdir(path.Join(rootDir, "fail-image"), 0o000)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
query := `
|
|
||||||
{
|
|
||||||
BaseImageList(image:"fail-image:latest"){
|
|
||||||
RepoName,
|
|
||||||
Tag,
|
|
||||||
Digest,
|
|
||||||
ConfigDigest,
|
|
||||||
LastUpdated,
|
|
||||||
IsSigned,
|
|
||||||
Size
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
|
||||||
So(strings.Contains(string(resp.Body()), "permission denied"), ShouldBeTrue)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:dupl
|
//nolint:dupl
|
||||||
@ -2183,19 +2318,21 @@ func TestBaseImageListNoRepos(t *testing.T) {
|
|||||||
|
|
||||||
query := `
|
query := `
|
||||||
{
|
{
|
||||||
BaseImageList(image:"test-image:latest"){
|
BaseImageList(image:"test-image"){
|
||||||
RepoName,
|
Results{
|
||||||
Tag,
|
RepoName,
|
||||||
Digest,
|
Tag,
|
||||||
ConfigDigest,
|
Digest,
|
||||||
LastUpdated,
|
ConfigDigest,
|
||||||
IsSigned,
|
LastUpdated,
|
||||||
Size
|
IsSigned,
|
||||||
|
Size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
|
||||||
So(strings.Contains(string(resp.Body()), "{\"data\":{\"BaseImageList\":[]}}"), ShouldBeTrue)
|
So(strings.Contains(string(resp.Body()), "no reference provided"), ShouldBeTrue)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -144,9 +144,9 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Query struct {
|
Query struct {
|
||||||
BaseImageList func(childComplexity int, image string) int
|
BaseImageList func(childComplexity int, image string, requestedPage *PageInput) int
|
||||||
CVEListForImage func(childComplexity int, image string, requestedPage *PageInput) int
|
CVEListForImage func(childComplexity int, image string, requestedPage *PageInput) int
|
||||||
DerivedImageList func(childComplexity int, image string) int
|
DerivedImageList func(childComplexity int, image string, requestedPage *PageInput) int
|
||||||
ExpandedRepoInfo func(childComplexity int, repo string) int
|
ExpandedRepoInfo func(childComplexity int, repo string) int
|
||||||
GlobalSearch func(childComplexity int, query string, filter *Filter, requestedPage *PageInput) int
|
GlobalSearch func(childComplexity int, query string, filter *Filter, requestedPage *PageInput) int
|
||||||
Image func(childComplexity int, image string) int
|
Image func(childComplexity int, image string) int
|
||||||
@ -195,8 +195,8 @@ type QueryResolver interface {
|
|||||||
ImageList(ctx context.Context, repo string, requestedPage *PageInput) ([]*ImageSummary, error)
|
ImageList(ctx context.Context, repo string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||||
ExpandedRepoInfo(ctx context.Context, repo string) (*RepoInfo, error)
|
ExpandedRepoInfo(ctx context.Context, repo string) (*RepoInfo, error)
|
||||||
GlobalSearch(ctx context.Context, query string, filter *Filter, requestedPage *PageInput) (*GlobalSearchResult, error)
|
GlobalSearch(ctx context.Context, query string, filter *Filter, requestedPage *PageInput) (*GlobalSearchResult, error)
|
||||||
DerivedImageList(ctx context.Context, image string) ([]*ImageSummary, error)
|
DerivedImageList(ctx context.Context, image string, requestedPage *PageInput) (*PaginatedImagesResult, error)
|
||||||
BaseImageList(ctx context.Context, image string) ([]*ImageSummary, error)
|
BaseImageList(ctx context.Context, image string, requestedPage *PageInput) (*PaginatedImagesResult, error)
|
||||||
Image(ctx context.Context, image string) (*ImageSummary, error)
|
Image(ctx context.Context, image string) (*ImageSummary, error)
|
||||||
Referrers(ctx context.Context, repo string, digest string, typeArg string) ([]*Referrer, error)
|
Referrers(ctx context.Context, repo string, digest string, typeArg string) ([]*Referrer, error)
|
||||||
}
|
}
|
||||||
@ -632,7 +632,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Query.BaseImageList(childComplexity, args["image"].(string)), true
|
return e.complexity.Query.BaseImageList(childComplexity, args["image"].(string), args["requestedPage"].(*PageInput)), true
|
||||||
|
|
||||||
case "Query.CVEListForImage":
|
case "Query.CVEListForImage":
|
||||||
if e.complexity.Query.CVEListForImage == nil {
|
if e.complexity.Query.CVEListForImage == nil {
|
||||||
@ -656,7 +656,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Query.DerivedImageList(childComplexity, args["image"].(string)), true
|
return e.complexity.Query.DerivedImageList(childComplexity, args["image"].(string), args["requestedPage"].(*PageInput)), true
|
||||||
|
|
||||||
case "Query.ExpandedRepoInfo":
|
case "Query.ExpandedRepoInfo":
|
||||||
if e.complexity.Query.ExpandedRepoInfo == nil {
|
if e.complexity.Query.ExpandedRepoInfo == nil {
|
||||||
@ -1193,12 +1193,12 @@ type Query {
|
|||||||
"""
|
"""
|
||||||
List of images which use the argument image
|
List of images which use the argument image
|
||||||
"""
|
"""
|
||||||
DerivedImageList(image: String!): [ImageSummary!]
|
DerivedImageList(image: String!, requestedPage: PageInput): PaginatedImagesResult!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
List of images on which the argument image depends on
|
List of images on which the argument image depends on
|
||||||
"""
|
"""
|
||||||
BaseImageList(image: String!): [ImageSummary!]
|
BaseImageList(image: String!, requestedPage: PageInput): PaginatedImagesResult!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Search for a specific image using its name
|
Search for a specific image using its name
|
||||||
@ -1231,6 +1231,15 @@ func (ec *executionContext) field_Query_BaseImageList_args(ctx context.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
args["image"] = arg0
|
args["image"] = arg0
|
||||||
|
var arg1 *PageInput
|
||||||
|
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||||
|
arg1, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["requestedPage"] = arg1
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1270,6 +1279,15 @@ func (ec *executionContext) field_Query_DerivedImageList_args(ctx context.Contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
args["image"] = arg0
|
args["image"] = arg0
|
||||||
|
var arg1 *PageInput
|
||||||
|
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||||
|
arg1, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["requestedPage"] = arg1
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4814,18 +4832,21 @@ func (ec *executionContext) _Query_DerivedImageList(ctx context.Context, field g
|
|||||||
}()
|
}()
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
ctx = rctx // use context from middleware stack in children
|
||||||
return ec.resolvers.Query().DerivedImageList(rctx, fc.Args["image"].(string))
|
return ec.resolvers.Query().DerivedImageList(rctx, fc.Args["image"].(string), fc.Args["requestedPage"].(*PageInput))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
if resTmp == nil {
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.([]*ImageSummary)
|
res := resTmp.(*PaginatedImagesResult)
|
||||||
fc.Result = res
|
fc.Result = res
|
||||||
return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res)
|
return ec.marshalNPaginatedImagesResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedImagesResult(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_Query_DerivedImageList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_Query_DerivedImageList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
@ -4836,50 +4857,12 @@ func (ec *executionContext) fieldContext_Query_DerivedImageList(ctx context.Cont
|
|||||||
IsResolver: true,
|
IsResolver: true,
|
||||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "RepoName":
|
case "Page":
|
||||||
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
|
return ec.fieldContext_PaginatedImagesResult_Page(ctx, field)
|
||||||
case "Tag":
|
case "Results":
|
||||||
return ec.fieldContext_ImageSummary_Tag(ctx, field)
|
return ec.fieldContext_PaginatedImagesResult_Results(ctx, field)
|
||||||
case "Digest":
|
|
||||||
return ec.fieldContext_ImageSummary_Digest(ctx, field)
|
|
||||||
case "ConfigDigest":
|
|
||||||
return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field)
|
|
||||||
case "LastUpdated":
|
|
||||||
return ec.fieldContext_ImageSummary_LastUpdated(ctx, field)
|
|
||||||
case "IsSigned":
|
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
|
||||||
case "Size":
|
|
||||||
return ec.fieldContext_ImageSummary_Size(ctx, field)
|
|
||||||
case "Platform":
|
|
||||||
return ec.fieldContext_ImageSummary_Platform(ctx, field)
|
|
||||||
case "Vendor":
|
|
||||||
return ec.fieldContext_ImageSummary_Vendor(ctx, field)
|
|
||||||
case "Score":
|
|
||||||
return ec.fieldContext_ImageSummary_Score(ctx, field)
|
|
||||||
case "DownloadCount":
|
|
||||||
return ec.fieldContext_ImageSummary_DownloadCount(ctx, field)
|
|
||||||
case "Layers":
|
|
||||||
return ec.fieldContext_ImageSummary_Layers(ctx, field)
|
|
||||||
case "Description":
|
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
|
||||||
case "Licenses":
|
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
|
||||||
case "Labels":
|
|
||||||
return ec.fieldContext_ImageSummary_Labels(ctx, field)
|
|
||||||
case "Title":
|
|
||||||
return ec.fieldContext_ImageSummary_Title(ctx, field)
|
|
||||||
case "Source":
|
|
||||||
return ec.fieldContext_ImageSummary_Source(ctx, field)
|
|
||||||
case "Documentation":
|
|
||||||
return ec.fieldContext_ImageSummary_Documentation(ctx, field)
|
|
||||||
case "History":
|
|
||||||
return ec.fieldContext_ImageSummary_History(ctx, field)
|
|
||||||
case "Vulnerabilities":
|
|
||||||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
|
||||||
case "Authors":
|
|
||||||
return ec.fieldContext_ImageSummary_Authors(ctx, field)
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type PaginatedImagesResult", field.Name)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -4910,18 +4893,21 @@ func (ec *executionContext) _Query_BaseImageList(ctx context.Context, field grap
|
|||||||
}()
|
}()
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
ctx = rctx // use context from middleware stack in children
|
||||||
return ec.resolvers.Query().BaseImageList(rctx, fc.Args["image"].(string))
|
return ec.resolvers.Query().BaseImageList(rctx, fc.Args["image"].(string), fc.Args["requestedPage"].(*PageInput))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
if resTmp == nil {
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.([]*ImageSummary)
|
res := resTmp.(*PaginatedImagesResult)
|
||||||
fc.Result = res
|
fc.Result = res
|
||||||
return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res)
|
return ec.marshalNPaginatedImagesResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedImagesResult(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_Query_BaseImageList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_Query_BaseImageList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
@ -4932,50 +4918,12 @@ func (ec *executionContext) fieldContext_Query_BaseImageList(ctx context.Context
|
|||||||
IsResolver: true,
|
IsResolver: true,
|
||||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "RepoName":
|
case "Page":
|
||||||
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
|
return ec.fieldContext_PaginatedImagesResult_Page(ctx, field)
|
||||||
case "Tag":
|
case "Results":
|
||||||
return ec.fieldContext_ImageSummary_Tag(ctx, field)
|
return ec.fieldContext_PaginatedImagesResult_Results(ctx, field)
|
||||||
case "Digest":
|
|
||||||
return ec.fieldContext_ImageSummary_Digest(ctx, field)
|
|
||||||
case "ConfigDigest":
|
|
||||||
return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field)
|
|
||||||
case "LastUpdated":
|
|
||||||
return ec.fieldContext_ImageSummary_LastUpdated(ctx, field)
|
|
||||||
case "IsSigned":
|
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
|
||||||
case "Size":
|
|
||||||
return ec.fieldContext_ImageSummary_Size(ctx, field)
|
|
||||||
case "Platform":
|
|
||||||
return ec.fieldContext_ImageSummary_Platform(ctx, field)
|
|
||||||
case "Vendor":
|
|
||||||
return ec.fieldContext_ImageSummary_Vendor(ctx, field)
|
|
||||||
case "Score":
|
|
||||||
return ec.fieldContext_ImageSummary_Score(ctx, field)
|
|
||||||
case "DownloadCount":
|
|
||||||
return ec.fieldContext_ImageSummary_DownloadCount(ctx, field)
|
|
||||||
case "Layers":
|
|
||||||
return ec.fieldContext_ImageSummary_Layers(ctx, field)
|
|
||||||
case "Description":
|
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
|
||||||
case "Licenses":
|
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
|
||||||
case "Labels":
|
|
||||||
return ec.fieldContext_ImageSummary_Labels(ctx, field)
|
|
||||||
case "Title":
|
|
||||||
return ec.fieldContext_ImageSummary_Title(ctx, field)
|
|
||||||
case "Source":
|
|
||||||
return ec.fieldContext_ImageSummary_Source(ctx, field)
|
|
||||||
case "Documentation":
|
|
||||||
return ec.fieldContext_ImageSummary_Documentation(ctx, field)
|
|
||||||
case "History":
|
|
||||||
return ec.fieldContext_ImageSummary_History(ctx, field)
|
|
||||||
case "Vulnerabilities":
|
|
||||||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
|
||||||
case "Authors":
|
|
||||||
return ec.fieldContext_ImageSummary_Authors(ctx, field)
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type PaginatedImagesResult", field.Name)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -8757,6 +8705,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
res = ec._Query_DerivedImageList(ctx, field)
|
res = ec._Query_DerivedImageList(ctx, field)
|
||||||
|
if res == graphql.Null {
|
||||||
|
atomic.AddUint32(&invalids, 1)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -8777,6 +8728,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
res = ec._Query_BaseImageList(ctx, field)
|
res = ec._Query_BaseImageList(ctx, field)
|
||||||
|
if res == graphql.Null {
|
||||||
|
atomic.AddUint32(&invalids, 1)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9459,6 +9413,20 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalNPaginatedImagesResult2zotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedImagesResult(ctx context.Context, sel ast.SelectionSet, v PaginatedImagesResult) graphql.Marshaler {
|
||||||
|
return ec._PaginatedImagesResult(ctx, sel, &v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalNPaginatedImagesResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedImagesResult(ctx context.Context, sel ast.SelectionSet, v *PaginatedImagesResult) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||||
|
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._PaginatedImagesResult(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalNPaginatedReposResult2zotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedReposResult(ctx context.Context, sel ast.SelectionSet, v PaginatedReposResult) graphql.Marshaler {
|
func (ec *executionContext) marshalNPaginatedReposResult2zotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPaginatedReposResult(ctx context.Context, sel ast.SelectionSet, v PaginatedReposResult) graphql.Marshaler {
|
||||||
return ec._PaginatedReposResult(ctx, sel, &v)
|
return ec._PaginatedReposResult(ctx, sel, &v)
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ func getImageListForDigest(ctx context.Context, digest string, repoDB repodb.Rep
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get all repos
|
// get all repos
|
||||||
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx, FilterByDigest(digest), pageInput)
|
reposMeta, manifestMetaMap, _, err := repoDB.FilterTags(ctx, FilterByDigest(digest), pageInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*gql_generated.ImageSummary{}, err
|
return []*gql_generated.ImageSummary{}, err
|
||||||
}
|
}
|
||||||
@ -336,7 +336,7 @@ func getImageListForCVE(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get all repos
|
// get all repos
|
||||||
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx, FilterByTagInfo(affectedImages), pageInput)
|
reposMeta, manifestMetaMap, _, err := repoDB.FilterTags(ctx, FilterByTagInfo(affectedImages), pageInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*gql_generated.ImageSummary{}, err
|
return []*gql_generated.ImageSummary{}, err
|
||||||
}
|
}
|
||||||
@ -388,7 +388,7 @@ func getImageListWithCVEFixed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get all repos
|
// get all repos
|
||||||
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx, FilterByTagInfo(tagsInfo), pageInput)
|
reposMeta, manifestMetaMap, _, err := repoDB.FilterTags(ctx, FilterByTagInfo(tagsInfo), pageInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*gql_generated.ImageSummary{}, err
|
return []*gql_generated.ImageSummary{}, err
|
||||||
}
|
}
|
||||||
@ -542,6 +542,225 @@ func canSkipField(preloads map[string]bool, s string) bool {
|
|||||||
return !fieldIsPresent
|
return !fieldIsPresent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func derivedImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
|
||||||
|
requestedPage *gql_generated.PageInput,
|
||||||
|
cveInfo cveinfo.CveInfo, log log.Logger,
|
||||||
|
) (*gql_generated.PaginatedImagesResult, error) {
|
||||||
|
derivedList := make([]*gql_generated.ImageSummary, 0)
|
||||||
|
|
||||||
|
if requestedPage == nil {
|
||||||
|
requestedPage = &gql_generated.PageInput{}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageInput := repodb.PageInput{
|
||||||
|
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||||
|
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||||
|
SortBy: repodb.SortCriteria(
|
||||||
|
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
skip := convert.SkipQGLField{
|
||||||
|
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
|
||||||
|
}
|
||||||
|
|
||||||
|
imageRepo, imageTag := common.GetImageDirAndTag(image)
|
||||||
|
if imageTag == "" {
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, repoDB, cveInfo, log)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need all available tags
|
||||||
|
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx,
|
||||||
|
filterDerivedImages(searchedImage),
|
||||||
|
pageInput)
|
||||||
|
if err != nil {
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repoMeta := range reposMeta {
|
||||||
|
summary := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
||||||
|
derivedList = append(derivedList, summary...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(derivedList) == 0 {
|
||||||
|
log.Info().Msg("no images found")
|
||||||
|
|
||||||
|
return &gql_generated.PaginatedImagesResult{
|
||||||
|
Page: &gql_generated.PageInfo{},
|
||||||
|
Results: derivedList,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql_generated.PaginatedImagesResult{
|
||||||
|
Results: derivedList,
|
||||||
|
Page: &gql_generated.PageInfo{
|
||||||
|
TotalCount: pageInfo.TotalCount,
|
||||||
|
ItemCount: pageInfo.ItemCount,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterDerivedImages(image *gql_generated.ImageSummary) repodb.FilterFunc {
|
||||||
|
return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
|
var addImageToList bool
|
||||||
|
|
||||||
|
var imageManifest ispec.Manifest
|
||||||
|
|
||||||
|
err := json.Unmarshal(manifestMeta.ManifestBlob, &imageManifest)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
||||||
|
if manifestDigest == *image.Digest {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
imageLayers := image.Layers
|
||||||
|
|
||||||
|
addImageToList = false
|
||||||
|
layers := imageManifest.Layers
|
||||||
|
|
||||||
|
sameLayer := 0
|
||||||
|
|
||||||
|
for _, l := range imageLayers {
|
||||||
|
for _, k := range layers {
|
||||||
|
if k.Digest.String() == *l.Digest {
|
||||||
|
sameLayer++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if all layers are the same
|
||||||
|
if sameLayer == len(imageLayers) {
|
||||||
|
// it's a derived image
|
||||||
|
addImageToList = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return addImageToList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
|
||||||
|
requestedPage *gql_generated.PageInput,
|
||||||
|
cveInfo cveinfo.CveInfo, log log.Logger,
|
||||||
|
) (*gql_generated.PaginatedImagesResult, error) {
|
||||||
|
imageSummaries := make([]*gql_generated.ImageSummary, 0)
|
||||||
|
|
||||||
|
if requestedPage == nil {
|
||||||
|
requestedPage = &gql_generated.PageInput{}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageInput := repodb.PageInput{
|
||||||
|
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||||
|
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||||
|
SortBy: repodb.SortCriteria(
|
||||||
|
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
skip := convert.SkipQGLField{
|
||||||
|
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
|
||||||
|
}
|
||||||
|
|
||||||
|
imageRepo, imageTag := common.GetImageDirAndTag(image)
|
||||||
|
|
||||||
|
if imageTag == "" {
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, repoDB, cveInfo, log)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need all available tags
|
||||||
|
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx,
|
||||||
|
filterBaseImages(searchedImage),
|
||||||
|
pageInput)
|
||||||
|
if err != nil {
|
||||||
|
return &gql_generated.PaginatedImagesResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repoMeta := range reposMeta {
|
||||||
|
summary := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
||||||
|
imageSummaries = append(imageSummaries, summary...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imageSummaries) == 0 {
|
||||||
|
log.Info().Msg("no images found")
|
||||||
|
|
||||||
|
return &gql_generated.PaginatedImagesResult{
|
||||||
|
Results: imageSummaries,
|
||||||
|
Page: &gql_generated.PageInfo{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql_generated.PaginatedImagesResult{
|
||||||
|
Page: &gql_generated.PageInfo{
|
||||||
|
TotalCount: pageInfo.TotalCount,
|
||||||
|
ItemCount: pageInfo.ItemCount,
|
||||||
|
},
|
||||||
|
Results: imageSummaries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterBaseImages(image *gql_generated.ImageSummary) repodb.FilterFunc {
|
||||||
|
return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
|
var addImageToList bool
|
||||||
|
|
||||||
|
var imageManifest ispec.Manifest
|
||||||
|
|
||||||
|
err := json.Unmarshal(manifestMeta.ManifestBlob, &imageManifest)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
||||||
|
if manifestDigest == *image.Digest {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
imageLayers := image.Layers
|
||||||
|
|
||||||
|
addImageToList = true
|
||||||
|
layers := imageManifest.Layers
|
||||||
|
|
||||||
|
for _, l := range layers {
|
||||||
|
foundLayer := false
|
||||||
|
|
||||||
|
for _, k := range imageLayers {
|
||||||
|
if l.Digest.String() == *k.Digest {
|
||||||
|
foundLayer = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundLayer {
|
||||||
|
addImageToList = false
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addImageToList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
|
func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
|
||||||
requestedPage *gql_generated.PageInput,
|
requestedPage *gql_generated.PageInput,
|
||||||
) error {
|
) error {
|
||||||
@ -765,7 +984,7 @@ func getImageList(ctx context.Context, repo string, repoDB repodb.RepoDB, cveInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reposMeta, manifestMetaMap, err := repoDB.SearchRepos(ctx, repo, repodb.Filter{}, pageInput)
|
// reposMeta, manifestMetaMap, err := repoDB.SearchRepos(ctx, repo, repodb.Filter{}, pageInput)
|
||||||
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx,
|
reposMeta, manifestMetaMap, _, err := repoDB.FilterTags(ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/opencontainers/image-spec/specs-go"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
@ -562,8 +563,8 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, ErrTestError
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,7 +578,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@ -604,7 +605,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,7 +626,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@ -658,7 +659,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
|
|
||||||
repos[0].Tags = matchedTags
|
repos[0].Tags = matchedTags
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -698,7 +699,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@ -736,7 +737,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
|
|
||||||
repos[0].Tags = matchedTags
|
repos[0].Tags = matchedTags
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,7 +773,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@ -812,7 +813,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
|
|
||||||
repos[0].Tags = matchedTags
|
repos[0].Tags = matchedTags
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -846,7 +847,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@ -881,7 +882,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
repos[i].Tags = matchedTags
|
repos[i].Tags = matchedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,10 +916,10 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
@ -961,7 +962,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||||||
|
|
||||||
repos, _ = pageFinder.Page()
|
repos, _ = pageFinder.Page()
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -992,8 +993,8 @@ func TestImageList(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, ErrTestError
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,7 +1009,7 @@ func TestImageList(t *testing.T) {
|
|||||||
mockSearchDB := mocks.RepoDBMock{
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
repos := []repodb.RepoMetadata{
|
repos := []repodb.RepoMetadata{
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
@ -1052,7 +1053,7 @@ func TestImageList(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, nil
|
return repos, manifestMetaDatas, repodb.PageInfo{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1273,6 +1274,42 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||||||
log := log.NewLogger("debug", "")
|
log := log.NewLogger("debug", "")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
Convey("GlobalSearch error bad requested page", func() {
|
||||||
|
resolverConfig := NewResolver(
|
||||||
|
log,
|
||||||
|
storage.StoreController{},
|
||||||
|
mocks.RepoDBMock{},
|
||||||
|
mocks.CveInfoMock{},
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver := queryResolver{
|
||||||
|
resolverConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := -1
|
||||||
|
offset := 0
|
||||||
|
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||||
|
pageInput := gql_generated.PageInput{
|
||||||
|
Limit: &limit,
|
||||||
|
Offset: &offset,
|
||||||
|
SortBy: &sortCriteria,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
limit = 0
|
||||||
|
offset = -1
|
||||||
|
pageInput = gql_generated.PageInput{
|
||||||
|
Limit: &limit,
|
||||||
|
Offset: &offset,
|
||||||
|
SortBy: &sortCriteria,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
|
Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
|
||||||
resolverConfig := NewResolver(
|
resolverConfig := NewResolver(
|
||||||
log,
|
log,
|
||||||
@ -1306,8 +1343,8 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||||||
mocks.RepoDBMock{
|
mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, ErrTestError
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mocks.CveInfoMock{},
|
mocks.CveInfoMock{},
|
||||||
@ -1330,8 +1367,8 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||||||
mocks.RepoDBMock{
|
mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, ErrTestError
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mocks.CveInfoMock{},
|
mocks.CveInfoMock{},
|
||||||
@ -1398,8 +1435,8 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||||||
mocks.RepoDBMock{
|
mocks.RepoDBMock{
|
||||||
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, ErrTestError
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, ErrTestError
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mocks.CveInfoMock{},
|
mocks.CveInfoMock{},
|
||||||
@ -1438,7 +1475,7 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||||||
resolverConfig,
|
resolverConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := qr.DerivedImageList(ctx, "repo:tag")
|
_, err := qr.DerivedImageList(ctx, "repo:tag", &gql_generated.PageInput{})
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1467,7 +1504,60 @@ func TestQueryResolverErrors(t *testing.T) {
|
|||||||
resolverConfig,
|
resolverConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := qr.BaseImageList(ctx, "repo:tag")
|
_, err := qr.BaseImageList(ctx, "repo:tag", &gql_generated.PageInput{})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("DerivedImageList and BaseImage List FilterTags() errors", func() {
|
||||||
|
configBlob, err := json.Marshal(ispec.Image{
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifest := ispec.Manifest{}
|
||||||
|
|
||||||
|
manifestBlob, err := json.Marshal(manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||||
|
|
||||||
|
resolverConfig := NewResolver(
|
||||||
|
log,
|
||||||
|
storage.StoreController{},
|
||||||
|
mocks.RepoDBMock{
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, ErrTestError
|
||||||
|
},
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Name: "repo",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mocks.CveInfoMock{},
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver := queryResolver{
|
||||||
|
resolverConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = resolver.DerivedImageList(ctx, "repo:tag", &gql_generated.PageInput{})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = resolver.BaseImageList(ctx, "repo:tag", &gql_generated.PageInput{})
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2182,3 +2272,675 @@ func getPageInput(limit int, offset int) *gql_generated.PageInput {
|
|||||||
SortBy: &sortCriteria,
|
SortBy: &sortCriteria,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDerivedImageList(t *testing.T) {
|
||||||
|
Convey("RepoDB FilterTags error", t, func() {
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
return make([]repodb.RepoMetadata, 0), make(map[string]repodb.ManifestMetadata), repodb.PageInfo{}, ErrTestError
|
||||||
|
},
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{}, ErrTestError
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{}, ErrTestError
|
||||||
|
},
|
||||||
|
}
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
images, err := derivedImageList(responseContext, "repo1:1.0.1", mockSearchDB, &gql_generated.PageInput{},
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(images.Results, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
//nolint: dupl
|
||||||
|
Convey("RepoDB FilterTags no repo available", t, func() {
|
||||||
|
configBlob, err := json.Marshal(ispec.Image{
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifest := ispec.Manifest{}
|
||||||
|
|
||||||
|
manifestBlob, err := json.Marshal(manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||||
|
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
return make([]repodb.RepoMetadata, 0), make(map[string]repodb.ManifestMetadata), repodb.PageInfo{}, nil
|
||||||
|
},
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.1": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
images, err := derivedImageList(responseContext, "repo1:1.0.1", mockSearchDB, &gql_generated.PageInput{},
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
//nolint: dupl
|
||||||
|
Convey("derived image list working", t, func() {
|
||||||
|
configBlob, err := json.Marshal(ispec.Image{
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configDigest := godigest.FromBytes(configBlob)
|
||||||
|
|
||||||
|
layers := [][]byte{
|
||||||
|
{10, 11, 10, 11},
|
||||||
|
{11, 11, 11, 11},
|
||||||
|
{10, 10, 10, 11},
|
||||||
|
{13, 14, 15, 11},
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestBlob, err := json.Marshal(ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[1]),
|
||||||
|
Size: int64(len(layers[1])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[2]),
|
||||||
|
Size: int64(len(layers[2])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
derivedManifestBlob, err := json.Marshal(ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[1]),
|
||||||
|
Size: int64(len(layers[1])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[2]),
|
||||||
|
Size: int64(len(layers[2])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[3]),
|
||||||
|
Size: int64(len(layers[3])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestMetas := map[string]repodb.ManifestMetadata{
|
||||||
|
"digestTag1.0.1": {
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
"digestTag1.0.2": {
|
||||||
|
ManifestBlob: derivedManifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
"digestTag1.0.3": {
|
||||||
|
ManifestBlob: derivedManifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||||
|
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.1": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repos := []repodb.RepoMetadata{
|
||||||
|
{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
"1.0.2": {Digest: "digestTag1.0.2", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
"1.0.3": {Digest: "digestTag1.0.3", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
Stars: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, repo := range repos {
|
||||||
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
|
for tag, descriptor := range repo.Tags {
|
||||||
|
if !filter(repo, manifestMetas[descriptor.Digest]) {
|
||||||
|
delete(matchedTags, tag)
|
||||||
|
delete(manifestMetas, descriptor.Digest)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repos[i].Tags = matchedTags
|
||||||
|
|
||||||
|
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||||
|
RepoMeta: repo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
repos, pageInfo := pageFinder.Page()
|
||||||
|
|
||||||
|
return repos, manifestMetas, pageInfo, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
|
||||||
|
Convey("valid derivedImageList, results not affected by pageInput", func() {
|
||||||
|
images, err := derivedImageList(responseContext, "repo1:1.0.1", mockSearchDB, &gql_generated.PageInput{},
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldNotBeEmpty)
|
||||||
|
So(len(images.Results), ShouldEqual, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("valid derivedImageList, results affected by pageInput", func() {
|
||||||
|
limit := 1
|
||||||
|
offset := 0
|
||||||
|
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||||
|
pageInput := gql_generated.PageInput{
|
||||||
|
Limit: &limit,
|
||||||
|
Offset: &offset,
|
||||||
|
SortBy: &sortCriteria,
|
||||||
|
}
|
||||||
|
|
||||||
|
images, err := derivedImageList(responseContext, "repo1:1.0.1", mockSearchDB, &pageInput,
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldNotBeEmpty)
|
||||||
|
So(len(images.Results), ShouldEqual, limit)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseImageList(t *testing.T) {
|
||||||
|
Convey("RepoDB FilterTags error", t, func() {
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
return make([]repodb.RepoMetadata, 0), make(map[string]repodb.ManifestMetadata), repodb.PageInfo{}, ErrTestError
|
||||||
|
},
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{}, ErrTestError
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{}, ErrTestError
|
||||||
|
},
|
||||||
|
}
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
images, err := baseImageList(responseContext, "repo1:1.0.2", mockSearchDB, &gql_generated.PageInput{},
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(images.Results, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
//nolint: dupl
|
||||||
|
Convey("RepoDB FilterTags no repo available", t, func() {
|
||||||
|
configBlob, err := json.Marshal(ispec.Image{
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifest := ispec.Manifest{}
|
||||||
|
|
||||||
|
manifestBlob, err := json.Marshal(manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||||
|
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
return make([]repodb.RepoMetadata, 0), make(map[string]repodb.ManifestMetadata), repodb.PageInfo{}, nil
|
||||||
|
},
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.2": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
images, err := baseImageList(responseContext, "repo1:1.0.2", mockSearchDB, &gql_generated.PageInput{},
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
//nolint: dupl
|
||||||
|
Convey("base image list working", t, func() {
|
||||||
|
configBlob, err := json.Marshal(ispec.Image{
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configDigest := godigest.FromBytes(configBlob)
|
||||||
|
|
||||||
|
layers := [][]byte{
|
||||||
|
{10, 11, 10, 11},
|
||||||
|
{11, 11, 11, 11},
|
||||||
|
{10, 10, 10, 11},
|
||||||
|
{13, 14, 15, 11},
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestBlob, err := json.Marshal(ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[1]),
|
||||||
|
Size: int64(len(layers[1])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[2]),
|
||||||
|
Size: int64(len(layers[2])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
derivedManifestBlob, err := json.Marshal(ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[1]),
|
||||||
|
Size: int64(len(layers[1])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[2]),
|
||||||
|
Size: int64(len(layers[2])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[3]),
|
||||||
|
Size: int64(len(layers[3])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestMetas := map[string]repodb.ManifestMetadata{
|
||||||
|
"digestTag1.0.1": {
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
"digestTag1.0.2": {
|
||||||
|
ManifestBlob: derivedManifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
derivedManifestDigest := godigest.FromBytes(derivedManifestBlob)
|
||||||
|
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.2": {Digest: derivedManifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: derivedManifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repos := []repodb.RepoMetadata{
|
||||||
|
{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
"1.0.3": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
"1.0.2": {Digest: "digestTag1.0.2", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
Stars: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, repo := range repos {
|
||||||
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
|
for tag, descriptor := range repo.Tags {
|
||||||
|
if !filter(repo, manifestMetas[descriptor.Digest]) {
|
||||||
|
delete(matchedTags, tag)
|
||||||
|
delete(manifestMetas, descriptor.Digest)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repos[i].Tags = matchedTags
|
||||||
|
|
||||||
|
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||||
|
RepoMeta: repo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
repos, pageInfo := pageFinder.Page()
|
||||||
|
|
||||||
|
return repos, manifestMetas, pageInfo, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
|
||||||
|
Convey("valid baseImageList, results not affected by pageInput", func() {
|
||||||
|
images, err := baseImageList(responseContext, "repo1:1.0.2", mockSearchDB,
|
||||||
|
&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldNotBeEmpty)
|
||||||
|
So(len(images.Results), ShouldEqual, 2)
|
||||||
|
expectedTags := []string{"1.0.1", "1.0.3"}
|
||||||
|
So(expectedTags, ShouldContain, *images.Results[0].Tag)
|
||||||
|
So(expectedTags, ShouldContain, *images.Results[1].Tag)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("valid baseImageList, results affected by pageInput", func() {
|
||||||
|
limit := 1
|
||||||
|
offset := 0
|
||||||
|
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||||
|
pageInput := gql_generated.PageInput{
|
||||||
|
Limit: &limit,
|
||||||
|
Offset: &offset,
|
||||||
|
SortBy: &sortCriteria,
|
||||||
|
}
|
||||||
|
|
||||||
|
images, err := baseImageList(responseContext, "repo1:1.0.2", mockSearchDB,
|
||||||
|
&pageInput, mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldNotBeEmpty)
|
||||||
|
So(len(images.Results), ShouldEqual, limit)
|
||||||
|
So(*images.Results[0].Tag, ShouldEqual, "1.0.1")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//nolint: dupl
|
||||||
|
Convey("filterTags working, no base image list found", t, func() {
|
||||||
|
configBlob, err := json.Marshal(ispec.Image{
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configDigest := godigest.FromBytes(configBlob)
|
||||||
|
|
||||||
|
layers := [][]byte{
|
||||||
|
{10, 11, 10, 11},
|
||||||
|
{11, 11, 11, 11},
|
||||||
|
{10, 10, 10, 11},
|
||||||
|
{13, 14, 15, 11},
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestBlob, err := json.Marshal(ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[0]),
|
||||||
|
Size: int64(len(layers[0])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[1]),
|
||||||
|
Size: int64(len(layers[1])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[2]),
|
||||||
|
Size: int64(len(layers[2])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
derivedManifestBlob, err := json.Marshal(ispec.Manifest{
|
||||||
|
Versioned: specs.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{
|
||||||
|
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||||
|
Digest: configDigest,
|
||||||
|
Size: int64(len(configBlob)),
|
||||||
|
},
|
||||||
|
Layers: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||||
|
Digest: godigest.FromBytes(layers[3]),
|
||||||
|
Size: int64(len(layers[3])),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestMetas := map[string]repodb.ManifestMetadata{
|
||||||
|
"digestTag1.0.1": {
|
||||||
|
ManifestBlob: manifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
"digestTag1.0.2": {
|
||||||
|
ManifestBlob: derivedManifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
DownloadCount: 100,
|
||||||
|
Signatures: make(repodb.ManifestSignatures),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
derivedManifestDigest := godigest.FromBytes(derivedManifestBlob)
|
||||||
|
|
||||||
|
mockSearchDB := mocks.RepoDBMock{
|
||||||
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.2": {Digest: derivedManifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||||
|
return repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: derivedManifestBlob,
|
||||||
|
ConfigBlob: configBlob,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
FilterTagsFn: func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
|
requestedPage repodb.PageInput,
|
||||||
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repos := []repodb.RepoMetadata{
|
||||||
|
{
|
||||||
|
Name: "repo1",
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
"1.0.2": {Digest: "digestTag1.0.2", MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
},
|
||||||
|
Stars: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, repo := range repos {
|
||||||
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
|
for tag, descriptor := range repo.Tags {
|
||||||
|
if !filter(repo, manifestMetas[descriptor.Digest]) {
|
||||||
|
delete(matchedTags, tag)
|
||||||
|
delete(manifestMetas, descriptor.Digest)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repos[i].Tags = matchedTags
|
||||||
|
|
||||||
|
pageFinder.Add(repodb.DetailedRepoMeta{
|
||||||
|
RepoMeta: repo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos, manifestMetas, repodb.PageInfo{}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
graphql.DefaultRecover)
|
||||||
|
|
||||||
|
mockCve := mocks.CveInfoMock{}
|
||||||
|
images, err := baseImageList(responseContext, "repo1:1.0.2", mockSearchDB, &gql_generated.PageInput{},
|
||||||
|
mockCve, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(images.Results, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -244,12 +244,12 @@ type Query {
|
|||||||
"""
|
"""
|
||||||
List of images which use the argument image
|
List of images which use the argument image
|
||||||
"""
|
"""
|
||||||
DerivedImageList(image: String!): [ImageSummary!]
|
DerivedImageList(image: String!, requestedPage: PageInput): PaginatedImagesResult!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
List of images on which the argument image depends on
|
List of images on which the argument image depends on
|
||||||
"""
|
"""
|
||||||
BaseImageList(image: String!): [ImageSummary!]
|
BaseImageList(image: String!, requestedPage: PageInput): PaginatedImagesResult!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Search for a specific image using its name
|
Search for a specific image using its name
|
||||||
|
@ -104,157 +104,17 @@ func (r *queryResolver) GlobalSearch(ctx context.Context, query string, filter *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DependencyListForImage is the resolver for the DependencyListForImage field.
|
// DependencyListForImage is the resolver for the DependencyListForImage field.
|
||||||
func (r *queryResolver) DerivedImageList(ctx context.Context, image string) ([]*gql_generated.ImageSummary, error) {
|
func (r *queryResolver) DerivedImageList(ctx context.Context, image string, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) {
|
||||||
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
derivedList, err := derivedImageList(ctx, image, r.repoDB, requestedPage, r.cveInfo, r.log)
|
||||||
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
||||||
|
|
||||||
repoList, err := layoutUtils.GetRepositories()
|
return derivedList, err
|
||||||
if err != nil {
|
|
||||||
r.log.Error().Err(err).Msg("unable to get repositories list")
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(repoList) == 0 {
|
|
||||||
r.log.Info().Msg("no repositories found")
|
|
||||||
|
|
||||||
return imageList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
imageDir, imageTag := common.GetImageDirAndTag(image)
|
|
||||||
|
|
||||||
if imageTag == "" {
|
|
||||||
return []*gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag)
|
|
||||||
if err != nil {
|
|
||||||
r.log.Info().Str("image", image).Msg("image not found")
|
|
||||||
|
|
||||||
return imageList, err
|
|
||||||
}
|
|
||||||
|
|
||||||
imageLayers := imageManifest.Layers
|
|
||||||
|
|
||||||
for _, repo := range repoList {
|
|
||||||
repoInfo, err := r.ExpandedRepoInfo(ctx, repo)
|
|
||||||
if err != nil {
|
|
||||||
r.log.Error().Err(err).Msg("unable to get image list")
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
imageSummaries := repoInfo.Images
|
|
||||||
|
|
||||||
// verify every image
|
|
||||||
for _, imageSummary := range imageSummaries {
|
|
||||||
if imageTag == *imageSummary.Tag && imageDir == repo {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
layers := imageSummary.Layers
|
|
||||||
|
|
||||||
sameLayer := 0
|
|
||||||
|
|
||||||
for _, l := range imageLayers {
|
|
||||||
for _, k := range layers {
|
|
||||||
if *k.Digest == l.Digest.String() {
|
|
||||||
sameLayer++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if all layers are the same
|
|
||||||
if sameLayer == len(imageLayers) {
|
|
||||||
// add to returned list
|
|
||||||
imageList = append(imageList, imageSummary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageList, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseImageList is the resolver for the BaseImageList field.
|
// BaseImageList is the resolver for the BaseImageList field.
|
||||||
func (r *queryResolver) BaseImageList(ctx context.Context, image string) ([]*gql_generated.ImageSummary, error) {
|
func (r *queryResolver) BaseImageList(ctx context.Context, image string, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) {
|
||||||
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
imageList, err := baseImageList(ctx, image, r.repoDB, requestedPage, r.cveInfo, r.log)
|
||||||
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
||||||
|
|
||||||
repoList, err := layoutUtils.GetRepositories()
|
return imageList, err
|
||||||
if err != nil {
|
|
||||||
r.log.Error().Err(err).Msg("unable to get repositories list")
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(repoList) == 0 {
|
|
||||||
r.log.Info().Msg("no repositories found")
|
|
||||||
|
|
||||||
return imageList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
imageDir, imageTag := common.GetImageDirAndTag(image)
|
|
||||||
|
|
||||||
if imageTag == "" {
|
|
||||||
return []*gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag)
|
|
||||||
if err != nil {
|
|
||||||
r.log.Info().Str("image", image).Msg("image not found")
|
|
||||||
|
|
||||||
return imageList, err
|
|
||||||
}
|
|
||||||
|
|
||||||
imageLayers := imageManifest.Layers
|
|
||||||
|
|
||||||
// This logic may not scale well in the future as we need to read all the
|
|
||||||
// manifest files from the disk when the call is made, we should improve in a future PR
|
|
||||||
for _, repo := range repoList {
|
|
||||||
repoInfo, err := r.ExpandedRepoInfo(ctx, repo)
|
|
||||||
if err != nil {
|
|
||||||
r.log.Error().Err(err).Msg("unable to get image list")
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
imageSummaries := repoInfo.Images
|
|
||||||
|
|
||||||
var addImageToList bool
|
|
||||||
// verify every image
|
|
||||||
for _, imageSummary := range imageSummaries {
|
|
||||||
if imageTag == *imageSummary.Tag && imageDir == repo {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addImageToList = true
|
|
||||||
layers := imageSummary.Layers
|
|
||||||
|
|
||||||
for _, l := range layers {
|
|
||||||
foundLayer := false
|
|
||||||
|
|
||||||
for _, k := range imageLayers {
|
|
||||||
if *l.Digest == k.Digest.String() {
|
|
||||||
foundLayer = true
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !foundLayer {
|
|
||||||
addImageToList = false
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if addImageToList {
|
|
||||||
imageList = append(imageList, imageSummary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageList, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image is the resolver for the Image field.
|
// Image is the resolver for the Image field.
|
||||||
|
@ -746,16 +746,17 @@ func (bdw DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
|
|||||||
|
|
||||||
func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
var (
|
var (
|
||||||
foundRepos = make([]repodb.RepoMetadata, 0)
|
foundRepos = make([]repodb.RepoMetadata, 0)
|
||||||
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
||||||
pageFinder repodb.PageFinder
|
pageFinder repodb.PageFinder
|
||||||
|
pageInfo repodb.PageInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bdw.DB.View(func(tx *bolt.Tx) error {
|
err = bdw.DB.View(func(tx *bolt.Tx) error {
|
||||||
@ -836,7 +837,7 @@ func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
foundRepos, _ = pageFinder.Page()
|
foundRepos, pageInfo = pageFinder.Page()
|
||||||
|
|
||||||
// keep just the manifestMeta we need
|
// keep just the manifestMeta we need
|
||||||
for _, repoMeta := range foundRepos {
|
for _, repoMeta := range foundRepos {
|
||||||
@ -848,7 +849,7 @@ func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return foundRepos, foundManifestMetadataMap, err
|
return foundRepos, foundManifestMetadataMap, pageInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
|
func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
|
||||||
|
@ -398,7 +398,7 @@ func TestWrapperErrors(t *testing.T) {
|
|||||||
err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck
|
err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
_, _, err = dynamoWrapper.FilterTags(
|
_, _, _, err = dynamoWrapper.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
@ -413,7 +413,7 @@ func TestWrapperErrors(t *testing.T) {
|
|||||||
err := dynamoWrapper.SetRepoTag("repo", "tag1", "manifestNotFound", "") //nolint:contextcheck
|
err := dynamoWrapper.SetRepoTag("repo", "tag1", "manifestNotFound", "") //nolint:contextcheck
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
_, _, err = dynamoWrapper.FilterTags(
|
_, _, _, err = dynamoWrapper.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
@ -431,7 +431,7 @@ func TestWrapperErrors(t *testing.T) {
|
|||||||
err = setBadManifestData(dynamoWrapper.Client, manifestDataTablename, "dig") //nolint:contextcheck
|
err = setBadManifestData(dynamoWrapper.Client, manifestDataTablename, "dig") //nolint:contextcheck
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
_, _, err = dynamoWrapper.FilterTags(
|
_, _, _, err = dynamoWrapper.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
@ -452,7 +452,7 @@ func TestWrapperErrors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
_, _, err = dynamoWrapper.FilterTags(
|
_, _, _, err = dynamoWrapper.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
|
@ -653,12 +653,13 @@ func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
|
|||||||
|
|
||||||
func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
var (
|
var (
|
||||||
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
||||||
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
||||||
pageFinder repodb.PageFinder
|
pageFinder repodb.PageFinder
|
||||||
repoMetaAttributeIterator iterator.AttributesIterator
|
repoMetaAttributeIterator iterator.AttributesIterator
|
||||||
|
pageInfo repodb.PageInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
repoMetaAttributeIterator = iterator.NewBaseDynamoAttributesIterator(
|
repoMetaAttributeIterator = iterator.NewBaseDynamoAttributesIterator(
|
||||||
@ -667,7 +668,7 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
|
|
||||||
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
|
repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
|
||||||
@ -675,14 +676,14 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
|
for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log
|
// log
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var repoMeta repodb.RepoMetadata
|
var repoMeta repodb.RepoMetadata
|
||||||
|
|
||||||
err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta)
|
err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil {
|
if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil {
|
||||||
@ -701,7 +702,7 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
if !manifestExists {
|
if !manifestExists {
|
||||||
manifestMeta, err := dwr.GetManifestMeta(repoMeta.Name, godigest.Digest(manifestDigest)) //nolint:contextcheck
|
manifestMeta, err := dwr.GetManifestMeta(repoMeta.Name, godigest.Digest(manifestDigest)) //nolint:contextcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{},
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo,
|
||||||
errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", manifestDigest)
|
errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", manifestDigest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,7 +710,7 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
|
|
||||||
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
|
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{},
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo,
|
||||||
errors.Wrapf(err, "repodb: error while unmashaling config for manifest with digest %s", manifestDigest)
|
errors.Wrapf(err, "repodb: error while unmashaling config for manifest with digest %s", manifestDigest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -734,7 +735,7 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
foundRepos, _ := pageFinder.Page()
|
foundRepos, pageInfo := pageFinder.Page()
|
||||||
|
|
||||||
// keep just the manifestMeta we need
|
// keep just the manifestMeta we need
|
||||||
for _, repoMeta := range foundRepos {
|
for _, repoMeta := range foundRepos {
|
||||||
@ -743,7 +744,7 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundRepos, foundManifestMetadataMap, err
|
return foundRepos, foundManifestMetadataMap, pageInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
|
func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
|
||||||
|
@ -78,7 +78,7 @@ type RepoDB interface { //nolint:interfacebloat
|
|||||||
|
|
||||||
// FilterTags filters for images given a filter function
|
// FilterTags filters for images given a filter function
|
||||||
FilterTags(ctx context.Context, filter FilterFunc,
|
FilterTags(ctx context.Context, filter FilterFunc,
|
||||||
requestedPage PageInput) ([]RepoMetadata, map[string]ManifestMetadata, error)
|
requestedPage PageInput) ([]RepoMetadata, map[string]ManifestMetadata, PageInfo, error)
|
||||||
|
|
||||||
PatchDB() error
|
PatchDB() error
|
||||||
}
|
}
|
||||||
|
@ -1335,7 +1335,7 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
Convey("Return all tags", func() {
|
Convey("Return all tags", func() {
|
||||||
repos, manifesMetaMap, err := repoDB.FilterTags(
|
repos, manifesMetaMap, pageInfo, err := repoDB.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
@ -1358,10 +1358,12 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
|
|||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest1.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest1.String())
|
||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest2.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest2.String())
|
||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest3.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest3.String())
|
||||||
|
So(pageInfo.ItemCount, ShouldEqual, 6)
|
||||||
|
So(pageInfo.TotalCount, ShouldEqual, 6)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Return all tags in a specific repo", func() {
|
Convey("Return all tags in a specific repo", func() {
|
||||||
repos, manifesMetaMap, err := repoDB.FilterTags(
|
repos, manifesMetaMap, pageInfo, err := repoDB.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return repoMeta.Name == repo1
|
return repoMeta.Name == repo1
|
||||||
@ -1381,10 +1383,12 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
|
|||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest1.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest1.String())
|
||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest2.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest2.String())
|
||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest3.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest3.String())
|
||||||
|
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||||
|
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Filter everything out", func() {
|
Convey("Filter everything out", func() {
|
||||||
repos, manifesMetaMap, err := repoDB.FilterTags(
|
repos, manifesMetaMap, pageInfo, err := repoDB.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return false
|
return false
|
||||||
@ -1395,6 +1399,8 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(repos), ShouldEqual, 0)
|
So(len(repos), ShouldEqual, 0)
|
||||||
So(len(manifesMetaMap), ShouldEqual, 0)
|
So(len(manifesMetaMap), ShouldEqual, 0)
|
||||||
|
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||||
|
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Search with access control", func() {
|
Convey("Search with access control", func() {
|
||||||
@ -1409,7 +1415,7 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
|
|||||||
authzCtxKey := localCtx.GetContextKey()
|
authzCtxKey := localCtx.GetContextKey()
|
||||||
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
|
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
|
||||||
|
|
||||||
repos, manifesMetaMap, err := repoDB.FilterTags(
|
repos, manifesMetaMap, pageInfo, err := repoDB.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
@ -1423,10 +1429,12 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
|
|||||||
So(len(repos[0].Tags), ShouldEqual, 1)
|
So(len(repos[0].Tags), ShouldEqual, 1)
|
||||||
So(repos[0].Tags, ShouldContainKey, "0.0.1")
|
So(repos[0].Tags, ShouldContainKey, "0.0.1")
|
||||||
So(manifesMetaMap, ShouldContainKey, manifestDigest3.String())
|
So(manifesMetaMap, ShouldContainKey, manifestDigest3.String())
|
||||||
|
So(pageInfo.ItemCount, ShouldEqual, 1)
|
||||||
|
So(pageInfo.TotalCount, ShouldEqual, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("With wrong pagination input", func() {
|
Convey("With wrong pagination input", func() {
|
||||||
repos, _, err := repoDB.FilterTags(
|
repos, _, _, err := repoDB.FilterTags(
|
||||||
ctx,
|
ctx,
|
||||||
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
||||||
return true
|
return true
|
||||||
|
@ -50,7 +50,7 @@ type RepoDBMock struct {
|
|||||||
|
|
||||||
FilterTagsFn func(ctx context.Context, filter repodb.FilterFunc,
|
FilterTagsFn func(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error)
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error)
|
||||||
|
|
||||||
SearchDigestsFn func(ctx context.Context, searchText string, requestedPage repodb.PageInput) (
|
SearchDigestsFn func(ctx context.Context, searchText string, requestedPage repodb.PageInput) (
|
||||||
[]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error)
|
[]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error)
|
||||||
@ -223,12 +223,12 @@ func (sdm RepoDBMock) SearchTags(ctx context.Context, searchText string, filter
|
|||||||
|
|
||||||
func (sdm RepoDBMock) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
func (sdm RepoDBMock) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
requestedPage repodb.PageInput,
|
requestedPage repodb.PageInput,
|
||||||
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) {
|
||||||
if sdm.FilterTagsFn != nil {
|
if sdm.FilterTagsFn != nil {
|
||||||
return sdm.FilterTagsFn(ctx, filter, requestedPage)
|
return sdm.FilterTagsFn(ctx, filter, requestedPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, nil
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdm RepoDBMock) SearchDigests(ctx context.Context, searchText string, requestedPage repodb.PageInput,
|
func (sdm RepoDBMock) SearchDigests(ctx context.Context, searchText string, requestedPage repodb.PageInput,
|
||||||
|
Loading…
Reference in New Issue
Block a user