0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

RepoSummary has a new attribute NewestTag of type ImageSummary

ImageListWithLatestTag currently returns a list of ImageInfo objects.
It needs to return consistent results with the API used for Global search as the same information will be used by the UI in the same type or cards.
So we need to update RepoSummary to include the data which right now is present in ImageInfo, but missing from RepoSummary (information on the latest tag in that specific repo).
Will update return type of ImageListWithLatestTag in a later PR (issue tracked in a separate GH issue)

Closes #666

Signed-off-by: Andrei Aaron <andaaron@cisco.com>
This commit is contained in:
Andrei Aaron 2022-07-22 20:01:38 +00:00 committed by Andrei Aaron
parent 87fc941b3c
commit b5f27c5b50
8 changed files with 190 additions and 31 deletions

View file

@ -87,15 +87,17 @@ type ImageSummary struct {
Platform OsArch `json:"platform"`
Vendor string `json:"vendor"`
Score int `json:"score"`
IsSigned bool `json:"isSigned"`
}
type RepoSummary struct {
Name string `json:"name"`
LastUpdated time.Time `json:"lastUpdated"`
Size string `json:"size"`
Platforms []OsArch `json:"platforms"`
Vendors []string `json:"vendors"`
Score int `json:"score"`
Name string `json:"name"`
LastUpdated time.Time `json:"lastUpdated"`
Size string `json:"size"`
Platforms []OsArch `json:"platforms"`
Vendors []string `json:"vendors"`
Score int `json:"score"`
NewestTag ImageSummary `json:"newestTag"`
}
type LayerSummary struct {
@ -797,18 +799,37 @@ func TestGlobalSearch(t *testing.T) {
Tag
LastUpdated
Size
IsSigned
Vendor
Score
Platform {
Os
Arch
}
}
Repos {
Name
LastUpdated
Size
Platforms {
Os
Arch
Os
Arch
}
Vendors
Score
NewestTag {
RepoName
Tag
LastUpdated
Size
IsSigned
Vendor
Score
Platform {
Os
Arch
}
}
}
Layers {
Digest
@ -826,11 +847,65 @@ func TestGlobalSearch(t *testing.T) {
err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil)
// There are 2 repos: zot-cve-test and zot-test, each having an image with tag 0.0.1
imageStore := ctlr.StoreController.DefaultStore
repos, err := imageStore.GetRepositories()
So(err, ShouldBeNil)
expectedRepoCount := len(repos)
allExpectedTagMap := make(map[string][]string, expectedRepoCount)
expectedImageCount := 0
for _, repo := range repos {
tags, err := imageStore.GetImageTags(repo)
So(err, ShouldBeNil)
allExpectedTagMap[repo] = tags
expectedImageCount += len(tags)
}
// Make sure the repo/image counts match before comparing actual content
So(responseStruct.GlobalSearchResult.GlobalSearch.Images, ShouldNotBeNil)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Images), ShouldNotBeEmpty)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Repos), ShouldNotBeEmpty)
t.Logf("returned images: %v", responseStruct.GlobalSearchResult.GlobalSearch.Images)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Images), ShouldEqual, expectedImageCount)
t.Logf("returned repos: %v", responseStruct.GlobalSearchResult.GlobalSearch.Repos)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Repos), ShouldEqual, expectedRepoCount)
t.Logf("returned layers: %v", responseStruct.GlobalSearchResult.GlobalSearch.Layers)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Layers), ShouldNotBeEmpty)
newestImageMap := make(map[string]ImageSummary)
for _, image := range responseStruct.GlobalSearchResult.GlobalSearch.Images {
// Make sure all returned results are supposed to be in the repo
So(allExpectedTagMap[image.RepoName], ShouldContain, image.Tag)
// Identify the newest image in each repo
if newestImage, ok := newestImageMap[image.RepoName]; ok {
if newestImage.LastUpdated.Before(image.LastUpdated) {
newestImageMap[image.RepoName] = image
}
} else {
newestImageMap[image.RepoName] = image
}
}
t.Logf("expected results for newest images in repos: %v", newestImageMap)
for _, repo := range responseStruct.GlobalSearchResult.GlobalSearch.Repos {
image := newestImageMap[repo.Name]
So(repo.Name, ShouldEqual, image.RepoName)
So(repo.LastUpdated, ShouldEqual, image.LastUpdated)
So(repo.Size, ShouldEqual, image.Size)
So(repo.Vendors[0], ShouldEqual, image.Vendor)
So(repo.Platforms[0].Os, ShouldEqual, image.Platform.Os)
So(repo.Platforms[0].Arch, ShouldEqual, image.Platform.Arch)
So(repo.NewestTag.RepoName, ShouldEqual, image.RepoName)
So(repo.NewestTag.Tag, ShouldEqual, image.Tag)
So(repo.NewestTag.LastUpdated, ShouldEqual, image.LastUpdated)
So(repo.NewestTag.Size, ShouldEqual, image.Size)
So(repo.NewestTag.IsSigned, ShouldEqual, image.IsSigned)
So(repo.NewestTag.Vendor, ShouldEqual, image.Vendor)
So(repo.NewestTag.Platform.Os, ShouldEqual, image.Platform.Os)
So(repo.NewestTag.Platform.Arch, ShouldEqual, image.Platform.Arch)
}
// GetRepositories fail
err = os.Chmod(rootDir, 0o333)

View file

@ -30,7 +30,7 @@ type OciLayoutUtils interface {
GetImagePlatform(imageInfo ispec.Image) (string, string)
GetImageVendor(imageInfo ispec.Image) string
GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64
GetRepoLastUpdated(repo string) (time.Time, error)
GetRepoLastUpdated(repo string) (TagInfo, error)
GetExpandedRepoInfo(name string) (RepoInfo, error)
GetImageConfigInfo(repo string, manifestDigest godigest.Digest) (ispec.Image, error)
}
@ -323,15 +323,15 @@ func (olu BaseOciLayoutUtils) GetImageConfigSize(repo string, manifestDigest god
return imageBlobManifest.Config.Size
}
func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (time.Time, error) {
func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (TagInfo, error) {
tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
if err != nil || len(tagsInfo) == 0 {
return time.Time{}, err
return TagInfo{}, err
}
latestTag := GetLatestTag(tagsInfo)
return latestTag.Timestamp, nil
return latestTag, nil
}
func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {

View file

@ -143,6 +143,7 @@ type ComplexityRoot struct {
RepoSummary struct {
LastUpdated func(childComplexity int) int
Name func(childComplexity int) int
NewestTag func(childComplexity int) int
Platforms func(childComplexity int) int
Score func(childComplexity int) int
Size func(childComplexity int) int
@ -596,6 +597,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.RepoSummary.Name(childComplexity), true
case "RepoSummary.NewestTag":
if e.complexity.RepoSummary.NewestTag == nil {
break
}
return e.complexity.RepoSummary.NewestTag(childComplexity), true
case "RepoSummary.Platforms":
if e.complexity.RepoSummary.Platforms == nil {
break
@ -794,6 +802,7 @@ type RepoSummary {
Platforms: [OsArch]
Vendors: [String]
Score: Int
NewestTag: ImageSummary
}
# Currently the same as LayerInfo, we can refactor later
@ -1392,6 +1401,8 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Repos(ctx context.Co
return ec.fieldContext_RepoSummary_Vendors(ctx, field)
case "Score":
return ec.fieldContext_RepoSummary_Score(ctx, field)
case "NewestTag":
return ec.fieldContext_RepoSummary_NewestTag(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type RepoSummary", field.Name)
},
@ -3740,6 +3751,65 @@ func (ec *executionContext) fieldContext_RepoSummary_Score(ctx context.Context,
return fc, nil
}
func (ec *executionContext) _RepoSummary_NewestTag(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_RepoSummary_NewestTag(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.NewestTag, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*ImageSummary)
fc.Result = res
return ec.marshalOImageSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_RepoSummary_NewestTag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "RepoSummary",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "RepoName":
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(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)
}
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TagInfo_Name(ctx, field)
if err != nil {
@ -6338,6 +6408,10 @@ func (ec *executionContext) _RepoSummary(ctx context.Context, sel ast.SelectionS
out.Values[i] = ec._RepoSummary_Score(ctx, field, obj)
case "NewestTag":
out.Values[i] = ec._RepoSummary_NewestTag(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}

View file

@ -95,12 +95,13 @@ type RepoInfo struct {
}
type RepoSummary struct {
Name *string `json:"Name"`
LastUpdated *time.Time `json:"LastUpdated"`
Size *string `json:"Size"`
Platforms []*OsArch `json:"Platforms"`
Vendors []*string `json:"Vendors"`
Score *int `json:"Score"`
Name *string `json:"Name"`
LastUpdated *time.Time `json:"LastUpdated"`
Size *string `json:"Size"`
Platforms []*OsArch `json:"Platforms"`
Vendors []*string `json:"Vendors"`
Score *int `json:"Score"`
NewestTag *ImageSummary `json:"NewestTag"`
}
type TagInfo struct {

View file

@ -211,9 +211,9 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
// made up of all manifests, configs and image layers
repoSize := int64(0)
lastUpdate, err := olu.GetRepoLastUpdated(repo)
lastUpdatedTag, err := olu.GetRepoLastUpdated(repo)
if err != nil {
log.Error().Err(err).Msgf("can't find latest update timestamp for repo: %s", repo)
log.Error().Err(err).Msgf("can't find latest updated tag for repo: %s", repo)
}
tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
@ -230,6 +230,8 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
continue
}
var lastUpdatedImageSummary gql_generated.ImageSummary
repoPlatforms := make([]*gql_generated.OsArch, 0, len(tagsInfo))
repoVendors := make([]*string, 0, len(repoInfo.Manifests))
@ -307,7 +309,7 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
repoPlatforms = append(repoPlatforms, osArch)
repoVendors = append(repoVendors, &vendor)
images = append(images, &gql_generated.ImageSummary{
imageSummary := gql_generated.ImageSummary{
RepoName: &repo,
Tag: &tag,
LastUpdated: &lastUpdated,
@ -316,7 +318,13 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
Platform: osArch,
Vendor: &vendor,
Score: &score,
})
}
if tagsInfo[i].Digest == lastUpdatedTag.Digest {
lastUpdatedImageSummary = imageSummary
}
images = append(images, &imageSummary)
}
}
@ -329,11 +337,12 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
repos = append(repos, &gql_generated.RepoSummary{
Name: &repo,
LastUpdated: &lastUpdate,
LastUpdated: &lastUpdatedTag.Timestamp,
Size: &repoSize,
Platforms: repoPlatforms,
Vendors: repoVendors,
Score: &index,
NewestTag: &lastUpdatedImageSummary,
})
}
}

View file

@ -4,7 +4,6 @@ import (
"errors"
"strings"
"testing"
"time"
godigest "github.com/opencontainers/go-digest"
. "github.com/smartystreets/goconvey/convey"
@ -19,8 +18,8 @@ func TestGlobalSearch(t *testing.T) {
Convey("globalSearch", t, func() {
Convey("GetRepoLastUpdated fail", func() {
mockOlum := mocks.OciLayoutUtilsMock{
GetRepoLastUpdatedFn: func(repo string) (time.Time, error) {
return time.Time{}, ErrTestError
GetRepoLastUpdatedFn: func(repo string) (common.TagInfo, error) {
return common.TagInfo{}, ErrTestError
},
}

View file

@ -95,6 +95,7 @@ type RepoSummary {
Platforms: [OsArch]
Vendors: [String]
Score: Int
NewestTag: ImageSummary
}
# Currently the same as LayerInfo, we can refactor later

View file

@ -20,7 +20,7 @@ type OciLayoutUtilsMock struct {
GetImageVendorFn func(imageInfo ispec.Image) string
GetImageManifestSizeFn func(repo string, manifestDigest godigest.Digest) int64
GetImageConfigSizeFn func(repo string, manifestDigest godigest.Digest) int64
GetRepoLastUpdatedFn func(repo string) (time.Time, error)
GetRepoLastUpdatedFn func(repo string) (common.TagInfo, error)
GetExpandedRepoInfoFn func(name string) (common.RepoInfo, error)
GetImageConfigInfoFn func(repo string, manifestDigest godigest.Digest) (ispec.Image, error)
}
@ -105,12 +105,12 @@ func (olum OciLayoutUtilsMock) GetImageConfigSize(repo string, manifestDigest go
return 0
}
func (olum OciLayoutUtilsMock) GetRepoLastUpdated(repo string) (time.Time, error) {
func (olum OciLayoutUtilsMock) GetRepoLastUpdated(repo string) (common.TagInfo, error) {
if olum.GetRepoLastUpdatedFn != nil {
return olum.GetRepoLastUpdatedFn(repo)
}
return time.Time{}, nil
return common.TagInfo{}, nil
}
func (olum OciLayoutUtilsMock) GetExpandedRepoInfo(name string) (common.RepoInfo, error) {