forked from distribution/distribution
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: implement artifact manifest handler in the storage package (#30)
Implemented artifact manifest handler related changes for the storage package, unit tests included. Part 3 of #21 Signed-off-by: wangxiaoxuan273 <[email protected]>
- Loading branch information
1 parent
0fce2c9
commit d03813c
Showing
5 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package storage | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/distribution/distribution/v3" | ||
dcontext "github.com/distribution/distribution/v3/context" | ||
"github.com/distribution/distribution/v3/manifest/ociartifact" | ||
"github.com/opencontainers/go-digest" | ||
v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||
) | ||
|
||
// ociArtifactManifestHandler is a ManifestHandler that covers oci artifact manifests. | ||
type ociArtifactManifestHandler struct { | ||
repository distribution.Repository | ||
blobStore distribution.BlobStore | ||
ctx context.Context | ||
} | ||
|
||
var _ ManifestHandler = &ociArtifactManifestHandler{} | ||
|
||
func (ms *ociArtifactManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) { | ||
dcontext.GetLogger(ms.ctx).Debug("(*ociArtifactManifestHandler).Unmarshal") | ||
|
||
m := &ociartifact.DeserializedManifest{} | ||
if err := m.UnmarshalJSON(content); err != nil { | ||
return nil, err | ||
} | ||
|
||
return m, nil | ||
} | ||
|
||
func (ms *ociArtifactManifestHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) { | ||
dcontext.GetLogger(ms.ctx).Debug("(*ociArtifactManifestHandler).Put") | ||
|
||
m, ok := manifest.(*ociartifact.DeserializedManifest) | ||
if !ok { | ||
return "", fmt.Errorf("non-oci artifact manifest put to ociArtifactManifestHandler: %T", manifest) | ||
} | ||
|
||
if err := ms.verifyArtifactManifest(ms.ctx, m, skipDependencyVerification); err != nil { | ||
return "", err | ||
} | ||
|
||
mt, payload, err := m.Payload() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
revision, err := ms.blobStore.Put(ctx, mt, payload) | ||
if err != nil { | ||
dcontext.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err) | ||
return "", err | ||
} | ||
|
||
return revision.Digest, nil | ||
} | ||
|
||
// verifyArtifactManifest ensures that the manifest content is valid from the | ||
// perspective of the registry. As a policy, the registry only tries to store | ||
// valid content, leaving trust policies of that content up to consumers. | ||
func (ms *ociArtifactManifestHandler) verifyArtifactManifest(ctx context.Context, mnfst *ociartifact.DeserializedManifest, skipDependencyVerification bool) error { | ||
var errs distribution.ErrManifestVerification | ||
|
||
if mnfst.MediaType != v1.MediaTypeArtifactManifest { | ||
return fmt.Errorf("unrecognized manifest media type %s", mnfst.MediaType) | ||
} | ||
|
||
if skipDependencyVerification { | ||
return nil | ||
} | ||
|
||
// validate the subject | ||
if mnfst.Subject != nil { | ||
// check if the digest is valid | ||
err := mnfst.Subject.Digest.Validate() | ||
if err != nil { | ||
errs = append(errs, err, distribution.ErrManifestBlobUnknown{Digest: mnfst.Subject.Digest}) | ||
} else { | ||
// check the presence | ||
manifestService, err := ms.repository.Manifests(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
exists, err := manifestService.Exists(ctx, mnfst.Subject.Digest) | ||
if err != nil || !exists { | ||
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: mnfst.Subject.Digest}) | ||
} | ||
} | ||
} | ||
|
||
// validate the blobs | ||
blobsService := ms.repository.Blobs(ctx) | ||
for _, descriptor := range mnfst.Blobs { | ||
// check if the digest is valid | ||
err := descriptor.Digest.Validate() | ||
if err != nil { | ||
errs = append(errs, err, distribution.ErrManifestBlobUnknown{Digest: descriptor.Digest}) | ||
continue | ||
} | ||
|
||
_, err = blobsService.Stat(ctx, descriptor.Digest) | ||
if err != nil { | ||
errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: descriptor.Digest}) | ||
} | ||
} | ||
|
||
if len(errs) != 0 { | ||
return errs | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package storage | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/distribution/distribution/v3" | ||
"github.com/distribution/distribution/v3/manifest/ociartifact" | ||
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory" | ||
"github.com/opencontainers/go-digest" | ||
v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||
) | ||
|
||
func TestVerifyOCIArtifactManifestBlobsAndSubject(t *testing.T) { | ||
ctx := context.Background() | ||
inmemoryDriver := inmemory.New() | ||
registry := createRegistry(t, inmemoryDriver) | ||
repo := makeRepository(t, registry, strings.ToLower(t.Name())) | ||
manifestService := makeManifestService(t, repo) | ||
|
||
subject, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageManifest, nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
blob, err := repo.Blobs(ctx).Put(ctx, v1.MediaTypeImageLayer, nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
template := ociartifact.Manifest{ | ||
MediaType: v1.MediaTypeArtifactManifest, | ||
} | ||
|
||
checkFn := func(m ociartifact.Manifest, rerr error) { | ||
dm, err := ociartifact.FromStruct(m) | ||
if err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
_, err = manifestService.Put(ctx, dm) | ||
if verr, ok := err.(distribution.ErrManifestVerification); ok { | ||
// Extract the first error | ||
if len(verr) == 2 { | ||
if _, ok = verr[1].(distribution.ErrManifestBlobUnknown); ok { | ||
err = verr[0] | ||
} | ||
} else if len(verr) == 1 { | ||
err = verr[0] | ||
} | ||
} | ||
if err != rerr { | ||
t.Errorf("%#v: expected %v, got %v", m, rerr, err) | ||
} | ||
} | ||
|
||
type testcase struct { | ||
Desc distribution.Descriptor | ||
Err error | ||
} | ||
|
||
layercases := []testcase{ | ||
// normal blob with media type v1.MediaTypeImageLayer | ||
{ | ||
blob, | ||
nil, | ||
}, | ||
// blob with empty media type but valid digest | ||
{ | ||
distribution.Descriptor{Digest: blob.Digest}, | ||
nil, | ||
}, | ||
// blob with invalid digest | ||
{ | ||
distribution.Descriptor{Digest: digest.Digest("invalid")}, | ||
digest.ErrDigestInvalidFormat, | ||
}, | ||
// empty descriptor | ||
{ | ||
distribution.Descriptor{}, | ||
digest.ErrDigestInvalidFormat, | ||
}, | ||
} | ||
|
||
for _, c := range layercases { | ||
m := template | ||
m.Subject = &subject | ||
m.Blobs = []distribution.Descriptor{c.Desc} | ||
checkFn(m, c.Err) | ||
} | ||
|
||
subjectcases := []testcase{ | ||
// normal subject | ||
{ | ||
subject, | ||
nil, | ||
}, | ||
// subject with invalid digest | ||
{ | ||
distribution.Descriptor{Digest: digest.Digest("invalid")}, | ||
digest.ErrDigestInvalidFormat, | ||
}, | ||
} | ||
|
||
for _, c := range subjectcases { | ||
m := template | ||
m.Subject = &c.Desc | ||
m.Blobs = []distribution.Descriptor{blob} | ||
checkFn(m, c.Err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters