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,6 +87,7 @@ type ImageSummary struct {
Platform OsArch `json:"platform"` Platform OsArch `json:"platform"`
Vendor string `json:"vendor"` Vendor string `json:"vendor"`
Score int `json:"score"` Score int `json:"score"`
IsSigned bool `json:"isSigned"`
} }
type RepoSummary struct { type RepoSummary struct {
@ -96,6 +97,7 @@ type RepoSummary struct {
Platforms []OsArch `json:"platforms"` Platforms []OsArch `json:"platforms"`
Vendors []string `json:"vendors"` Vendors []string `json:"vendors"`
Score int `json:"score"` Score int `json:"score"`
NewestTag ImageSummary `json:"newestTag"`
} }
type LayerSummary struct { type LayerSummary struct {
@ -797,7 +799,13 @@ func TestGlobalSearch(t *testing.T) {
Tag Tag
LastUpdated LastUpdated
Size Size
IsSigned
Vendor
Score Score
Platform {
Os
Arch
}
} }
Repos { Repos {
Name Name
@ -809,6 +817,19 @@ func TestGlobalSearch(t *testing.T) {
} }
Vendors Vendors
Score Score
NewestTag {
RepoName
Tag
LastUpdated
Size
IsSigned
Vendor
Score
Platform {
Os
Arch
}
}
} }
Layers { Layers {
Digest Digest
@ -826,11 +847,65 @@ func TestGlobalSearch(t *testing.T) {
err = json.Unmarshal(resp.Body(), responseStruct) err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil) 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(responseStruct.GlobalSearchResult.GlobalSearch.Images, ShouldNotBeNil)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Images), ShouldNotBeEmpty) t.Logf("returned images: %v", responseStruct.GlobalSearchResult.GlobalSearch.Images)
So(len(responseStruct.GlobalSearchResult.GlobalSearch.Repos), ShouldNotBeEmpty) 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) 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 // GetRepositories fail
err = os.Chmod(rootDir, 0o333) err = os.Chmod(rootDir, 0o333)

View file

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

View file

@ -143,6 +143,7 @@ type ComplexityRoot struct {
RepoSummary struct { RepoSummary struct {
LastUpdated func(childComplexity int) int LastUpdated func(childComplexity int) int
Name func(childComplexity int) int Name func(childComplexity int) int
NewestTag func(childComplexity int) int
Platforms func(childComplexity int) int Platforms func(childComplexity int) int
Score func(childComplexity int) int Score func(childComplexity int) int
Size 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 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": case "RepoSummary.Platforms":
if e.complexity.RepoSummary.Platforms == nil { if e.complexity.RepoSummary.Platforms == nil {
break break
@ -794,6 +802,7 @@ type RepoSummary {
Platforms: [OsArch] Platforms: [OsArch]
Vendors: [String] Vendors: [String]
Score: Int Score: Int
NewestTag: ImageSummary
} }
# Currently the same as LayerInfo, we can refactor later # 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) return ec.fieldContext_RepoSummary_Vendors(ctx, field)
case "Score": case "Score":
return ec.fieldContext_RepoSummary_Score(ctx, field) 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) 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 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) { func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TagInfo_Name(ctx, field) fc, err := ec.fieldContext_TagInfo_Name(ctx, field)
if err != nil { 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) out.Values[i] = ec._RepoSummary_Score(ctx, field, obj)
case "NewestTag":
out.Values[i] = ec._RepoSummary_NewestTag(ctx, field, obj)
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View file

@ -101,6 +101,7 @@ type RepoSummary struct {
Platforms []*OsArch `json:"Platforms"` Platforms []*OsArch `json:"Platforms"`
Vendors []*string `json:"Vendors"` Vendors []*string `json:"Vendors"`
Score *int `json:"Score"` Score *int `json:"Score"`
NewestTag *ImageSummary `json:"NewestTag"`
} }
type TagInfo struct { 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 // made up of all manifests, configs and image layers
repoSize := int64(0) repoSize := int64(0)
lastUpdate, err := olu.GetRepoLastUpdated(repo) lastUpdatedTag, err := olu.GetRepoLastUpdated(repo)
if err != nil { 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) tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
@ -230,6 +230,8 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
continue continue
} }
var lastUpdatedImageSummary gql_generated.ImageSummary
repoPlatforms := make([]*gql_generated.OsArch, 0, len(tagsInfo)) repoPlatforms := make([]*gql_generated.OsArch, 0, len(tagsInfo))
repoVendors := make([]*string, 0, len(repoInfo.Manifests)) 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) repoPlatforms = append(repoPlatforms, osArch)
repoVendors = append(repoVendors, &vendor) repoVendors = append(repoVendors, &vendor)
images = append(images, &gql_generated.ImageSummary{ imageSummary := gql_generated.ImageSummary{
RepoName: &repo, RepoName: &repo,
Tag: &tag, Tag: &tag,
LastUpdated: &lastUpdated, LastUpdated: &lastUpdated,
@ -316,7 +318,13 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
Platform: osArch, Platform: osArch,
Vendor: &vendor, Vendor: &vendor,
Score: &score, 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{ repos = append(repos, &gql_generated.RepoSummary{
Name: &repo, Name: &repo,
LastUpdated: &lastUpdate, LastUpdated: &lastUpdatedTag.Timestamp,
Size: &repoSize, Size: &repoSize,
Platforms: repoPlatforms, Platforms: repoPlatforms,
Vendors: repoVendors, Vendors: repoVendors,
Score: &index, Score: &index,
NewestTag: &lastUpdatedImageSummary,
}) })
} }
} }

View file

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

View file

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

View file

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