0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-30 22:34:13 -05:00

Implement RepoListWithNewestImage to return [RepoSummary]

Removed access by index in repoListWithNewestImage

Signed-off-by: Catalin Hofnar <catalin.hofnar@gmail.com>
This commit is contained in:
Catalin Hofnar 2022-07-29 18:33:34 +03:00 committed by Andrei Aaron
parent 981ca6ddb4
commit 9ca5fa1029
5 changed files with 361 additions and 132 deletions

View file

@ -24,6 +24,7 @@ import (
"github.com/sigstore/cosign/cmd/cosign/cli/sign" "github.com/sigstore/cosign/cmd/cosign/cli/sign"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1" "gopkg.in/resty.v1"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/api/constants"
@ -51,9 +52,9 @@ var (
subRootDir string subRootDir string
) )
type ImgResponsWithLatestTag struct { type RepoWithNewestImageResponse struct {
ImgListWithLatestTag ImgListWithLatestTag `json:"data"` RepoListWithNewestImage RepoListWithNewestImage `json:"data"`
Errors []ErrorGQL `json:"errors"` Errors []ErrorGQL `json:"errors"`
} }
type ExpandedRepoInfoResp struct { type ExpandedRepoInfoResp struct {
@ -112,8 +113,8 @@ type ExpandedRepoInfo struct {
} }
//nolint:tagliatelle // graphQL schema //nolint:tagliatelle // graphQL schema
type ImgListWithLatestTag struct { type RepoListWithNewestImage struct {
Images []ImageInfo `json:"ImageListWithLatestTag"` Repos []RepoSummary `json:"RepoListWithNewestImage"`
} }
type ErrorGQL struct { type ErrorGQL struct {
@ -318,8 +319,132 @@ func TestImageFormat(t *testing.T) {
}) })
} }
func TestLatestTagSearchHTTP(t *testing.T) { func TestRepoListWithNewestImage(t *testing.T) {
Convey("Test latest image search by timestamp", t, func() { Convey("Test repoListWithNewestImage AddError", t, func() {
subpath := "/a"
err := testSetup(t, subpath)
if err != nil {
panic(err)
}
err = os.RemoveAll(path.Join(rootDir, "zot-cve-test"))
if err != nil {
panic(err)
}
err = os.RemoveAll(path.Join(rootDir, subpath))
if err != nil {
panic(err)
}
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = rootDir
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{Enable: &defaultVal},
}
conf.Extensions.Search.CVE = nil
ctlr := api.NewController(conf)
go func() {
// this blocks
if err := ctlr.Run(context.Background()); err != nil {
return
}
}()
// wait till ready
for {
_, err := resty.R().Get(baseURL)
if err == nil {
break
}
time.Sleep(100 * time.Millisecond)
}
// shut down server
defer func() {
ctx := context.Background()
_ = ctlr.Server.Shutdown(ctx)
}()
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = os.Remove(path.Join(rootDir,
"zot-test/blobs/sha256/2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396"))
if err != nil {
panic(err)
}
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
errmsg := fmt.Sprint(zerr.ErrBlobNotFound)
body := string(resp.Body())
So(body, ShouldContainSubstring, errmsg)
So(resp.StatusCode(), ShouldEqual, 200)
err = CopyFiles("../../../../test/data/zot-test", path.Join(rootDir, "zot-test"))
if err != nil {
panic(err)
}
err = os.Remove(path.Join(rootDir,
"zot-test/blobs/sha256/adf3bb6cc81f8bd6a9d5233be5f0c1a4f1e3ed1cf5bbdfad7708cc8d4099b741"))
if err != nil {
panic(err)
}
err = os.Remove(path.Join(rootDir,
"zot-test/blobs/sha256/2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc"))
if err != nil {
panic(err)
}
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
errmsg = fmt.Sprint(zerr.ErrBlobNotFound)
body = string(resp.Body())
So(body, ShouldContainSubstring, errmsg)
So(resp.StatusCode(), ShouldEqual, 200)
err = CopyFiles("../../../../test/data/zot-test", path.Join(rootDir, "zot-test"))
if err != nil {
panic(err)
}
err = os.Remove(path.Join(rootDir, "zot-test/index.json"))
if err != nil {
panic(err)
}
//nolint: lll
manifestNoAnnotations := "{\"schemaVersion\":2,\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396\",\"size\":350}]}"
err = os.WriteFile(path.Join(rootDir, "zot-test/index.json"), []byte(manifestNoAnnotations), 0o600)
if err != nil {
panic(err)
}
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
body = string(resp.Body())
So(body, ShouldContainSubstring, "reference not found for this manifest")
So(resp.StatusCode(), ShouldEqual, 200)
})
Convey("Test repoListWithNewestImage by tag with HTTP", t, func() {
subpath := "/a" subpath := "/a"
err := testSetup(t, subpath) err := testSetup(t, subpath)
if err != nil { if err != nil {
@ -373,20 +498,22 @@ func TestLatestTagSearchHTTP(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 422) So(resp.StatusCode(), ShouldEqual, 422)
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
var responseStruct ImgResponsWithLatestTag var responseStruct RepoWithNewestImageResponse
err = json.Unmarshal(resp.Body(), &responseStruct) err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 4) So(len(responseStruct.RepoListWithNewestImage.Repos), ShouldEqual, 4)
images := responseStruct.ImgListWithLatestTag.Images images := responseStruct.RepoListWithNewestImage.Repos
So(images[0].Tag, ShouldEqual, "0.0.1") So(images[0].NewestImage.Tag, ShouldEqual, "0.0.1")
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -395,14 +522,15 @@ func TestLatestTagSearchHTTP(t *testing.T) {
panic(err) panic(err)
} }
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), &responseStruct) err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 0) So(responseStruct.Errors, ShouldNotBeNil)
err = os.Chmod(rootDir, 0o755) err = os.Chmod(rootDir, 0o755)
if err != nil { if err != nil {
@ -419,7 +547,8 @@ func TestLatestTagSearchHTTP(t *testing.T) {
panic(err) panic(err)
} }
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
@ -430,7 +559,8 @@ func TestLatestTagSearchHTTP(t *testing.T) {
panic(err) panic(err)
} }
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
@ -440,7 +570,8 @@ func TestLatestTagSearchHTTP(t *testing.T) {
panic(err) panic(err)
} }
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
@ -451,7 +582,8 @@ func TestLatestTagSearchHTTP(t *testing.T) {
panic(err) panic(err)
} }
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") resp, err = resty.R().Get(baseURL + graphqlQueryPrefix +
"?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}")
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)

View file

@ -98,14 +98,14 @@ type ComplexityRoot struct {
} }
Query struct { Query struct {
CVEListForImage func(childComplexity int, image string) int CVEListForImage func(childComplexity int, image string) int
ExpandedRepoInfo func(childComplexity int, repo string) int ExpandedRepoInfo func(childComplexity int, repo string) int
GlobalSearch func(childComplexity int, query string) int GlobalSearch func(childComplexity int, query string) int
ImageList func(childComplexity int, repo string) int ImageList func(childComplexity int, repo string) int
ImageListForCve func(childComplexity int, id string) int ImageListForCve func(childComplexity int, id string) int
ImageListForDigest func(childComplexity int, id string) int ImageListForDigest func(childComplexity int, id string) int
ImageListWithCVEFixed func(childComplexity int, id string, image string) int ImageListWithCVEFixed func(childComplexity int, id string, image string) int
ImageListWithLatestTag func(childComplexity int) int RepoListWithNewestImage func(childComplexity int) int
} }
RepoInfo struct { RepoInfo struct {
@ -132,7 +132,7 @@ type QueryResolver interface {
ImageListForCve(ctx context.Context, id string) ([]*ImageSummary, error) ImageListForCve(ctx context.Context, id string) ([]*ImageSummary, error)
ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*ImageSummary, error) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*ImageSummary, error)
ImageListForDigest(ctx context.Context, id string) ([]*ImageSummary, error) ImageListForDigest(ctx context.Context, id string) ([]*ImageSummary, error)
ImageListWithLatestTag(ctx context.Context) ([]*ImageSummary, error) RepoListWithNewestImage(ctx context.Context) ([]*RepoSummary, error)
ImageList(ctx context.Context, repo string) ([]*ImageSummary, error) ImageList(ctx context.Context, repo string) ([]*ImageSummary, error)
ExpandedRepoInfo(ctx context.Context, repo string) (*RepoInfo, error) ExpandedRepoInfo(ctx context.Context, repo string) (*RepoInfo, error)
GlobalSearch(ctx context.Context, query string) (*GlobalSearchResult, error) GlobalSearch(ctx context.Context, query string) (*GlobalSearchResult, error)
@ -468,12 +468,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string)), true return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string)), true
case "Query.ImageListWithLatestTag": case "Query.RepoListWithNewestImage":
if e.complexity.Query.ImageListWithLatestTag == nil { if e.complexity.Query.RepoListWithNewestImage == nil {
break break
} }
return e.complexity.Query.ImageListWithLatestTag(childComplexity), true return e.complexity.Query.RepoListWithNewestImage(childComplexity), true
case "RepoInfo.Images": case "RepoInfo.Images":
if e.complexity.RepoInfo.Images == nil { if e.complexity.RepoInfo.Images == nil {
@ -697,7 +697,7 @@ type Query {
ImageListForCVE(id: String!): [ImageSummary!] ImageListForCVE(id: String!): [ImageSummary!]
ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!] ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!]
ImageListForDigest(id: String!): [ImageSummary!] ImageListForDigest(id: String!): [ImageSummary!]
ImageListWithLatestTag: [ImageSummary!] RepoListWithNewestImage: [RepoSummary!]! # Newest based on created timestamp
ImageList(repo: String!): [ImageSummary!] ImageList(repo: String!): [ImageSummary!]
ExpandedRepoInfo(repo: String!): RepoInfo! ExpandedRepoInfo(repo: String!): RepoInfo!
GlobalSearch(query: String!): GlobalSearchResult! GlobalSearch(query: String!): GlobalSearchResult!
@ -2639,8 +2639,8 @@ func (ec *executionContext) fieldContext_Query_ImageListForDigest(ctx context.Co
return fc, nil return fc, nil
} }
func (ec *executionContext) _Query_ImageListWithLatestTag(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query_RepoListWithNewestImage(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query_ImageListWithLatestTag(ctx, field) fc, err := ec.fieldContext_Query_RepoListWithNewestImage(ctx, field)
if err != nil { if err != nil {
return graphql.Null return graphql.Null
} }
@ -2653,21 +2653,24 @@ func (ec *executionContext) _Query_ImageListWithLatestTag(ctx context.Context, f
}() }()
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().ImageListWithLatestTag(rctx) return ec.resolvers.Query().RepoListWithNewestImage(rctx)
}) })
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.([]*RepoSummary)
fc.Result = res fc.Result = res
return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) return ec.marshalNRepoSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummaryᚄ(ctx, field.Selections, res)
} }
func (ec *executionContext) fieldContext_Query_ImageListWithLatestTag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { func (ec *executionContext) fieldContext_Query_RepoListWithNewestImage(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{ fc = &graphql.FieldContext{
Object: "Query", Object: "Query",
Field: field, Field: field,
@ -2675,38 +2678,28 @@ func (ec *executionContext) fieldContext_Query_ImageListWithLatestTag(ctx contex
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 "Name":
return ec.fieldContext_ImageSummary_RepoName(ctx, field) return ec.fieldContext_RepoSummary_Name(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field)
case "Digest":
return ec.fieldContext_ImageSummary_Digest(ctx, field)
case "ConfigDigest":
return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field)
case "LastUpdated": case "LastUpdated":
return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) return ec.fieldContext_RepoSummary_LastUpdated(ctx, field)
case "IsSigned":
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
case "Size": case "Size":
return ec.fieldContext_ImageSummary_Size(ctx, field) return ec.fieldContext_RepoSummary_Size(ctx, field)
case "Platform": case "Platforms":
return ec.fieldContext_ImageSummary_Platform(ctx, field) return ec.fieldContext_RepoSummary_Platforms(ctx, field)
case "Vendor": case "Vendors":
return ec.fieldContext_ImageSummary_Vendor(ctx, field) return ec.fieldContext_RepoSummary_Vendors(ctx, field)
case "Score": case "Score":
return ec.fieldContext_ImageSummary_Score(ctx, field) return ec.fieldContext_RepoSummary_Score(ctx, field)
case "NewestImage":
return ec.fieldContext_RepoSummary_NewestImage(ctx, field)
case "DownloadCount": case "DownloadCount":
return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) return ec.fieldContext_RepoSummary_DownloadCount(ctx, field)
case "Layers": case "StarCount":
return ec.fieldContext_ImageSummary_Layers(ctx, field) return ec.fieldContext_RepoSummary_StarCount(ctx, field)
case "Description": case "IsBookmarked":
return ec.fieldContext_ImageSummary_Description(ctx, field) return ec.fieldContext_RepoSummary_IsBookmarked(ctx, field)
case "Licenses":
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
case "Labels":
return ec.fieldContext_ImageSummary_Labels(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 RepoSummary", field.Name)
}, },
} }
return fc, nil return fc, nil
@ -5795,7 +5788,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
out.Concurrently(i, func() graphql.Marshaler { out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx) return rrm(innerCtx)
}) })
case "ImageListWithLatestTag": case "RepoListWithNewestImage":
field := field field := field
innerFunc := func(ctx context.Context) (res graphql.Marshaler) { innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
@ -5804,7 +5797,10 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
} }
}() }()
res = ec._Query_ImageListWithLatestTag(ctx, field) res = ec._Query_RepoListWithNewestImage(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res return res
} }
@ -6379,6 +6375,60 @@ func (ec *executionContext) marshalNRepoInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋe
return ec._RepoInfo(ctx, sel, v) return ec._RepoInfo(ctx, sel, v)
} }
func (ec *executionContext) marshalNRepoSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummaryᚄ(ctx context.Context, sel ast.SelectionSet, v []*RepoSummary) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNRepoSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummary(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
func (ec *executionContext) marshalNRepoSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummary(ctx context.Context, sel ast.SelectionSet, v *RepoSummary) 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._RepoSummary(ctx, sel, v)
}
func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) {
res, err := graphql.UnmarshalString(v) res, err := graphql.UnmarshalString(v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)

View file

@ -11,10 +11,12 @@ import (
"strconv" "strconv"
"strings" "strings"
glob "github.com/bmatcuk/doublestar/v4" "github.com/99designs/gqlgen/graphql"
v1 "github.com/google/go-containerregistry/pkg/v1" glob "github.com/bmatcuk/doublestar/v4" // nolint:gci
v1 "github.com/google/go-containerregistry/pkg/v1" // nolint:gci
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/vektah/gqlparser/v2/gqlerror"
"zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
digestinfo "zotregistry.io/zot/pkg/extensions/search/digest" digestinfo "zotregistry.io/zot/pkg/extensions/search/digest"
@ -114,80 +116,125 @@ func (r *queryResolver) getImageListForDigest(repoList []string, digest string)
return imgResultForDigest, errResult return imgResultForDigest, errResult
} }
func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageSummary, error) { // nolint:lll
results := make([]*gql_generated.ImageSummary, 0) func (r *queryResolver) repoListWithNewestImage(ctx context.Context, store storage.ImageStore) ([]*gql_generated.RepoSummary, error) {
repos := []*gql_generated.RepoSummary{}
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
repoList, err := store.GetRepositories() repoNames, err := store.GetRepositories()
if err != nil { if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting repositories list") return nil, err
return results, err
} }
if len(repoList) == 0 { for _, repo := range repoNames {
r.log.Info().Msg("no repositories found") lastUpdatedTag, err := olu.GetRepoLastUpdated(repo)
}
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
for _, repo := range repoList {
tagsInfo, err := layoutUtils.GetImageTagsWithTimestamp(repo)
if err != nil { if err != nil {
r.log.Error().Err(err).Msg("extension api: error getting tag timestamp info") graphql.AddError(ctx, err)
return results, err
}
if len(tagsInfo) == 0 {
r.log.Info().Str("no tagsinfo found for repo", repo).Msg(" continuing traversing")
continue continue
} }
latestTag := common.GetLatestTag(tagsInfo) repoSize := int64(0)
repoBlob2Size := make(map[string]int64, 10)
tagsInfo, _ := olu.GetImageTagsWithTimestamp(repo)
digest := godigest.Digest(latestTag.Digest) manifests, err := olu.GetImageManifests(repo)
manifest, err := layoutUtils.GetImageBlobManifest(repo, digest)
if err != nil { if err != nil {
r.log.Error().Err(err).Msg("extension api: error reading manifest") graphql.AddError(ctx, err)
return results, err continue
} }
size := strconv.FormatInt(manifest.Config.Size, 10) repoPlatforms := make([]*gql_generated.OsArch, 0, len(tagsInfo))
repoVendors := make([]*string, 0, len(manifests))
repoName := repo
name := repo var lastUpdatedImageSummary gql_generated.ImageSummary
imageConfig, err := layoutUtils.GetImageInfo(repo, manifest.Config.Digest) var brokenManifest bool
if err != nil {
r.log.Error().Err(err).Msg("extension api: error reading image config")
return results, err for i, manifest := range manifests {
imageLayersSize := int64(0)
manifestSize := olu.GetImageManifestSize(repo, manifests[i].Digest)
imageBlobManifest, _ := olu.GetImageBlobManifest(repo, manifests[i].Digest)
configSize := imageBlobManifest.Config.Size
repoBlob2Size[manifests[i].Digest.String()] = manifestSize
repoBlob2Size[imageBlobManifest.Config.Digest.Hex] = configSize
for _, layer := range imageBlobManifest.Layers {
repoBlob2Size[layer.Digest.String()] = layer.Size
imageLayersSize += layer.Size
}
imageSize := imageLayersSize + manifestSize + configSize
imageConfigInfo, _ := olu.GetImageConfigInfo(repo, manifests[i].Digest)
os, arch := olu.GetImagePlatform(imageConfigInfo)
osArch := &gql_generated.OsArch{
Os: &os,
Arch: &arch,
}
repoPlatforms = append(repoPlatforms, osArch)
vendor := olu.GetImageVendor(imageConfigInfo)
repoVendors = append(repoVendors, &vendor)
manifestTag, ok := manifest.Annotations[ispec.AnnotationRefName]
if !ok {
graphql.AddError(ctx, gqlerror.Errorf("reference not found for this manifest"))
brokenManifest = true
break
}
tag := manifestTag
size := strconv.Itoa(int(imageSize))
isSigned := olu.CheckManifestSignature(repo, manifests[i].Digest)
lastUpdated := olu.GetImageLastUpdated(imageConfigInfo)
score := 0
imageSummary := gql_generated.ImageSummary{
RepoName: &repoName,
Tag: &tag,
LastUpdated: &lastUpdated,
IsSigned: &isSigned,
Size: &size,
Platform: osArch,
Vendor: &vendor,
Score: &score,
}
if tagsInfo[i].Digest == lastUpdatedTag.Digest {
lastUpdatedImageSummary = imageSummary
}
} }
labels := imageConfig.Config.Labels if brokenManifest {
continue
}
// Read Description for blob := range repoBlob2Size {
desc := common.GetDescription(labels) repoSize += repoBlob2Size[blob]
}
// Read licenses repoSizeStr := strconv.FormatInt(repoSize, 10)
license := common.GetLicense(labels) index := 0
// Read vendor repos = append(repos, &gql_generated.RepoSummary{
vendor := common.GetVendor(labels) Name: &repoName,
LastUpdated: &lastUpdatedTag.Timestamp,
// Read categories Size: &repoSizeStr,
categories := common.GetCategories(labels) Platforms: repoPlatforms,
Vendors: repoVendors,
results = append(results, &gql_generated.ImageSummary{ Score: &index,
RepoName: &name, Tag: &latestTag.Name, NewestImage: &lastUpdatedImageSummary,
Description: &desc, Licenses: &license, Vendor: &vendor,
Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp,
}) })
} }
return results, nil return repos, nil
} }
func cleanQuerry(query string) string { func cleanQuerry(query string) string {

View file

@ -84,7 +84,7 @@ type Query {
ImageListForCVE(id: String!): [ImageSummary!] ImageListForCVE(id: String!): [ImageSummary!]
ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!] ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!]
ImageListForDigest(id: String!): [ImageSummary!] ImageListForDigest(id: String!): [ImageSummary!]
ImageListWithLatestTag: [ImageSummary!] RepoListWithNewestImage: [RepoSummary!]! # Newest based on created timestamp
ImageList(repo: String!): [ImageSummary!] ImageList(repo: String!): [ImageSummary!]
ExpandedRepoInfo(repo: String!): RepoInfo! ExpandedRepoInfo(repo: String!): RepoInfo!
GlobalSearch(query: String!): GlobalSearchResult! GlobalSearch(query: String!): GlobalSearchResult!

View file

@ -287,41 +287,41 @@ func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*g
return imgResultForDigest, nil return imgResultForDigest, nil
} }
// ImageListWithLatestTag is the resolver for the ImageListWithLatestTag field. // RepoListWithNewestImage is the resolver for the RepoListWithNewestImage field.
func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_generated.ImageSummary, error) { func (r *queryResolver) RepoListWithNewestImage(ctx context.Context) ([]*gql_generated.RepoSummary, error) {
r.log.Info().Msg("extension api: finding image list") r.log.Info().Msg("extension api: finding image list")
imageList := make([]*gql_generated.ImageSummary, 0) repoList := make([]*gql_generated.RepoSummary, 0)
defaultStore := r.storeController.DefaultStore defaultStore := r.storeController.DefaultStore
dsImageList, err := r.getImageListWithLatestTag(defaultStore) dsRepoList, err := r.repoListWithNewestImage(ctx, defaultStore)
if err != nil { if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting default store image list") r.log.Error().Err(err).Msg("extension api: error extracting default store image list")
return imageList, err return repoList, err
} }
if len(dsImageList) != 0 { if len(dsRepoList) != 0 {
imageList = append(imageList, dsImageList...) repoList = append(repoList, dsRepoList...)
} }
subStore := r.storeController.SubStore subStore := r.storeController.SubStore
for _, store := range subStore { for _, store := range subStore {
ssImageList, err := r.getImageListWithLatestTag(store) ssRepoList, err := r.repoListWithNewestImage(ctx, store)
if err != nil { if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting default store image list") r.log.Error().Err(err).Msg("extension api: error extracting substore image list")
return imageList, err return repoList, err
} }
if len(ssImageList) != 0 { if len(ssRepoList) != 0 {
imageList = append(imageList, ssImageList...) repoList = append(repoList, ssRepoList...)
} }
} }
return imageList, nil return repoList, nil
} }
// ImageList is the resolver for the ImageList field. // ImageList is the resolver for the ImageList field.