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

api: implement delete by tag

This commit is contained in:
Shivam Mishra 2021-08-20 14:08:41 -07:00 committed by Ramkumar Chinchani
parent fed5c09b71
commit c6670b1329
4 changed files with 83 additions and 25 deletions

View file

@ -17,6 +17,7 @@ https://anuvu.github.io/zot/
* Uses [OCI image layout](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) for image storage * Uses [OCI image layout](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) for image storage
* Can serve any OCI image layout as a registry * Can serve any OCI image layout as a registry
* Supports [helm charts](https://helm.sh/docs/topics/registries/) * Supports [helm charts](https://helm.sh/docs/topics/registries/)
* Supports image deletion by tag
* Currently suitable for on-prem deployments (e.g. colocated with Kubernetes) * Currently suitable for on-prem deployments (e.g. colocated with Kubernetes)
* Compatible with ecosystem tools such as [skopeo](#skopeo) and [cri-o](#cri-o) * Compatible with ecosystem tools such as [skopeo](#skopeo) and [cri-o](#cri-o)
* [Vulnerability scanning of images](#Scanning-images-for-known-vulnerabilities) * [Vulnerability scanning of images](#Scanning-images-for-known-vulnerabilities)

View file

@ -516,6 +516,14 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(d, ShouldNotBeEmpty) So(d, ShouldNotBeEmpty)
So(d, ShouldEqual, digest.String()) So(d, ShouldEqual, digest.String())
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(baseURL + "/v2/repo7/manifests/test:1.0.1")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201)
d = resp.Header().Get(api.DistContentDigestKey)
So(d, ShouldNotBeEmpty)
So(d, ShouldEqual, digest.String())
content = []byte("this is a blob5") content = []byte("this is a blob5")
digest = godigest.FromBytes(content) digest = godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
@ -565,11 +573,11 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// delete manifest by tag should fail // delete manifest by tag should pass
resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/test:1.0") resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 202)
// delete manifest by digest // delete manifest by digest (1.0 deleted but 1.0.1 has same reference)
resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String()) resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
@ -880,15 +888,6 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// delete manifest by tag should fail
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/test:1.0")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400)
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/test:1.0")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400)
// delete manifest by digest // delete manifest by digest
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -588,11 +588,14 @@ func (is *ImageStore) DeleteImageManifest(repo string, reference string) error {
return errors.ErrRepoNotFound return errors.ErrRepoNotFound
} }
// as per spec "reference" can only be a digest and not a tag isTag := false
// as per spec "reference" can be a digest and a tag
digest, err := godigest.Parse(reference) digest, err := godigest.Parse(reference)
if err != nil { if err != nil {
is.log.Error().Err(err).Msg("invalid reference") is.log.Debug().Str("invalid digest: ", reference).Msg("storage: assuming tag")
return errors.ErrBadManifest
isTag = true
} }
is.Lock() is.Lock()
@ -620,7 +623,19 @@ func (is *ImageStore) DeleteImageManifest(repo string, reference string) error {
outIndex.Manifests = []ispec.Descriptor{} outIndex.Manifests = []ispec.Descriptor{}
for _, m = range index.Manifests { for _, m = range index.Manifests {
if reference == m.Digest.String() { if isTag {
tag, ok := m.Annotations[ispec.AnnotationRefName]
if ok && tag == reference {
is.log.Debug().Str("deleting tag", tag).Msg("")
digest = m.Digest
found = true
continue
}
} else if reference == m.Digest.String() {
is.log.Debug().Str("deleting reference", reference).Msg("")
found = true found = true
continue continue
} }
@ -657,9 +672,22 @@ func (is *ImageStore) DeleteImageManifest(repo string, reference string) error {
} }
} }
p := path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded()) // Delete blob only when blob digest not present in manifest entry.
// e.g. 1.0.1 & 1.0.2 have same blob digest so if we delete 1.0.1, blob should not be removed.
toDelete := true
_ = os.Remove(p) for _, m = range outIndex.Manifests {
if digest.String() == m.Digest.String() {
toDelete = false
break
}
}
if toDelete {
p := path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded())
_ = os.Remove(p)
}
return nil return nil
} }

View file

@ -129,6 +129,8 @@ func TestAPIs(t *testing.T) {
}) })
Convey("Good image manifest", func() { Convey("Good image manifest", func() {
annotationsMap := make(map[string]string)
annotationsMap[ispec.AnnotationRefName] = "1.0"
m := ispec.Manifest{ m := ispec.Manifest{
Config: ispec.Descriptor{ Config: ispec.Descriptor{
Digest: d, Digest: d,
@ -141,29 +143,57 @@ func TestAPIs(t *testing.T) {
Size: int64(l), Size: int64(l),
}, },
}, },
Annotations: map[string]string{ispec.AnnotationRefName: "1.0"}, Annotations: annotationsMap,
} }
m.SchemaVersion = 2 m.SchemaVersion = 2
mb, _ = json.Marshal(m) mb, _ := json.Marshal(m)
d := godigest.FromBytes(mb) d := godigest.FromBytes(mb)
_, err = il.PutImageManifest("test", d.String(), ispec.MediaTypeImageManifest, mb) _, err = il.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, mb)
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, err = il.GetImageTags("test") _, err = il.PutImageManifest("test", "2.0", ispec.MediaTypeImageManifest, mb)
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, err = il.PutImageManifest("test", "3.0", ispec.MediaTypeImageManifest, mb)
So(err, ShouldBeNil)
// total tags should be 3 but they have same reference.
tags, err := il.GetImageTags("test")
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 3)
_, _, _, err = il.GetImageManifest("test", d.String()) _, _, _, err = il.GetImageManifest("test", d.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = il.DeleteImageManifest("test", "1.0") err = il.DeleteImageManifest("test", "1.0")
So(err, ShouldNotBeNil)
err = il.DeleteBlob("test", blobDigest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
tags, err = il.GetImageTags("test")
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 2)
// We deleted only one tag, make sure blob should not be removed.
hasBlob, _, err := il.CheckBlob("test", d.String())
So(err, ShouldBeNil)
So(hasBlob, ShouldEqual, true)
// If we pass reference all manifest with input reference should be deleted.
err = il.DeleteImageManifest("test", d.String()) err = il.DeleteImageManifest("test", d.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
tags, err = il.GetImageTags("test")
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
// All tags/references are deleted, blob should not be present in disk.
hasBlob, _, err = il.CheckBlob("test", d.String())
So(err, ShouldNotBeNil)
So(hasBlob, ShouldEqual, false)
err = il.DeleteBlob("test", blobDigest.String())
So(err, ShouldBeNil)
_, _, _, err = il.GetImageManifest("test", d.String()) _, _, _, err = il.GetImageManifest("test", d.String())
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })