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:
parent
87fc941b3c
commit
b5f27c5b50
8 changed files with 190 additions and 31 deletions
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ type RepoSummary {
|
|||
Platforms: [OsArch]
|
||||
Vendors: [String]
|
||||
Score: Int
|
||||
NewestTag: ImageSummary
|
||||
}
|
||||
|
||||
# Currently the same as LayerInfo, we can refactor later
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue