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

search: graphql api to give detailed repo info

DetailedRepoInfo graphql api returns detailed repo info given repo name
repo contains its manifests info
Each manifest entry contains digest,signed, tag and layers info
Each layer info containes digest, size

Signed-off-by: Shivam Mishra <shimish2@cisco.com>
This commit is contained in:
Shivam Mishra 2022-02-02 02:02:05 +00:00 committed by Ramkumar Chinchani
parent 4ddfd059b6
commit 37d150e32f
7 changed files with 942 additions and 29 deletions

2
go.mod
View file

@ -41,6 +41,7 @@ require (
github.com/opencontainers/umoci v0.4.8-0.20210922062158-e60a0cc726e6
github.com/oras-project/artifacts-spec v1.0.0-draft.1
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/client_model v0.2.0
github.com/rs/zerolog v1.26.0
@ -271,7 +272,6 @@ require (
github.com/owenrumney/squealer v0.2.28 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.31.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect

View file

@ -36,6 +36,15 @@ type ImgResponsWithLatestTag struct {
Errors []ErrorGQL `json:"errors"`
}
type ExpandedRepoInfoResp struct {
ExpandedRepoInfo ExpandedRepoInfo `json:"data"`
Errors []ErrorGQL `json:"errors"`
}
type ExpandedRepoInfo struct {
RepoInfo common.RepoInfo `json:"expandedRepoInfo"`
}
//nolint:tagliatelle // graphQL schema
type ImgListWithLatestTag struct {
Images []ImageInfo `json:"ImageListWithLatestTag"`
@ -311,6 +320,110 @@ func TestLatestTagSearchHTTP(t *testing.T) {
})
}
func TestExpandedRepoInfo(t *testing.T) {
Convey("Test expanded repo info", t, func() {
err := testSetup()
if err != nil {
panic(err)
}
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = rootDir
conf.Storage.SubPaths = make(map[string]config.StorageConfig)
conf.Storage.SubPaths["/a"] = config.StorageConfig{RootDirectory: subRootDir}
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(); 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 + "/v2/")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, err = resty.R().Get(baseURL + "/query")
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
query := "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + "/query?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
responseStruct := &ExpandedRepoInfoResp{}
err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
query = "{ExpandedRepoInfo(repo:\"\"){Manifests%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + "/query?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
query = "{ExpandedRepoInfo(repo:\"a/zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + "/query?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256",
"2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396"))
if err != nil {
panic(err)
}
query = "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + "/query?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil)
})
}
func TestUtilsMethod(t *testing.T) {
Convey("Test utils", t, func() {
// Test GetRepo method

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
goerrors "errors"
"path"
"strconv"
"strings"
"time"
@ -17,12 +18,30 @@ import (
"zotregistry.io/zot/pkg/storage"
)
const cosignedAnnotation = "dev.cosign.signature.baseimage"
// OciLayoutInfo ...
type OciLayoutUtils struct {
Log log.Logger
StoreController storage.StoreController
}
type RepoInfo struct {
Manifests []Manifest `json:"manifests"`
}
type Manifest struct {
Tag string `json:"tag"`
Digest string `json:"digest"`
IsSigned bool `json:"isSigned"`
Layers []Layer `json:"layers"`
}
type Layer struct {
Size string `json:"size"`
Digest string `json:"digest"`
}
// NewOciLayoutUtils initializes a new OciLayoutUtils object.
func NewOciLayoutUtils(storeController storage.StoreController, log log.Logger) *OciLayoutUtils {
return &OciLayoutUtils{Log: log, StoreController: storeController}
@ -183,6 +202,65 @@ func (olu OciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, err
return tagsInfo, nil
}
func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
repo := RepoInfo{}
manifests := make([]Manifest, 0)
manifestList, err := olu.GetImageManifests(name)
if err != nil {
olu.Log.Error().Err(err).Msg("error getting image manifests")
return RepoInfo{}, err
}
for _, manifest := range manifestList {
manifestInfo := Manifest{}
manifestInfo.Digest = manifest.Digest.Encoded()
manifestInfo.IsSigned = false
tag, ok := manifest.Annotations[ispec.AnnotationRefName]
if !ok {
tag = "latest"
}
manifestInfo.Tag = tag
manifest, err := olu.GetImageBlobManifest(name, manifest.Digest)
if err != nil {
olu.Log.Error().Err(err).Msg("error getting image manifest blob")
return RepoInfo{}, err
}
layers := make([]Layer, 0)
for _, layer := range manifest.Layers {
layerInfo := Layer{}
layerInfo.Digest = layer.Digest.Hex
layerInfo.Size = strconv.FormatInt(layer.Size, 10)
layers = append(layers, layerInfo)
if _, ok := layer.Annotations[cosignedAnnotation]; ok {
manifestInfo.IsSigned = true
}
}
manifestInfo.Layers = layers
manifests = append(manifests, manifestInfo)
}
repo.Manifests = manifests
return repo, nil
}
func GetImageDirAndTag(imageName string) (string, string) {
var imageDir string

File diff suppressed because it is too large Load diff

View file

@ -44,12 +44,28 @@ type ImgResultForFixedCve struct {
Tags []*TagInfo `json:"Tags"`
}
type LayerInfo struct {
Size *string `json:"Size"`
Digest *string `json:"Digest"`
}
type ManifestInfo struct {
Digest *string `json:"Digest"`
Tag *string `json:"Tag"`
IsSigned *bool `json:"IsSigned"`
Layers []*LayerInfo `json:"Layers"`
}
type PackageInfo struct {
Name *string `json:"Name"`
InstalledVersion *string `json:"InstalledVersion"`
FixedVersion *string `json:"FixedVersion"`
}
type RepoInfo struct {
Manifests []*ManifestInfo `json:"Manifests"`
}
type TagInfo struct {
Name *string `json:"Name"`
Digest *string `json:"Digest"`

View file

@ -62,6 +62,52 @@ func GetResolverConfig(log log.Logger, storeController storage.StoreController,
}
}
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, name string) (*RepoInfo, error) {
olu := common.NewOciLayoutUtils(r.storeController, r.log)
repo, err := olu.GetExpandedRepoInfo(name)
if err != nil {
r.log.Error().Err(err).Msg("error getting repos")
return &RepoInfo{}, err
}
// repos type is of common deep copy this to search
repoInfo := &RepoInfo{}
manifests := make([]*ManifestInfo, 0)
for _, manifest := range repo.Manifests {
tag := manifest.Tag
digest := manifest.Digest
isSigned := manifest.IsSigned
manifestInfo := &ManifestInfo{Tag: &tag, Digest: &digest, IsSigned: &isSigned}
layers := make([]*LayerInfo, 0)
for _, l := range manifest.Layers {
size := l.Size
digest := l.Digest
layerInfo := &LayerInfo{Digest: &digest, Size: &size}
layers = append(layers, layerInfo)
}
manifestInfo.Layers = layers
manifests = append(manifests, manifestInfo)
}
repoInfo.Manifests = manifests
return repoInfo, nil
}
func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error) {
trivyCtx := r.cveInfo.GetTrivyContext(image)

View file

@ -50,10 +50,27 @@ type ImageInfo {
Labels: String
}
type RepoInfo {
Manifests: [ManifestInfo]
}
type ManifestInfo {
Digest: String
Tag: String
IsSigned: Boolean
Layers: [LayerInfo]
}
type LayerInfo {
Size: String # Int64 is not supported.
Digest: String
}
type Query {
CVEListForImage(image: String!) :CVEResultForImage
ImageListForCVE(id: String!) :[ImgResultForCVE]
ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE
ImageListForDigest(id: String!) :[ImgResultForDigest]
ImageListWithLatestTag:[ImageInfo]
ExpandedRepoInfo(repo: String!):RepoInfo
}