mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 04:17:55 +08:00
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:
+104
-28
@@ -483,7 +483,7 @@ func TestDerivedImageList(t *testing.T) {
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
err := uploadManifest(url)
|
||||
err := uploadManifestDerivedBase(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -493,7 +493,7 @@ func TestDerivedImageList(t *testing.T) {
|
||||
Convey("Test from real server", t, func() {
|
||||
Convey("Test derived images list working", func() {
|
||||
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))
|
||||
defer os.Remove(configPath)
|
||||
cmd := NewImageCommand(new(searchService))
|
||||
@@ -507,19 +507,11 @@ func TestDerivedImageList(t *testing.T) {
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
actual := strings.TrimSpace(str)
|
||||
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() {
|
||||
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
||||
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"}
|
||||
Convey("Test derived images list fails", func() {
|
||||
args := []string{"imagetest", "--derived-images", "repo7:test:missing"}
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||
defer os.Remove(configPath)
|
||||
cmd := NewImageCommand(new(searchService))
|
||||
@@ -533,7 +525,7 @@ func TestDerivedImageList(t *testing.T) {
|
||||
|
||||
Convey("Test derived images list cannot print", func() {
|
||||
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))
|
||||
defer os.Remove(configPath)
|
||||
cmd := NewImageCommand(new(searchService))
|
||||
@@ -564,7 +556,7 @@ func TestBaseImageList(t *testing.T) {
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
err := uploadManifest(url)
|
||||
err := uploadManifestDerivedBase(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -588,19 +580,11 @@ func TestBaseImageList(t *testing.T) {
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
actual := strings.TrimSpace(str)
|
||||
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() {
|
||||
t.Logf("%s", ctlr.Config.Storage.RootDirectory)
|
||||
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"}
|
||||
Convey("Test base images list fail", func() {
|
||||
args := []string{"imagetest", "--base-images", "repo7:test:missing"}
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||
defer os.Remove(configPath)
|
||||
cmd := NewImageCommand(new(searchService))
|
||||
@@ -1481,6 +1465,98 @@ func uploadManifest(url string) error {
|
||||
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{}
|
||||
|
||||
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,
|
||||
) (*imageListStructForDerivedImagesGQL, error) {
|
||||
imageListGQLResponse := &imageListStructForDerivedImagesGQL{}
|
||||
imageListGQLResponse.Data.ImageList = []imageStruct{
|
||||
imageListGQLResponse.Data.ImageList.Results = []imageStruct{
|
||||
{
|
||||
RepoName: "dummyImageName",
|
||||
Tag: "tag",
|
||||
@@ -1519,7 +1595,7 @@ func (service mockService) getBaseImageListGQL(ctx context.Context, config searc
|
||||
derivedImage string,
|
||||
) (*imageListStructForBaseImagesGQL, error) {
|
||||
imageListGQLResponse := &imageListStructForBaseImagesGQL{}
|
||||
imageListGQLResponse.Data.ImageList = []imageStruct{
|
||||
imageListGQLResponse.Data.ImageList.Results = []imageStruct{
|
||||
{
|
||||
RepoName: "dummyImageName",
|
||||
Tag: "tag",
|
||||
|
||||
+2
-2
@@ -241,7 +241,7 @@ func (search derivedImageListSearcherGQL) search(config searchConfig) (bool, 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
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func (search baseImageListSearcherGQL) search(config searchConfig) (bool, error)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+29
-18
@@ -71,14 +71,16 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
DerivedImageList(image:"%s"){
|
||||
RepoName,
|
||||
Tag,
|
||||
Digest,
|
||||
ConfigDigest,
|
||||
Layers {Size Digest},
|
||||
LastUpdated,
|
||||
IsSigned,
|
||||
Size
|
||||
Results{
|
||||
RepoName,
|
||||
Tag,
|
||||
Digest,
|
||||
ConfigDigest,
|
||||
Layers {Size Digest},
|
||||
LastUpdated,
|
||||
IsSigned,
|
||||
Size
|
||||
}
|
||||
}
|
||||
}`, derivedImage)
|
||||
|
||||
@@ -98,14 +100,16 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
BaseImageList(image:"%s"){
|
||||
RepoName,
|
||||
Tag,
|
||||
Digest,
|
||||
ConfigDigest,
|
||||
Layers {Size Digest},
|
||||
LastUpdated,
|
||||
IsSigned,
|
||||
Size
|
||||
Results{
|
||||
RepoName,
|
||||
Tag,
|
||||
Digest,
|
||||
ConfigDigest,
|
||||
Layers {Size Digest},
|
||||
LastUpdated,
|
||||
IsSigned,
|
||||
Size
|
||||
}
|
||||
}
|
||||
}`, baseImage)
|
||||
|
||||
@@ -862,6 +866,13 @@ type imageStruct struct {
|
||||
IsSigned bool `json:"isSigned"`
|
||||
}
|
||||
|
||||
type DerivedImageList struct {
|
||||
Results []imageStruct `json:"results"`
|
||||
}
|
||||
type BaseImageList struct {
|
||||
Results []imageStruct `json:"results"`
|
||||
}
|
||||
|
||||
type imageListStructGQL struct {
|
||||
Errors []errorGraphQL `json:"errors"`
|
||||
Data struct {
|
||||
@@ -879,14 +890,14 @@ type imageListStructForDigestGQL struct {
|
||||
type imageListStructForDerivedImagesGQL struct {
|
||||
Errors []errorGraphQL `json:"errors"`
|
||||
Data struct {
|
||||
ImageList []imageStruct `json:"DerivedImageList"` //nolint:tagliatelle
|
||||
ImageList DerivedImageList `json:"DerivedImageList"` //nolint:tagliatelle
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type imageListStructForBaseImagesGQL struct {
|
||||
Errors []errorGraphQL `json:"errors"`
|
||||
Data struct {
|
||||
ImageList []imageStruct `json:"BaseImageList"` //nolint:tagliatelle
|
||||
ImageList BaseImageList `json:"BaseImageList"` //nolint:tagliatelle
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user