mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
feat(referrers): added index support for referrers queries (#1560)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
@@ -58,6 +58,10 @@ func GetManifestArtifactType(manifestContent ispec.Manifest) string {
|
||||
return manifestContent.Config.MediaType
|
||||
}
|
||||
|
||||
func GetIndexArtifactType(indexContent ispec.Index) string {
|
||||
return indexContent.ArtifactType
|
||||
}
|
||||
|
||||
// GetImageLastUpdated This method will return last updated timestamp.
|
||||
// The Created timestamp is used, but if it is missing, look at the
|
||||
// history field and, if provided, return the timestamp of last entry in history.
|
||||
|
||||
@@ -1064,6 +1064,107 @@ func TestGetReferrersGQL(t *testing.T) {
|
||||
|
||||
So(referrersResp.Referrers[0].Digest, ShouldEqual, artifactManifestDigest)
|
||||
})
|
||||
|
||||
Convey("Get referrers with index as referrer", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.RootDirectory = t.TempDir()
|
||||
conf.Storage.GC = false
|
||||
|
||||
defaultVal := true
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
Lint: &extconf.LintConfig{
|
||||
BaseConfig: extconf.BaseConfig{
|
||||
Enable: &defaultVal,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
conf.Extensions.Search.CVE = nil
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
// Upload the index referrer
|
||||
|
||||
targetImg, err := GetRandomImage("")
|
||||
So(err, ShouldBeNil)
|
||||
targetDigest, err := targetImg.Digest()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = UploadImage(targetImg, baseURL, "repo")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexReferrer, err := GetRandomMultiarchImage("ref")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactType := "art.type"
|
||||
indexReferrer.Index.ArtifactType = artifactType
|
||||
indexReferrer.Index.Subject = &ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: targetDigest,
|
||||
}
|
||||
|
||||
indexReferrerDigest, err := indexReferrer.Digest()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = UploadMultiarchImage(indexReferrer, baseURL, "repo")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Call Referrers GQL
|
||||
|
||||
referrersQuery := `
|
||||
{
|
||||
Referrers( repo: "%s", digest: "%s"){
|
||||
ArtifactType,
|
||||
Digest,
|
||||
MediaType,
|
||||
Size,
|
||||
Annotations{
|
||||
Key
|
||||
Value
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
referrersQuery = fmt.Sprintf(referrersQuery, "repo", targetDigest.String())
|
||||
|
||||
resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(referrersQuery))
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
So(resp.Body(), ShouldNotBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
referrersResp := &zcommon.ReferrersResp{}
|
||||
|
||||
err = json.Unmarshal(resp.Body(), referrersResp)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(referrersResp.Referrers), ShouldEqual, 1)
|
||||
So(referrersResp.Referrers[0].ArtifactType, ShouldResemble, artifactType)
|
||||
So(referrersResp.Referrers[0].Digest, ShouldResemble, indexReferrerDigest.String())
|
||||
So(referrersResp.Referrers[0].MediaType, ShouldResemble, ispec.MediaTypeImageIndex)
|
||||
|
||||
// Make REST call
|
||||
|
||||
resp, err = resty.R().Get(baseURL + "/v2/repo/referrers/" + targetDigest.String())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var index ispec.Index
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &index)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(index.Manifests), ShouldEqual, 1)
|
||||
So(index.Manifests[0].ArtifactType, ShouldEqual, artifactType)
|
||||
So(index.Manifests[0].Digest.String(), ShouldResemble, indexReferrerDigest.String())
|
||||
So(index.Manifests[0].MediaType, ShouldResemble, ispec.MediaTypeImageIndex)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExpandedRepoInfo(t *testing.T) {
|
||||
|
||||
@@ -407,22 +407,43 @@ func GetReferredSubject(descriptorBlob []byte, referrerDigest, mediaType string,
|
||||
referrerSubject *ispec.Descriptor
|
||||
)
|
||||
|
||||
var manifestContent ispec.Manifest
|
||||
switch mediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
var manifestContent ispec.Manifest
|
||||
|
||||
err := json.Unmarshal(descriptorBlob, &manifestContent)
|
||||
if err != nil {
|
||||
return "", referrerInfo, false,
|
||||
fmt.Errorf("repodb: can't unmarshal manifest for digest %s: %w", referrerDigest, err)
|
||||
}
|
||||
err := json.Unmarshal(descriptorBlob, &manifestContent)
|
||||
if err != nil {
|
||||
return "", referrerInfo, false,
|
||||
fmt.Errorf("repodb: can't unmarshal manifest for digest %s: %w", referrerDigest, err)
|
||||
}
|
||||
|
||||
referrerSubject = manifestContent.Subject
|
||||
referrerSubject = manifestContent.Subject
|
||||
|
||||
referrerInfo = ReferrerInfo{
|
||||
Digest: referrerDigest,
|
||||
MediaType: mediaType,
|
||||
ArtifactType: zcommon.GetManifestArtifactType(manifestContent),
|
||||
Size: len(descriptorBlob),
|
||||
Annotations: manifestContent.Annotations,
|
||||
referrerInfo = ReferrerInfo{
|
||||
Digest: referrerDigest,
|
||||
MediaType: mediaType,
|
||||
ArtifactType: zcommon.GetManifestArtifactType(manifestContent),
|
||||
Size: len(descriptorBlob),
|
||||
Annotations: manifestContent.Annotations,
|
||||
}
|
||||
case ispec.MediaTypeImageIndex:
|
||||
var indexContent ispec.Index
|
||||
|
||||
err := json.Unmarshal(descriptorBlob, &indexContent)
|
||||
if err != nil {
|
||||
return "", referrerInfo, false,
|
||||
fmt.Errorf("repodb: can't unmarshal manifest for digest %s: %w", referrerDigest, err)
|
||||
}
|
||||
|
||||
referrerSubject = indexContent.Subject
|
||||
|
||||
referrerInfo = ReferrerInfo{
|
||||
Digest: referrerDigest,
|
||||
MediaType: mediaType,
|
||||
ArtifactType: zcommon.GetIndexArtifactType(indexContent),
|
||||
Size: len(descriptorBlob),
|
||||
Annotations: indexContent.Annotations,
|
||||
}
|
||||
}
|
||||
|
||||
if referrerSubject == nil || referrerSubject.Digest.String() == "" {
|
||||
|
||||
@@ -621,6 +621,9 @@ func TestGetReferredSubject(t *testing.T) {
|
||||
Convey("GetReferredSubject error", t, func() {
|
||||
_, _, _, err := repodb.GetReferredSubject([]byte("bad json"), "digest", ispec.MediaTypeImageManifest)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, _, err = repodb.GetReferredSubject([]byte("bad json"), "digest", ispec.MediaTypeImageIndex)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -581,14 +581,14 @@ func GetReferrers(imgStore storageTypes.ImageStore, repo string, gdigest godiges
|
||||
|
||||
result := []ispec.Descriptor{}
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.Digest == gdigest {
|
||||
for _, descriptor := range index.Manifests {
|
||||
if descriptor.Digest == gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
buf, err := imgStore.GetBlobContent(repo, manifest.Digest)
|
||||
buf, err := imgStore.GetBlobContent(repo, descriptor.Digest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("blob", imgStore.BlobPath(repo, manifest.Digest)).Msg("failed to read manifest")
|
||||
log.Error().Err(err).Str("blob", imgStore.BlobPath(repo, descriptor.Digest)).Msg("failed to read manifest")
|
||||
|
||||
if errors.Is(err, zerr.ErrBlobNotFound) {
|
||||
return nilIndex, zerr.ErrManifestNotFound
|
||||
@@ -597,31 +597,59 @@ func GetReferrers(imgStore storageTypes.ImageStore, repo string, gdigest godiges
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if manifest.MediaType == ispec.MediaTypeImageManifest {
|
||||
var mfst ispec.Manifest
|
||||
if err := json.Unmarshal(buf, &mfst); err != nil {
|
||||
log.Error().Err(err).Str("manifest digest", manifest.Digest.String()).Msg("invalid JSON")
|
||||
switch descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
var manifestContent ispec.Manifest
|
||||
|
||||
if err := json.Unmarshal(buf, &manifestContent); err != nil {
|
||||
log.Error().Err(err).Str("manifest digest", descriptor.Digest.String()).Msg("invalid JSON")
|
||||
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if mfst.Subject == nil || mfst.Subject.Digest != gdigest {
|
||||
if manifestContent.Subject == nil || manifestContent.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
manifestArtifactType := zcommon.GetManifestArtifactType(mfst)
|
||||
manifestArtifactType := zcommon.GetManifestArtifactType(manifestContent)
|
||||
|
||||
if len(artifactTypes) > 0 && !zcommon.Contains(artifactTypes, manifestArtifactType) {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
MediaType: descriptor.MediaType,
|
||||
ArtifactType: manifestArtifactType,
|
||||
Size: manifest.Size,
|
||||
Digest: manifest.Digest,
|
||||
Annotations: mfst.Annotations,
|
||||
Size: descriptor.Size,
|
||||
Digest: descriptor.Digest,
|
||||
Annotations: manifestContent.Annotations,
|
||||
})
|
||||
case ispec.MediaTypeImageIndex:
|
||||
var indexContent ispec.Index
|
||||
|
||||
if err := json.Unmarshal(buf, &indexContent); err != nil {
|
||||
log.Error().Err(err).Str("manifest digest", descriptor.Digest.String()).Msg("invalid JSON")
|
||||
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if indexContent.Subject == nil || indexContent.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
indexArtifactType := zcommon.GetIndexArtifactType(indexContent)
|
||||
|
||||
if len(artifactTypes) > 0 && !zcommon.Contains(artifactTypes, indexArtifactType) {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ispec.Descriptor{
|
||||
MediaType: descriptor.MediaType,
|
||||
ArtifactType: indexArtifactType,
|
||||
Size: descriptor.Size,
|
||||
Digest: descriptor.Digest,
|
||||
Annotations: indexContent.Annotations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +334,49 @@ func TestGetReferrersErrors(t *testing.T) {
|
||||
[]string{artifactType}, log.With().Caller().Logger())
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Index bad blob", func() {
|
||||
imgStore = &mocks.MockedImageStore{
|
||||
GetIndexContentFn: func(repo string) ([]byte, error) {
|
||||
return []byte(`{
|
||||
"manifests": [{
|
||||
"digest": "digest",
|
||||
"mediaType": "application/vnd.oci.image.index.v1+json"
|
||||
}]
|
||||
}`), nil
|
||||
},
|
||||
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return []byte("bad blob"), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = common.GetReferrers(imgStore, "zot-test", validDigest,
|
||||
[]string{}, log.With().Caller().Logger())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Index bad artifac type", func() {
|
||||
imgStore = &mocks.MockedImageStore{
|
||||
GetIndexContentFn: func(repo string) ([]byte, error) {
|
||||
return []byte(`{
|
||||
"manifests": [{
|
||||
"digest": "digest",
|
||||
"mediaType": "application/vnd.oci.image.index.v1+json"
|
||||
}]
|
||||
}`), nil
|
||||
},
|
||||
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return []byte(`{
|
||||
"subject": {"digest": "` + validDigest.String() + `"}
|
||||
}`), nil
|
||||
},
|
||||
}
|
||||
|
||||
ref, err := common.GetReferrers(imgStore, "zot-test", validDigest,
|
||||
[]string{"art.type"}, log.With().Caller().Logger())
|
||||
So(err, ShouldBeNil)
|
||||
So(len(ref.Manifests), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user