mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
feat(artifact): add OCI references support (#936)
Thanks @jdolitsky et al for kicking off these changes at: https://github.com/oci-playground/zot/commits/main Thanks @sudo-bmitch for reviewing the patch Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
This commit is contained in:
committed by
GitHub
parent
eb722905cb
commit
c0f93caacb
+31
-4
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/notaryproject/notation-go"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
oras "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sigstore/cosign/pkg/oci/remote"
|
||||
|
||||
@@ -63,7 +63,8 @@ func ValidateManifest(imgStore ImageStore, repo, reference, mediaType string, bo
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
|
||||
if mediaType == ispec.MediaTypeImageManifest {
|
||||
switch mediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
var manifest ispec.Manifest
|
||||
if err := json.Unmarshal(body, &manifest); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
@@ -79,13 +80,38 @@ func ValidateManifest(imgStore ImageStore, repo, reference, mediaType string, bo
|
||||
return digest, err
|
||||
}
|
||||
}
|
||||
} else if mediaType == artifactspec.MediaTypeArtifactManifest {
|
||||
|
||||
if manifest.Subject != nil {
|
||||
var m ispec.Descriptor
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
}
|
||||
case oras.MediaTypeArtifactManifest:
|
||||
var m notation.Descriptor
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
case ispec.MediaTypeArtifactManifest:
|
||||
var artifact ispec.Artifact
|
||||
if err := json.Unmarshal(body, &artifact); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
|
||||
if artifact.Subject != nil {
|
||||
var m ispec.Descriptor
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
@@ -423,5 +449,6 @@ func ApplyLinter(imgStore ImageStore, linter Lint, repo string, manifestDesc isp
|
||||
func IsSupportedMediaType(mediaType string) bool {
|
||||
return mediaType == ispec.MediaTypeImageIndex ||
|
||||
mediaType == ispec.MediaTypeImageManifest ||
|
||||
mediaType == artifactspec.MediaTypeArtifactManifest
|
||||
mediaType == ispec.MediaTypeArtifactManifest ||
|
||||
mediaType == oras.MediaTypeArtifactManifest
|
||||
}
|
||||
|
||||
@@ -6,16 +6,18 @@ import (
|
||||
|
||||
const (
|
||||
// BlobUploadDir defines the upload directory for blob uploads.
|
||||
BlobUploadDir = ".uploads"
|
||||
SchemaVersion = 2
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
RLOCK = "RLock"
|
||||
RWLOCK = "RWLock"
|
||||
BlobsCache = "blobs"
|
||||
DuplicatesBucket = "duplicates"
|
||||
OriginalBucket = "original"
|
||||
DBExtensionName = ".db"
|
||||
DBCacheLockCheckTimeout = 10 * time.Second
|
||||
BoltdbName = "cache"
|
||||
BlobUploadDir = ".uploads"
|
||||
SchemaVersion = 2
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
RLOCK = "RLock"
|
||||
RWLOCK = "RWLock"
|
||||
BlobsCache = "blobs"
|
||||
DuplicatesBucket = "duplicates"
|
||||
OriginalBucket = "original"
|
||||
DBExtensionName = ".db"
|
||||
DBCacheLockCheckTimeout = 10 * time.Second
|
||||
BoltdbName = "cache"
|
||||
ReferrerFilterAnnotation = "org.opencontainers.references.filtersApplied"
|
||||
//
|
||||
)
|
||||
|
||||
+130
-11
@@ -20,10 +20,11 @@ import (
|
||||
guuid "github.com/gofrs/uuid"
|
||||
"github.com/minio/sha256-simd"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
imeta "github.com/opencontainers/image-spec/specs-go"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/umoci"
|
||||
"github.com/opencontainers/umoci/oci/casext"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
oras "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
@@ -37,8 +38,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
defaultSchemaVersion = 2
|
||||
)
|
||||
|
||||
// ImageStoreLocal provides the image storage operations.
|
||||
@@ -186,8 +188,7 @@ func (is *ImageStoreLocal) initRepo(name string) error {
|
||||
// "index.json" file - create if it doesn't exist
|
||||
indexPath := path.Join(repoDir, "index.json")
|
||||
if _, err := os.Stat(indexPath); err != nil {
|
||||
index := ispec.Index{}
|
||||
index.SchemaVersion = 2
|
||||
index := ispec.Index{Versioned: imeta.Versioned{SchemaVersion: defaultSchemaVersion}}
|
||||
|
||||
buf, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
@@ -1339,7 +1340,120 @@ func (is *ImageStoreLocal) DeleteBlob(repo string, digest godigest.Digest) error
|
||||
}
|
||||
|
||||
func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, artifactType string,
|
||||
) ([]artifactspec.Descriptor, error) {
|
||||
) (ispec.Index, error) {
|
||||
var lockLatency time.Time
|
||||
|
||||
nilIndex := ispec.Index{}
|
||||
|
||||
if err := gdigest.Validate(); err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
dir := path.Join(is.rootDir, repo)
|
||||
if !is.DirExists(dir) {
|
||||
return nilIndex, zerr.ErrRepoNotFound
|
||||
}
|
||||
|
||||
index, err := storage.GetIndex(is, repo, is.log)
|
||||
if err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
is.RLock(&lockLatency)
|
||||
defer is.RUnlock(&lockLatency)
|
||||
|
||||
found := false
|
||||
|
||||
result := []ispec.Descriptor{}
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.Digest == gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
p := path.Join(dir, "blobs", manifest.Digest.Algorithm().String(), manifest.Digest.Encoded())
|
||||
|
||||
buf, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
is.log.Error().Err(err).Str("blob", p).Msg("failed to read manifest")
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return nilIndex, zerr.ErrManifestNotFound
|
||||
}
|
||||
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if manifest.MediaType == ispec.MediaTypeImageManifest {
|
||||
var mfst ispec.Manifest
|
||||
if err := json.Unmarshal(buf, &mfst); err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if mfst.Subject == nil || mfst.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
if artifactType != "" && mfst.Config.MediaType != artifactType {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
ArtifactType: mfst.Config.MediaType,
|
||||
Size: manifest.Size,
|
||||
Digest: manifest.Digest,
|
||||
Annotations: mfst.Annotations,
|
||||
})
|
||||
} else if manifest.MediaType == ispec.MediaTypeArtifactManifest {
|
||||
var art ispec.Artifact
|
||||
if err := json.Unmarshal(buf, &art); err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if art.Subject == nil || art.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
if artifactType != "" && art.ArtifactType != artifactType {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
ArtifactType: art.ArtifactType,
|
||||
Size: manifest.Size,
|
||||
Digest: manifest.Digest,
|
||||
Annotations: art.Annotations,
|
||||
})
|
||||
}
|
||||
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nilIndex, zerr.ErrManifestNotFound
|
||||
}
|
||||
|
||||
index = ispec.Index{
|
||||
Versioned: imeta.Versioned{SchemaVersion: defaultSchemaVersion},
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
Manifests: result,
|
||||
Annotations: map[string]string{},
|
||||
}
|
||||
|
||||
// response was filtered by artifactType
|
||||
if artifactType != "" {
|
||||
index.Annotations[storageConstants.ReferrerFilterAnnotation] = artifactType
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (is *ImageStoreLocal) GetOrasReferrers(repo string, gdigest godigest.Digest, artifactType string,
|
||||
) ([]oras.Descriptor, error) {
|
||||
var lockLatency time.Time
|
||||
|
||||
if err := gdigest.Validate(); err != nil {
|
||||
@@ -1361,10 +1475,10 @@ func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, ar
|
||||
|
||||
found := false
|
||||
|
||||
result := []artifactspec.Descriptor{}
|
||||
result := []oras.Descriptor{}
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.MediaType != artifactspec.MediaTypeArtifactManifest {
|
||||
if manifest.MediaType != oras.MediaTypeArtifactManifest {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1381,18 +1495,23 @@ func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, ar
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var artManifest artifactspec.Manifest
|
||||
var artManifest oras.Manifest
|
||||
if err := json.Unmarshal(buf, &artManifest); err != nil {
|
||||
is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if artifactType != artManifest.ArtifactType || gdigest != artManifest.Subject.Digest {
|
||||
if artManifest.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, artifactspec.Descriptor{
|
||||
// filter by artifact type
|
||||
if artifactType != "" && artManifest.ArtifactType != artifactType {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, oras.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
ArtifactType: artManifest.ArtifactType,
|
||||
Digest: manifest.Digest,
|
||||
|
||||
@@ -147,14 +147,14 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// invalid GetReferrers
|
||||
_, err = imgStore.GetReferrers("invalid", "invalid", "invalid")
|
||||
// invalid GetOrasReferrers
|
||||
_, err = imgStore.GetOrasReferrers("invalid", "invalid", "invalid")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = imgStore.GetReferrers(repoName, "invalid", "invalid")
|
||||
_, err = imgStore.GetOrasReferrers(repoName, "invalid", "invalid")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = imgStore.GetReferrers(repoName, digest, "invalid")
|
||||
_, err = imgStore.GetOrasReferrers(repoName, digest, "invalid")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// invalid DeleteImageManifest
|
||||
@@ -175,7 +175,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetReferrers(t *testing.T) {
|
||||
func TestGetOrasReferrers(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
log := log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||
@@ -218,7 +218,7 @@ func TestGetReferrers(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
descriptors, err := imgStore.GetReferrers("zot-test", digest, "signature-example")
|
||||
descriptors, err := imgStore.GetOrasReferrers("zot-test", digest, "signature-example")
|
||||
So(err, ShouldBeNil)
|
||||
So(descriptors, ShouldNotBeEmpty)
|
||||
So(descriptors[0].ArtifactType, ShouldEqual, "signature-example")
|
||||
@@ -982,7 +982,7 @@ func FuzzGetBlobContent(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzGetReferrers(f *testing.F) {
|
||||
func FuzzGetOrasReferrers(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data string) {
|
||||
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||
metrics := monitoring.NewMetricsServer(false, *log)
|
||||
@@ -1033,7 +1033,7 @@ func FuzzGetReferrers(f *testing.F) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = imgStore.GetReferrers("zot-test", digest, data)
|
||||
_, err = imgStore.GetOrasReferrers("zot-test", digest, data)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrManifestNotFound) || isKnownErr(err) {
|
||||
return
|
||||
|
||||
@@ -1224,7 +1224,12 @@ func (is *ObjectStorage) GetBlobContent(repo string, digest godigest.Digest) ([]
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (is *ObjectStorage) GetReferrers(repo string, digest godigest.Digest, mediaType string,
|
||||
func (is *ObjectStorage) GetReferrers(repo string, digest godigest.Digest, artifactType string,
|
||||
) (ispec.Index, error) {
|
||||
return ispec.Index{}, zerr.ErrMethodNotSupported
|
||||
}
|
||||
|
||||
func (is *ObjectStorage) GetOrasReferrers(repo string, digest godigest.Digest, artifactType string,
|
||||
) ([]artifactspec.Descriptor, error) {
|
||||
return nil, zerr.ErrMethodNotSupported
|
||||
}
|
||||
|
||||
@@ -917,6 +917,18 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrMethodNotSupported)
|
||||
})
|
||||
|
||||
Convey("Test GetOrasReferrers", func(c C) {
|
||||
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
|
||||
DeleteFn: func(ctx context.Context, path string) error {
|
||||
return errS3
|
||||
},
|
||||
})
|
||||
d := godigest.FromBytes([]byte(""))
|
||||
_, err := imgStore.GetOrasReferrers(testImage, d, "application/image")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrMethodNotSupported)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
@@ -48,7 +49,8 @@ type ImageStore interface { //nolint:interfacebloat
|
||||
DeleteBlob(repo string, digest godigest.Digest) error
|
||||
GetIndexContent(repo string) ([]byte, error)
|
||||
GetBlobContent(repo string, digest godigest.Digest) ([]byte, error)
|
||||
GetReferrers(repo string, digest godigest.Digest, mediaType string) ([]artifactspec.Descriptor, error)
|
||||
GetReferrers(repo string, digest godigest.Digest, artifactType string) (ispec.Index, error)
|
||||
GetOrasReferrers(repo string, digest godigest.Digest, artifactType string) ([]artifactspec.Descriptor, error)
|
||||
RunGCRepo(repo string) error
|
||||
RunGCPeriodically(interval time.Duration, sch *scheduler.Scheduler)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user