mirror of
https://github.com/project-zot/zot.git
synced 2026-06-15 11:37:56 +08:00
feat: support pushing multiple tags for a single manifest (#3885)
* feat: support pushing multiple tags for a single manifest See https://github.com/opencontainers/distribution-spec/pull/600 Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> * fix: constants not replaced in swagger output Also godot mandates comments ending in dots, which produces bad results in the swagger generated files, see the extra ". which is now fixed below: ``` diff --git a/swagger/docs.go b/swagger/docs.go index 84b08277..fb2c45c3 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -114,7 +114,7 @@ const docTemplate = `{ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } @@ -200,7 +200,7 @@ const docTemplate = `{ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } diff --git a/swagger/swagger.json b/swagger/swagger.json index cfeb3900..247f95fa 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -106,7 +106,7 @@ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } @@ -192,7 +192,7 @@ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 57641c2f..09b30dcc 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -310,7 +310,7 @@ paths: schema: type: string "400": - description: bad request". + description: bad request schema: type: string "500": @@ -366,7 +366,7 @@ paths: schema: type: string "400": - description: bad request". + description: bad request schema: type: string "500": ``` Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> --------- Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
This commit is contained in:
@@ -92,6 +92,15 @@ linters:
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- godot
|
||||
path: pkg/api/routes.go
|
||||
- linters:
|
||||
- godot
|
||||
path: pkg/extensions/extension_image_trust.go
|
||||
- linters:
|
||||
- godot
|
||||
path: pkg/extensions/extension_mgmt.go
|
||||
- linters:
|
||||
- lll
|
||||
- varnamelen
|
||||
|
||||
@@ -117,6 +117,8 @@ var (
|
||||
ErrEmptyRepoName = errors.New("repo name can't be empty string")
|
||||
ErrEmptyTag = errors.New("tag can't be empty string")
|
||||
ErrEmptyDigest = errors.New("digest can't be empty string")
|
||||
ErrEmptyManifestTagQuery = errors.New("empty tag query parameter")
|
||||
ErrInvalidManifestTagQuery = errors.New("invalid tag query parameter: not a valid OCI/Docker tag")
|
||||
ErrInvalidRepoRefFormat = errors.New("invalid image reference format, use [repo:tag] or [repo@digest]")
|
||||
ErrLimitIsNegative = errors.New("pagination limit has negative value")
|
||||
ErrLimitIsExcessive = errors.New("pagination limit has excessive value")
|
||||
|
||||
@@ -3,12 +3,23 @@ package constants
|
||||
import "time"
|
||||
|
||||
const (
|
||||
RoutePrefix = "/v2"
|
||||
Blobs = "blobs"
|
||||
Uploads = "uploads"
|
||||
DistAPIVersion = "Docker-Distribution-API-Version"
|
||||
DistContentDigestKey = "Docker-Content-Digest"
|
||||
SubjectDigestKey = "OCI-Subject"
|
||||
RoutePrefix = "/v2"
|
||||
Blobs = "blobs"
|
||||
Uploads = "uploads"
|
||||
DistAPIVersion = "Docker-Distribution-API-Version"
|
||||
DistContentDigestKey = "Docker-Content-Digest"
|
||||
// OCITagResponseKey is returned on digest manifest pushes that include tag query
|
||||
// parameters (distribution-spec PR #600).
|
||||
OCITagResponseKey = "OCI-Tag"
|
||||
SubjectDigestKey = "OCI-Subject"
|
||||
// MaxManifestDigestQueryTags is the maximum number of raw `tag=` query parameters accepted on
|
||||
// PUT .../manifests/<digest>?tag=... (draft OCI distribution-spec: registries MUST support at
|
||||
// least 10 and MAY respond with 414 beyond this limit). It uses the OCI tag max length (128;
|
||||
// must match pkg/regexp.TagMaxLen) and an ~8KiB request-target budget, reserving 2048 bytes
|
||||
// for path and digest:
|
||||
//
|
||||
// (8192 - 2048) / (len("tag=") + 128 + 1) == 46
|
||||
MaxManifestDigestQueryTags = (8192 - 2048) / (len("tag=") + 128 + 1)
|
||||
BlobUploadUUID = "Blob-Upload-UUID"
|
||||
DefaultMediaType = "application/json"
|
||||
BinaryMediaType = "application/octet-stream"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package constants_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"zotregistry.dev/zot/v2/pkg/api/constants"
|
||||
zreg "zotregistry.dev/zot/v2/pkg/regexp"
|
||||
)
|
||||
|
||||
func TestMaxManifestDigestQueryTagsDerived(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := (8192 - 2048) / (len("tag=") + zreg.TagMaxLen + 1)
|
||||
|
||||
if constants.MaxManifestDigestQueryTags != want {
|
||||
t.Fatalf("MaxManifestDigestQueryTags = %d, want %d", constants.MaxManifestDigestQueryTags, want)
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import (
|
||||
extconf "zotregistry.dev/zot/v2/pkg/extensions/config"
|
||||
"zotregistry.dev/zot/v2/pkg/log"
|
||||
"zotregistry.dev/zot/v2/pkg/meta"
|
||||
zreg "zotregistry.dev/zot/v2/pkg/regexp"
|
||||
"zotregistry.dev/zot/v2/pkg/storage"
|
||||
storageConstants "zotregistry.dev/zot/v2/pkg/storage/constants"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/gc"
|
||||
@@ -7808,6 +7809,142 @@ func TestManifestValidation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestManifestDigestQueryTags(t *testing.T) {
|
||||
Convey("Manifest PUT with digest ?tag= query parameters", t, func() {
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
dir := t.TempDir()
|
||||
ctlr := makeController(conf, dir)
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartServer()
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
defer cm.StopServer()
|
||||
|
||||
repoName := "digest-query-tags"
|
||||
img := CreateRandomImage()
|
||||
manifestBytes := img.ManifestDescriptor.Data
|
||||
manifestDigest := img.ManifestDescriptor.Digest
|
||||
|
||||
err := UploadImage(img, baseURL, repoName, "initial")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
putManifestByDigest := func(rawQuery string) *resty.Response {
|
||||
t.Helper()
|
||||
|
||||
manifestPutURL, perr := url.Parse(baseURL + fmt.Sprintf("/v2/%s/manifests/%s", repoName, manifestDigest.String()))
|
||||
So(perr, ShouldBeNil)
|
||||
manifestPutURL.RawQuery = rawQuery
|
||||
|
||||
resp, rerr := resty.R().
|
||||
SetHeader("Content-Type", ispec.MediaTypeImageManifest).
|
||||
SetBody(manifestBytes).
|
||||
Put(manifestPutURL.String())
|
||||
So(rerr, ShouldBeNil)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
Convey("multiple tag query parameters add tags and return OCI-Tag headers", func() {
|
||||
manifestPutURL, err := url.Parse(baseURL + fmt.Sprintf("/v2/%s/manifests/%s", repoName, manifestDigest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q := manifestPutURL.Query()
|
||||
q.Add("tag", "v1.0.0")
|
||||
q.Add("tag", "v1.0")
|
||||
q.Add("tag", "edge")
|
||||
manifestPutURL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := resty.R().
|
||||
SetHeader("Content-Type", ispec.MediaTypeImageManifest).
|
||||
SetBody(manifestBytes).
|
||||
Put(manifestPutURL.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
So(resp.Header().Get(constants.DistContentDigestKey), ShouldEqual, manifestDigest.String())
|
||||
|
||||
ociTags := resp.Header().Values(constants.OCITagResponseKey)
|
||||
sort.Strings(ociTags)
|
||||
So(ociTags, ShouldResemble, []string{"edge", "v1.0", "v1.0.0"})
|
||||
|
||||
for _, tag := range []string{"v1.0.0", "v1.0", "edge"} {
|
||||
gresp, gerr := resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/manifests/%s", repoName, tag))
|
||||
So(gerr, ShouldBeNil)
|
||||
So(gresp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("tag query with non-digest path reference returns 400", func() {
|
||||
manifestPutURL, err := url.Parse(baseURL + fmt.Sprintf("/v2/%s/manifests/initial", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
manifestPutURL.RawQuery = "tag=notallowed"
|
||||
|
||||
resp, err := resty.R().
|
||||
SetHeader("Content-Type", ispec.MediaTypeImageManifest).
|
||||
SetBody(manifestBytes).
|
||||
Put(manifestPutURL.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
Convey("empty tag query parameter returns 400", func() {
|
||||
resp := putManifestByDigest("tag=")
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
Convey("more than max tag query parameters returns 414", func() {
|
||||
q := url.Values{}
|
||||
for i := range constants.MaxManifestDigestQueryTags + 1 {
|
||||
q.Add("tag", fmt.Sprintf("t%d", i))
|
||||
}
|
||||
|
||||
resp := putManifestByDigest(q.Encode())
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusRequestURITooLong)
|
||||
})
|
||||
|
||||
Convey("more than max raw tag parameters returns 414 even when values are duplicates", func() {
|
||||
q := url.Values{}
|
||||
for range constants.MaxManifestDigestQueryTags + 1 {
|
||||
q.Add("tag", "same")
|
||||
}
|
||||
|
||||
resp := putManifestByDigest(q.Encode())
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusRequestURITooLong)
|
||||
})
|
||||
|
||||
Convey("invalid tag query value returns 400", func() {
|
||||
q := url.Values{}
|
||||
q.Set("tag", "bad/ref")
|
||||
|
||||
resp := putManifestByDigest(q.Encode())
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
Convey("tag query value longer than distribution-spec max length returns 400", func() {
|
||||
longTag := strings.Repeat("a", zreg.TagMaxLen+1)
|
||||
q := url.Values{}
|
||||
q.Set("tag", longTag)
|
||||
|
||||
resp := putManifestByDigest(q.Encode())
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
Convey("duplicate tag query values are deduplicated in response headers", func() {
|
||||
q := url.Values{}
|
||||
q.Add("tag", "dup")
|
||||
q.Add("tag", "dup")
|
||||
|
||||
resp := putManifestByDigest(q.Encode())
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
So(resp.Header().Values(constants.OCITagResponseKey), ShouldResemble, []string{"dup"})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestArtifactReferences(t *testing.T) {
|
||||
Convey("Validate Artifact References", t, func() {
|
||||
// start a new server
|
||||
|
||||
+116
-30
@@ -266,7 +266,7 @@ func getUIHeadersHandler(config *config.Config, allowedMethods ...string) func(h
|
||||
// @Router /v2/ [get]
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "ok".
|
||||
// @Success 200 {string} string "ok"
|
||||
func (rh *RouteHandler) CheckVersionSupport(response http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodOptions {
|
||||
return
|
||||
@@ -304,7 +304,7 @@ func (rh *RouteHandler) CheckVersionSupport(response http.ResponseWriter, reques
|
||||
// @Param last query string true "last tag value for pagination"
|
||||
// @Success 200 {object} common.ImageTags
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 400 {string} string "bad request".
|
||||
// @Failure 400 {string} string "bad request"
|
||||
func (rh *RouteHandler) ListTags(response http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodOptions {
|
||||
return
|
||||
@@ -436,9 +436,9 @@ func (rh *RouteHandler) ListTags(response http.ResponseWriter, request *http.Req
|
||||
// @Param name path string true "repository name"
|
||||
// @Param reference path string true "image reference or digest"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Header 200 {object} constants.DistContentDigestKey
|
||||
// @Header 200 {string} Docker-Content-Digest "Manifest digest of the content"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
func (rh *RouteHandler) CheckManifest(response http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodOptions {
|
||||
return
|
||||
@@ -509,7 +509,7 @@ type ExtensionList struct {
|
||||
// @Param name path string true "repository name"
|
||||
// @Param reference path string true "image reference or digest"
|
||||
// @Success 200 {object} api.ImageManifest
|
||||
// @Header 200 {object} constants.DistContentDigestKey
|
||||
// @Header 200 {string} Docker-Content-Digest "Manifest digest of the content"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/manifests/{reference} [get].
|
||||
@@ -676,15 +676,19 @@ func (rh *RouteHandler) GetReferrers(response http.ResponseWriter, request *http
|
||||
|
||||
// UpdateManifest godoc
|
||||
// @Summary Update image manifest
|
||||
// @Description Update an image's manifest given a reference or a digest
|
||||
// @Description Update an image's manifest given a reference or a digest. On digest pushes with `tag=` query
|
||||
// @Description parameters, 201 responses repeat the `OCI-Tag` header once per tag value.
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param reference path string true "image reference or digest"
|
||||
// @Header 201 {object} constants.DistContentDigestKey
|
||||
// @Success 201 {string} string "created"
|
||||
// @Param tag query []string false "additional tag(s) for digest pushes" collectionFormat(multi)
|
||||
// @Success 201 "created"
|
||||
// @Header 201 {string} Docker-Content-Digest "Manifest digest of the uploaded content"
|
||||
// @Header 201 {string} OCI-Tag "Echoed tag= value; this header is repeatable (one field per tag= query parameter)"
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 414 {string} string "too many tag query parameters"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/manifests/{reference} [put].
|
||||
func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *http.Request) {
|
||||
@@ -717,6 +721,30 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
|
||||
return
|
||||
}
|
||||
|
||||
var digestQueryTags []string
|
||||
|
||||
rawTagQuery := request.URL.Query()["tag"]
|
||||
if len(rawTagQuery) > 0 {
|
||||
if len(rawTagQuery) > constants.MaxManifestDigestQueryTags {
|
||||
e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{
|
||||
"reason": fmt.Sprintf("too many tag query parameters (max %d)", constants.MaxManifestDigestQueryTags),
|
||||
})
|
||||
zcommon.WriteJSON(response, http.StatusRequestURITooLong, apiErr.NewErrorList(e))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var normErr error
|
||||
|
||||
digestQueryTags, normErr = normalizeManifestExtraTags(rawTagQuery)
|
||||
if normErr != nil {
|
||||
err := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"reason": normErr.Error()})
|
||||
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(err))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(request.Body)
|
||||
// hard to reach test case, injected error (simulates an interrupted image manifest upload)
|
||||
// err could be io.ErrUnexpectedEOF
|
||||
@@ -727,7 +755,16 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
|
||||
return
|
||||
}
|
||||
|
||||
digest, subjectDigest, err := imgStore.PutImageManifest(name, reference, mediaType, body)
|
||||
if len(digestQueryTags) > 0 && !zcommon.IsDigest(reference) {
|
||||
err := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{
|
||||
"reason": "tag query parameters are only valid when pushing a manifest by digest",
|
||||
})
|
||||
zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
digest, subjectDigest, err := imgStore.PutImageManifest(name, reference, mediaType, body, digestQueryTags)
|
||||
if err != nil {
|
||||
details := zerr.GetDetails(err)
|
||||
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
||||
@@ -769,12 +806,22 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
|
||||
}
|
||||
|
||||
if rh.c.MetaDB != nil {
|
||||
err := meta.OnUpdateManifest(request.Context(), name, reference, mediaType,
|
||||
digest, body, rh.c.StoreController, rh.c.MetaDB, rh.c.Log)
|
||||
if err != nil {
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
if len(digestQueryTags) > 0 {
|
||||
err := meta.OnUpdateManifestDigestTags(request.Context(), name, digestQueryTags, mediaType,
|
||||
digest, body, rh.c.StoreController, rh.c.MetaDB, rh.c.Log)
|
||||
if err != nil {
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := meta.OnUpdateManifest(request.Context(), name, reference, mediaType,
|
||||
digest, body, rh.c.StoreController, rh.c.MetaDB, rh.c.Log)
|
||||
if err != nil {
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,9 +831,42 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
|
||||
|
||||
response.Header().Set("Location", fmt.Sprintf("/v2/%s/manifests/%s", name, digest))
|
||||
response.Header().Set(constants.DistContentDigestKey, digest.String())
|
||||
|
||||
for _, tag := range digestQueryTags {
|
||||
response.Header().Add(constants.OCITagResponseKey, tag) //nolint:canonicalheader
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
// normalizeManifestExtraTags deduplicates tag query values in order, rejects empty components, and
|
||||
// requires each value to match the OCI distribution-spec tag grammar (zreg.IsDistributionSpecTag).
|
||||
func normalizeManifestExtraTags(raw []string) ([]string, error) {
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
out := make([]string, 0, len(raw))
|
||||
|
||||
for _, rawTag := range raw {
|
||||
cleanedTag := strings.TrimSpace(rawTag)
|
||||
if cleanedTag == "" {
|
||||
return nil, zerr.ErrEmptyManifestTagQuery
|
||||
}
|
||||
|
||||
if !zreg.IsDistributionSpecTag(cleanedTag) {
|
||||
return nil, zerr.ErrInvalidManifestTagQuery
|
||||
}
|
||||
|
||||
if _, ok := seen[cleanedTag]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[cleanedTag] = struct{}{}
|
||||
out = append(out, cleanedTag)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeleteManifest godoc
|
||||
// @Summary Delete image manifest
|
||||
// @Description Delete an image's manifest given a reference or a digest
|
||||
@@ -794,7 +874,12 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param reference path string true "image reference or digest"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Success 202 "accepted"
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 405 {string} string "method not allowed"
|
||||
// @Failure 409 {string} string "conflict"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/manifests/{reference} [delete].
|
||||
func (rh *RouteHandler) DeleteManifest(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
@@ -933,7 +1018,7 @@ func canMount(userAc *reqCtx.UserAccessControl, imgStore storageTypes.ImageStore
|
||||
// @Param name path string true "repository name"
|
||||
// @Param digest path string true "blob/layer digest"
|
||||
// @Success 200 {object} api.ImageManifest
|
||||
// @Header 200 {object} constants.DistContentDigestKey
|
||||
// @Header 200 {string} Docker-Content-Digest "Manifest digest of the content"
|
||||
// @Router /v2/{name}/blobs/{digest} [head].
|
||||
func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
@@ -1076,7 +1161,7 @@ func parseRangeHeader(contentRange string) (int64, int64, error) {
|
||||
// @Produce application/vnd.oci.image.layer.v1.tar+gzip
|
||||
// @Param name path string true "repository name"
|
||||
// @Param digest path string true "blob/layer digest"
|
||||
// @Header 200 {object} constants.DistContentDigestKey
|
||||
// @Header 200 {string} Docker-Content-Digest "Manifest digest of the content"
|
||||
// @Success 200 {object} api.ImageManifest
|
||||
// @Router /v2/{name}/blobs/{digest} [get].
|
||||
func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Request) {
|
||||
@@ -1187,7 +1272,7 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param digest path string true "blob/layer digest"
|
||||
// @Success 202 {string} string "accepted"
|
||||
// @Success 202 "accepted"
|
||||
// @Router /v2/{name}/blobs/{digest} [delete].
|
||||
func (rh *RouteHandler) DeleteBlob(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
@@ -1246,7 +1331,7 @@ func (rh *RouteHandler) DeleteBlob(response http.ResponseWriter, request *http.R
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Success 202 {string} string "accepted"
|
||||
// @Success 202 "accepted"
|
||||
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
|
||||
// @Header 202 {string} Range "0-0"
|
||||
// @Failure 401 {string} string "unauthorized"
|
||||
@@ -1424,9 +1509,10 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param session_id path string true "upload session_id"
|
||||
// @Success 204 {string} string "no content"
|
||||
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
|
||||
// @Header 202 {string} Range "0-128"
|
||||
// @Success 204 "no content"
|
||||
// @Header 204 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
|
||||
// @Header 204 {string} Range "0-128"
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/blobs/uploads/{session_id} [get].
|
||||
@@ -1485,10 +1571,10 @@ func (rh *RouteHandler) GetBlobUpload(response http.ResponseWriter, request *htt
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param session_id path string true "upload session_id"
|
||||
// @Success 202 {string} string "accepted"
|
||||
// @Success 202 "accepted"
|
||||
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
|
||||
// @Header 202 {string} Range "0-128"
|
||||
// @Header 200 {object} api.BlobUploadUUID
|
||||
// @Header 202 {string} Blob-Upload-UUID "Opaque blob upload session identifier"
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 416 {string} string "range not satisfiable"
|
||||
@@ -1585,9 +1671,9 @@ func (rh *RouteHandler) PatchBlobUpload(response http.ResponseWriter, request *h
|
||||
// @Param name path string true "repository name"
|
||||
// @Param session_id path string true "upload session_id"
|
||||
// @Param digest query string true "blob/layer digest"
|
||||
// @Success 201 {string} string "created"
|
||||
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{digest}"
|
||||
// @Header 200 {object} constants.DistContentDigestKey
|
||||
// @Success 201 "created"
|
||||
// @Header 201 {string} Location "/v2/{name}/blobs/{digest}"
|
||||
// @Header 201 {string} Docker-Content-Digest "Digest of the committed blob"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/blobs/uploads/{session_id} [put].
|
||||
@@ -1742,7 +1828,7 @@ finish:
|
||||
// @Produce json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param session_id path string true "upload session_id"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Success 204 "no content"
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/blobs/uploads/{session_id} [delete].
|
||||
@@ -1944,8 +2030,8 @@ func (rh *RouteHandler) ListExtensions(w http.ResponseWriter, r *http.Request) {
|
||||
// @Router /zot/auth/logout [post]
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "ok".
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
func (rh *RouteHandler) Logout(response http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodOptions {
|
||||
return
|
||||
|
||||
+101
-5
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/project-zot/mockoidc"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
@@ -265,7 +266,7 @@ func TestRoutes(t *testing.T) {
|
||||
"reference": "reference",
|
||||
},
|
||||
&mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", zerr.ErrRepoNotFound
|
||||
@@ -280,7 +281,7 @@ func TestRoutes(t *testing.T) {
|
||||
},
|
||||
|
||||
&mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", zerr.ErrManifestNotFound
|
||||
@@ -294,7 +295,7 @@ func TestRoutes(t *testing.T) {
|
||||
"reference": "reference",
|
||||
},
|
||||
&mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", zerr.ErrBadManifest
|
||||
@@ -308,7 +309,7 @@ func TestRoutes(t *testing.T) {
|
||||
"reference": "reference",
|
||||
},
|
||||
&mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", zerr.ErrBlobNotFound
|
||||
@@ -323,7 +324,7 @@ func TestRoutes(t *testing.T) {
|
||||
"reference": "reference",
|
||||
},
|
||||
&mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", zerr.ErrRepoBadVersion
|
||||
@@ -332,6 +333,101 @@ func TestRoutes(t *testing.T) {
|
||||
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
Convey("UpdateManifest digest query tags with MetaDB", func() {
|
||||
defer func() {
|
||||
ctlr.MetaDB = nil
|
||||
}()
|
||||
|
||||
configBlob := []byte(`{"architecture":"amd64","os":"linux"}`)
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
manifest := ispec.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Digest: configDigest,
|
||||
Size: int64(len(configBlob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{},
|
||||
}
|
||||
|
||||
mcontent, mErr := json.Marshal(manifest)
|
||||
So(mErr, ShouldBeNil)
|
||||
|
||||
manifestDigest := godigest.FromBytes(mcontent)
|
||||
digestRef := manifestDigest.String()
|
||||
|
||||
ism := &mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, extraTags []string) (
|
||||
godigest.Digest, godigest.Digest, error,
|
||||
) {
|
||||
So(extraTags, ShouldResemble, []string{"meta-a", "meta-b"})
|
||||
So(string(body), ShouldEqual, string(mcontent))
|
||||
|
||||
return manifestDigest, godigest.Digest(""), nil
|
||||
},
|
||||
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
if digest == configDigest {
|
||||
return configBlob, nil
|
||||
}
|
||||
|
||||
return nil, zerr.ErrBlobNotFound
|
||||
},
|
||||
}
|
||||
ctlr.StoreController.DefaultStore = ism
|
||||
|
||||
runDigestMultiTag := func(metaDB mTypes.MetaDB) *httptest.ResponseRecorder {
|
||||
ctlr.MetaDB = metaDB
|
||||
|
||||
reqURL := baseURL + "?tag=meta-a&tag=meta-b"
|
||||
request, reqErr := http.NewRequestWithContext(context.Background(), http.MethodPut, reqURL,
|
||||
bytes.NewBuffer(mcontent))
|
||||
So(reqErr, ShouldBeNil)
|
||||
|
||||
request = mux.SetURLVars(request, map[string]string{
|
||||
"name": "test",
|
||||
"reference": digestRef,
|
||||
})
|
||||
request.Header.Add("Content-Type", ispec.MediaTypeImageManifest)
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
rthdlr.UpdateManifest(response, request)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
Convey("SetRepoReference succeeds", func() {
|
||||
rec := runDigestMultiTag(mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
So(rec.Code, ShouldEqual, http.StatusCreated)
|
||||
So(rec.Header().Values(constants.OCITagResponseKey), ShouldResemble, []string{"meta-a", "meta-b"})
|
||||
So(rec.Header().Get(constants.DistContentDigestKey), ShouldEqual, manifestDigest.String())
|
||||
})
|
||||
|
||||
Convey("SetRepoReference fails for a later tag returns 500", func() {
|
||||
var calls int
|
||||
|
||||
rec := runDigestMultiTag(mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error {
|
||||
calls++
|
||||
|
||||
if reference == "meta-b" {
|
||||
return ErrUnexpectedError
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
So(calls, ShouldEqual, 2)
|
||||
So(rec.Code, ShouldEqual, http.StatusInternalServerError)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("DeleteManifest", func() {
|
||||
testDeleteManifest := func(headers map[string]string, urlVars map[string]string, ism *mocks.MockedImageStore) int {
|
||||
ctlr.StoreController.DefaultStore = ism
|
||||
|
||||
@@ -81,8 +81,8 @@ type ImageTrust struct {
|
||||
// @Produce json
|
||||
// @Param requestBody body string true "Public key content"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Failure 400 {string} string "bad request".
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWriter, request *http.Request) {
|
||||
body, err := io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
@@ -116,8 +116,8 @@ func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWrite
|
||||
// @Param truststoreType query string false "truststore type"
|
||||
// @Param requestBody body string true "Certificate content"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Failure 400 {string} string "bad request".
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
// @Failure 400 {string} string "bad request"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseWriter, request *http.Request) {
|
||||
var truststoreType string
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ type Mgmt struct {
|
||||
// @Produce json
|
||||
// @Param resource query string false "specify resource" Enums(config)
|
||||
// @Success 200 {object} extensions.StrippedConfig
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
func (mgmt *Mgmt) HandleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
sanitizedConfig := mgmt.Conf.Sanitize()
|
||||
|
||||
|
||||
@@ -4271,7 +4271,7 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexMultiArchMiddle1Digest, _, err := storeCtlr.GetDefaultImageStore().PutImageManifest(repoName,
|
||||
"multiArchMiddle1", ispec.MediaTypeImageIndex, indexMultiArchMiddle1Blob)
|
||||
"multiArchMiddle1", ispec.MediaTypeImageIndex, indexMultiArchMiddle1Blob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image211 := CreateRandomImage()
|
||||
@@ -4296,7 +4296,7 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexMultiArchMiddle2Digest, _, err := storeCtlr.GetDefaultImageStore().PutImageManifest(repoName,
|
||||
"multiArchMiddle2", ispec.MediaTypeImageIndex, indexMultiArchMiddle2Blob)
|
||||
"multiArchMiddle2", ispec.MediaTypeImageIndex, indexMultiArchMiddle2Blob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image31 := CreateRandomImage()
|
||||
@@ -4331,7 +4331,7 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = storeCtlr.GetDefaultImageStore().PutImageManifest(repoName, "multiArchTop", ispec.MediaTypeImageIndex,
|
||||
indexMultiArchTopBlob)
|
||||
indexMultiArchTopBlob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ctlrManager.StartAndWait(port)
|
||||
@@ -4443,7 +4443,7 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexMultiArchMiddle1Digest, _, err := storeCtlr.GetDefaultImageStore().PutImageManifest(repoName,
|
||||
"multiArchMiddle1", ispec.MediaTypeImageIndex, indexMultiArchMiddle1Blob)
|
||||
"multiArchMiddle1", ispec.MediaTypeImageIndex, indexMultiArchMiddle1Blob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image211 := CreateRandomImage()
|
||||
@@ -4468,7 +4468,7 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexMultiArchMiddle2Digest, _, err := storeCtlr.GetDefaultImageStore().PutImageManifest(repoName,
|
||||
"multiArchMiddle2", ispec.MediaTypeImageIndex, indexMultiArchMiddle2Blob)
|
||||
"multiArchMiddle2", ispec.MediaTypeImageIndex, indexMultiArchMiddle2Blob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image31 := CreateRandomImage()
|
||||
@@ -4503,7 +4503,7 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = storeCtlr.GetDefaultImageStore().PutImageManifest(repoName, "multiArchTop", ispec.MediaTypeImageIndex,
|
||||
indexMultiArchTopBlob)
|
||||
indexMultiArchTopBlob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
@@ -5229,7 +5229,7 @@ func TestMetaDBWhenSigningImages(t *testing.T) {
|
||||
Convey("imageIsSignature fails", func() {
|
||||
// make image store ignore the wrong format of the input
|
||||
ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", nil
|
||||
@@ -6626,7 +6626,7 @@ func TestMetaDBWhenDeletingImages(t *testing.T) {
|
||||
|
||||
Convey("imageIsSignature fails", func() {
|
||||
ctlr.StoreController.DefaultStore = mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", nil
|
||||
@@ -6652,7 +6652,7 @@ func TestMetaDBWhenDeletingImages(t *testing.T) {
|
||||
|
||||
return configBlob, nil
|
||||
},
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", nil
|
||||
@@ -6682,7 +6682,7 @@ func TestMetaDBWhenDeletingImages(t *testing.T) {
|
||||
|
||||
return configBlob, nil
|
||||
},
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
|
||||
godigest.Digest, error,
|
||||
) {
|
||||
return "", "", ErrTestError
|
||||
|
||||
@@ -218,7 +218,7 @@ func (registry *DestinationRegistry) copyManifest(repo string, desc ispec.Descri
|
||||
}
|
||||
|
||||
digest, _, err := imageStore.PutImageManifest(repo, reference,
|
||||
desc.MediaType, manifestContent)
|
||||
desc.MediaType, manifestContent, nil)
|
||||
if err != nil {
|
||||
registry.log.Error().Str("errorType", common.TypeOf(err)).
|
||||
Err(err).Msg("couldn't upload manifest")
|
||||
@@ -299,7 +299,7 @@ func (registry *DestinationRegistry) copyManifest(repo string, desc ispec.Descri
|
||||
return firstMissingErr
|
||||
}
|
||||
|
||||
_, _, err := imageStore.PutImageManifest(repo, reference, desc.MediaType, manifestContent)
|
||||
_, _, err := imageStore.PutImageManifest(repo, reference, desc.MediaType, manifestContent, nil)
|
||||
if err != nil {
|
||||
registry.log.Error().Str("errorType", common.TypeOf(err)).Str("repo", repo).Str("reference", reference).
|
||||
Err(err).Msg("failed to upload manifest")
|
||||
|
||||
@@ -864,7 +864,7 @@ func TestDestinationRegistry(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -880,7 +880,7 @@ func TestDestinationRegistry(t *testing.T) {
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("sync index image", func() {
|
||||
@@ -1067,7 +1067,7 @@ func TestDestinationRegistry(t *testing.T) {
|
||||
So(manifestDigest, ShouldNotBeNil)
|
||||
|
||||
// Store the manifest in the temp image store
|
||||
_, _, err = tempImgStore.PutImageManifest(repoName, manifestDigest.String(), ispec.MediaTypeImageManifest, manifestContent)
|
||||
_, _, err = tempImgStore.PutImageManifest(repoName, manifestDigest.String(), ispec.MediaTypeImageManifest, manifestContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Add to index
|
||||
@@ -1085,7 +1085,7 @@ func TestDestinationRegistry(t *testing.T) {
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
// Store the index manifest in the temp image store
|
||||
_, _, err = tempImgStore.PutImageManifest(repoName, indexDigest.String(), ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = tempImgStore.PutImageManifest(repoName, indexDigest.String(), ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Now remove one of the child manifest blobs to trigger the error
|
||||
@@ -1178,7 +1178,7 @@ func TestDestinationRegistry(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("sync image", func() {
|
||||
|
||||
+166
-1
@@ -2,10 +2,12 @@ package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
zerr "zotregistry.dev/zot/v2/errors"
|
||||
zcommon "zotregistry.dev/zot/v2/pkg/common"
|
||||
"zotregistry.dev/zot/v2/pkg/compat"
|
||||
"zotregistry.dev/zot/v2/pkg/log"
|
||||
@@ -13,6 +15,132 @@ import (
|
||||
"zotregistry.dev/zot/v2/pkg/storage"
|
||||
)
|
||||
|
||||
// priorTagManifest records where MetaDB believed each tag pointed before a digest PUT with tag=
|
||||
// parameters could move it. Rollback loads manifest bytes from the blob store (GetBlobContent).
|
||||
type priorTagManifest struct {
|
||||
digest godigest.Digest
|
||||
mediaType string
|
||||
}
|
||||
|
||||
// priorTagManifestsFromMetaDB returns digest and media type from RepoMeta for tags that already
|
||||
// exist in metadb. Omitted tags are new or unknown to metadb. ErrRepoMetaNotFound yields an empty
|
||||
// map. Rollback reads manifest blobs from storage via GetBlobContent(prior.digest).
|
||||
// If a tag exists only in the image store and not in metadb, rollback cannot restore a moved tag
|
||||
// (metadb and storage should stay in sync during normal operation).
|
||||
func priorTagManifestsFromMetaDB(ctx context.Context, metaDB mTypes.MetaDB, repo string, tags []string,
|
||||
) (map[string]priorTagManifest, error) {
|
||||
empty := map[string]priorTagManifest{}
|
||||
|
||||
if len(tags) == 0 {
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(repoMeta.Tags) == 0 {
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
out := make(map[string]priorTagManifest, len(tags))
|
||||
|
||||
for _, tag := range tags {
|
||||
desc, ok := repoMeta.Tags[tag]
|
||||
if !ok || desc.Digest == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
dgst, parseErr := godigest.Parse(desc.Digest)
|
||||
if parseErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
descMediaType := desc.MediaType
|
||||
if descMediaType == "" {
|
||||
descMediaType = v1.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
out[tag] = priorTagManifest{
|
||||
digest: dgst,
|
||||
mediaType: descMediaType,
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// rollbackDigestManifestTags deletes every tag in tags from the image store (this PUT added them to the
|
||||
// index). It runs OnDeleteManifest only for tags in appliedMetaTags: those had a successful meta update for
|
||||
// digest and must be reverted. Calling OnDeleteManifest for other tags is unsafe—RemoveRepoReference can
|
||||
// drop a tag entry even when metadb still maps that tag to a different digest (e.g. meta not updated yet).
|
||||
// When priorTagManifests has an entry for a tag, it re-applies that manifest so moved tags point at their
|
||||
// original digest again.
|
||||
func rollbackDigestManifestTags(ctx context.Context, repo string, tags, appliedMetaTags []string, mediaType string,
|
||||
digest godigest.Digest,
|
||||
body []byte, storeController storage.StoreController, metaDB mTypes.MetaDB, log log.Logger,
|
||||
priorTagManifests map[string]priorTagManifest,
|
||||
) {
|
||||
imgStore := storeController.GetImageStore(repo)
|
||||
|
||||
for i := len(tags) - 1; i >= 0; i-- {
|
||||
refTag := tags[i]
|
||||
if delErr := imgStore.DeleteImageManifest(repo, refTag, false); delErr != nil &&
|
||||
!errors.Is(delErr, zerr.ErrManifestNotFound) {
|
||||
log.Error().Err(delErr).Str("repository", repo).Str("tag", refTag).
|
||||
Msg("multi-tag digest push: rollback DeleteImageManifest failed")
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(appliedMetaTags) - 1; i >= 0; i-- {
|
||||
refTag := appliedMetaTags[i]
|
||||
|
||||
metaDelErr := OnDeleteManifest(repo, refTag, mediaType, digest, body, storeController, metaDB, log)
|
||||
if metaDelErr != nil {
|
||||
log.Error().Err(metaDelErr).Str("repository", repo).Str("tag", refTag).
|
||||
Msg("multi-tag digest push: rollback OnDeleteManifest failed")
|
||||
}
|
||||
}
|
||||
|
||||
if len(priorTagManifests) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, refTag := range tags {
|
||||
prior, ok := priorTagManifests[refTag]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
restoreBody, blobErr := imgStore.GetBlobContent(repo, prior.digest)
|
||||
if blobErr != nil {
|
||||
log.Error().Err(blobErr).Str("repository", repo).Str("tag", refTag).
|
||||
Msg("multi-tag digest push: rollback load prior manifest blob failed")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if _, _, putErr := imgStore.PutImageManifest(repo, prior.digest.String(), prior.mediaType, restoreBody,
|
||||
[]string{refTag}); putErr != nil {
|
||||
log.Error().Err(putErr).Str("repository", repo).Str("tag", refTag).
|
||||
Msg("multi-tag digest push: rollback restore prior manifest in store failed")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if metaErr := OnUpdateManifest(ctx, repo, refTag, prior.mediaType, prior.digest, restoreBody,
|
||||
storeController, metaDB, log); metaErr != nil {
|
||||
log.Error().Err(metaErr).Str("repository", repo).Str("tag", refTag).
|
||||
Msg("multi-tag digest push: rollback restore prior metadb failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdateManifest is called when a new manifest is added. It updates metadb according to the type
|
||||
// of image pushed(normal images, signatues, etc.). In care of any errors, it makes sure to keep
|
||||
// consistency between metadb and the image store.
|
||||
@@ -43,6 +171,43 @@ func OnUpdateManifest(ctx context.Context, repo, reference, mediaType string, di
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnUpdateManifestDigestTags updates metadb for each tag from a digest-addressed manifest push that used
|
||||
// repeated `tag=` query parameters. It snapshots each tag's prior digest and media type from MetaDB
|
||||
// (GetRepoMeta) before updates, then calls OnUpdateManifest per tag; on the first failure it removes
|
||||
// every tag in tags from the image store, reverts MetaDB only for tags that had already completed
|
||||
// OnUpdateManifest successfully, and restores moved tags using the snapshot (see rollbackDigestManifestTags).
|
||||
func OnUpdateManifestDigestTags(ctx context.Context, repo string, tags []string, mediaType string,
|
||||
digest godigest.Digest, body []byte,
|
||||
storeController storage.StoreController, metaDB mTypes.MetaDB, log log.Logger,
|
||||
) error {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
priorTagManifests, err := priorTagManifestsFromMetaDB(ctx, metaDB, repo, tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
applied := make([]string, 0, len(tags))
|
||||
|
||||
for _, tag := range tags {
|
||||
if err := OnUpdateManifest(ctx, repo, tag, mediaType, digest, body, storeController, metaDB, log); err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
||||
Msg("multi-tag digest push: meta update failed; rolling back tag query additions")
|
||||
|
||||
rollbackDigestManifestTags(ctx, repo, tags, applied, mediaType, digest, body, storeController, metaDB, log,
|
||||
priorTagManifests)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
applied = append(applied, tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnDeleteManifest is called when a manifest is deleted. It updates metadb according to the type
|
||||
// of image pushed(normal images, signatues, etc.). In care of any errors, it makes sure to keep
|
||||
// consistency between metadb and the image store.
|
||||
@@ -82,7 +247,7 @@ func OnDeleteManifest(repo, reference, mediaType string, digest godigest.Digest,
|
||||
log.Info().Str("component", "metadb").Msg("restoring image store")
|
||||
|
||||
// restore image store
|
||||
_, _, err := imgStore.PutImageManifest(repo, reference, mediaType, manifestBlob)
|
||||
_, _, err := imgStore.PutImageManifest(repo, reference, mediaType, manifestBlob, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("component", "metadb").
|
||||
Msg("failed to restore manifest to image store, database is not consistent")
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.dev/zot/v2/errors"
|
||||
"zotregistry.dev/zot/v2/pkg/log"
|
||||
mTypes "zotregistry.dev/zot/v2/pkg/meta/types"
|
||||
"zotregistry.dev/zot/v2/pkg/storage"
|
||||
testimage "zotregistry.dev/zot/v2/pkg/test/image-utils"
|
||||
"zotregistry.dev/zot/v2/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var errHookInternal = errors.New("hook internal test error")
|
||||
|
||||
func TestPriorTagManifestsFromMetaDB(t *testing.T) {
|
||||
Convey("priorTagManifestsFromMetaDB", t, func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Convey("empty tags", func() {
|
||||
out, err := priorTagManifestsFromMetaDB(ctx, mocks.MetaDBMock{}, "repo", nil)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(out), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("repo meta not found yields empty map", func() {
|
||||
db := mocks.MetaDBMock{
|
||||
GetRepoMetaFn: func(context.Context, string) (mTypes.RepoMeta, error) {
|
||||
return mTypes.RepoMeta{}, zerr.ErrRepoMetaNotFound
|
||||
},
|
||||
}
|
||||
|
||||
out, err := priorTagManifestsFromMetaDB(ctx, db, "repo", []string{"t"})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(out), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("get repo meta error propagates", func() {
|
||||
db := mocks.MetaDBMock{
|
||||
GetRepoMetaFn: func(context.Context, string) (mTypes.RepoMeta, error) {
|
||||
return mTypes.RepoMeta{}, errHookInternal
|
||||
},
|
||||
}
|
||||
|
||||
_, err := priorTagManifestsFromMetaDB(ctx, db, "repo", []string{"t"})
|
||||
So(errors.Is(err, errHookInternal), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("empty tag map in repo meta", func() {
|
||||
db := mocks.MetaDBMock{
|
||||
GetRepoMetaFn: func(context.Context, string) (mTypes.RepoMeta, error) {
|
||||
return mTypes.RepoMeta{Tags: map[mTypes.Tag]mTypes.Descriptor{}}, nil
|
||||
},
|
||||
}
|
||||
|
||||
out, err := priorTagManifestsFromMetaDB(ctx, db, "repo", []string{"t"})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(out), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("skips unknown tag empty digest invalid digest", func() {
|
||||
good := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
db := mocks.MetaDBMock{
|
||||
GetRepoMetaFn: func(context.Context, string) (mTypes.RepoMeta, error) {
|
||||
return mTypes.RepoMeta{
|
||||
Tags: map[mTypes.Tag]mTypes.Descriptor{
|
||||
"only-good": {Digest: good, MediaType: ispec.MediaTypeImageManifest},
|
||||
"empty-dig": {Digest: "", MediaType: ispec.MediaTypeImageManifest},
|
||||
"bad-dig": {Digest: "not-a-digest", MediaType: ispec.MediaTypeImageManifest},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
tags := []string{"missing", "only-good", "empty-dig", "bad-dig"}
|
||||
out, err := priorTagManifestsFromMetaDB(ctx, db, "repo", tags)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(out), ShouldEqual, 1)
|
||||
|
||||
pm, ok := out["only-good"]
|
||||
So(ok, ShouldBeTrue)
|
||||
So(pm.digest.String(), ShouldEqual, good)
|
||||
So(pm.mediaType, ShouldEqual, ispec.MediaTypeImageManifest)
|
||||
})
|
||||
|
||||
Convey("default media type when descriptor empty", func() {
|
||||
good := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
db := mocks.MetaDBMock{
|
||||
GetRepoMetaFn: func(context.Context, string) (mTypes.RepoMeta, error) {
|
||||
return mTypes.RepoMeta{
|
||||
Tags: map[mTypes.Tag]mTypes.Descriptor{
|
||||
"t": {Digest: good, MediaType: ""},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
out, err := priorTagManifestsFromMetaDB(ctx, db, "repo", []string{"t"})
|
||||
So(err, ShouldBeNil)
|
||||
So(out["t"].mediaType, ShouldEqual, ispec.MediaTypeImageManifest)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRollbackDigestManifestTags(t *testing.T) {
|
||||
Convey("rollbackDigestManifestTags", t, func() {
|
||||
ctx := context.Background()
|
||||
testLog := log.NewTestLogger()
|
||||
|
||||
img := testimage.CreateDefaultImage()
|
||||
mediaType := img.ManifestDescriptor.MediaType
|
||||
if mediaType == "" {
|
||||
mediaType = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
body := img.ManifestDescriptor.Data
|
||||
dgst := img.Digest()
|
||||
|
||||
Convey("delete manifest error is logged path", func() {
|
||||
var deleteCalls int
|
||||
|
||||
is := mocks.MockedImageStore{
|
||||
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
|
||||
deleteCalls++
|
||||
|
||||
return errors.New("delete failed")
|
||||
},
|
||||
}
|
||||
|
||||
sc := storage.StoreController{DefaultStore: &is}
|
||||
rollbackDigestManifestTags(ctx, "repo", []string{"a"}, nil, mediaType, dgst, body, sc,
|
||||
mocks.MetaDBMock{}, testLog, nil)
|
||||
|
||||
So(deleteCalls, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("delete manifest not found is ignored", func() {
|
||||
is := mocks.MockedImageStore{
|
||||
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
|
||||
return zerr.ErrManifestNotFound
|
||||
},
|
||||
}
|
||||
|
||||
sc := storage.StoreController{DefaultStore: &is}
|
||||
rollbackDigestManifestTags(ctx, "repo", []string{"a"}, nil, mediaType, dgst, body, sc,
|
||||
mocks.MetaDBMock{}, testLog, nil)
|
||||
})
|
||||
|
||||
Convey("on delete manifest failure is logged", func() {
|
||||
is := mocks.MockedImageStore{}
|
||||
|
||||
metaDB := mocks.MetaDBMock{
|
||||
RemoveRepoReferenceFn: func(string, string, godigest.Digest) error {
|
||||
return errors.New("remove failed")
|
||||
},
|
||||
}
|
||||
|
||||
sc := storage.StoreController{DefaultStore: &is}
|
||||
rollbackDigestManifestTags(ctx, "repo", []string{"t"}, []string{"t"}, mediaType, dgst, body, sc,
|
||||
metaDB, testLog, nil)
|
||||
})
|
||||
|
||||
Convey("prior restore get blob fails", func() {
|
||||
other := testimage.CreateRandomImage()
|
||||
priorD := (&other).Digest()
|
||||
prior := map[string]priorTagManifest{
|
||||
"t": {digest: priorD, mediaType: mediaType},
|
||||
}
|
||||
|
||||
is := mocks.MockedImageStore{
|
||||
DeleteImageManifestFn: func(string, string, bool) error { return nil },
|
||||
GetBlobContentFn: func(string, godigest.Digest) ([]byte, error) {
|
||||
return nil, errors.New("blob missing")
|
||||
},
|
||||
}
|
||||
|
||||
sc := storage.StoreController{DefaultStore: &is}
|
||||
rollbackDigestManifestTags(ctx, "repo", []string{"t"}, []string{"t"}, mediaType, dgst, body, sc,
|
||||
mocks.MetaDBMock{}, testLog, prior)
|
||||
})
|
||||
|
||||
Convey("prior restore put manifest fails", func() {
|
||||
priorBody := body
|
||||
priorD := dgst
|
||||
prior := map[string]priorTagManifest{
|
||||
"t": {digest: priorD, mediaType: mediaType},
|
||||
}
|
||||
|
||||
is := mocks.MockedImageStore{
|
||||
DeleteImageManifestFn: func(string, string, bool) error { return nil },
|
||||
GetBlobContentFn: func(_ string, blobDigest godigest.Digest) ([]byte, error) {
|
||||
So(blobDigest, ShouldResemble, priorD)
|
||||
|
||||
return priorBody, nil
|
||||
},
|
||||
PutImageManifestFn: func(string, string, string, []byte, []string) (godigest.Digest, godigest.Digest, error) {
|
||||
return "", "", errors.New("put failed")
|
||||
},
|
||||
}
|
||||
|
||||
sc := storage.StoreController{DefaultStore: &is}
|
||||
rollbackDigestManifestTags(ctx, "repo", []string{"t"}, []string{"t"}, mediaType, dgst, body, sc,
|
||||
mocks.MetaDBMock{}, testLog, prior)
|
||||
})
|
||||
|
||||
Convey("prior restore metadb update fails", func() {
|
||||
priorBody := body
|
||||
priorD := dgst
|
||||
prior := map[string]priorTagManifest{
|
||||
"t": {digest: priorD, mediaType: mediaType},
|
||||
}
|
||||
|
||||
var manifest ispec.Manifest
|
||||
err := json.Unmarshal(priorBody, &manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configBytes, err := json.Marshal(img.Config)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
metaDB := mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(context.Context, string, string, mTypes.ImageMeta) error {
|
||||
return errors.New("set ref failed")
|
||||
},
|
||||
}
|
||||
|
||||
is := mocks.MockedImageStore{
|
||||
DeleteImageManifestFn: func(string, string, bool) error { return nil },
|
||||
GetBlobContentFn: func(_ string, blobDigest godigest.Digest) ([]byte, error) {
|
||||
switch {
|
||||
case blobDigest == priorD:
|
||||
return priorBody, nil
|
||||
case blobDigest == manifest.Config.Digest:
|
||||
return configBytes, nil
|
||||
default:
|
||||
So(blobDigest.String(), ShouldBeIn,
|
||||
[]string{priorD.String(), manifest.Config.Digest.String()})
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
PutImageManifestFn: func(_, _, _ string, blob []byte, _ []string) (godigest.Digest, godigest.Digest, error) {
|
||||
d := godigest.FromBytes(blob)
|
||||
|
||||
return d, d, nil
|
||||
},
|
||||
}
|
||||
|
||||
sc := storage.StoreController{DefaultStore: &is}
|
||||
rollbackDigestManifestTags(ctx, "repo", []string{"t"}, []string{"t"}, mediaType, dgst, body, sc,
|
||||
metaDB, testLog, prior)
|
||||
})
|
||||
})
|
||||
}
|
||||
+393
-1
@@ -5,20 +5,412 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.dev/zot/v2/errors"
|
||||
"zotregistry.dev/zot/v2/pkg/extensions/monitoring"
|
||||
"zotregistry.dev/zot/v2/pkg/log"
|
||||
"zotregistry.dev/zot/v2/pkg/meta"
|
||||
"zotregistry.dev/zot/v2/pkg/meta/boltdb"
|
||||
mTypes "zotregistry.dev/zot/v2/pkg/meta/types"
|
||||
"zotregistry.dev/zot/v2/pkg/storage"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/local"
|
||||
stypes "zotregistry.dev/zot/v2/pkg/storage/types"
|
||||
. "zotregistry.dev/zot/v2/pkg/test/image-utils"
|
||||
"zotregistry.dev/zot/v2/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("test error")
|
||||
var (
|
||||
errDeleteAfterMetaHookTest = errors.New("delete manifest after meta failure hook test")
|
||||
errDigestTagsSetRepoReferenceFail = errors.New("injected SetRepoReference failure for digest-tags rollback tests")
|
||||
errGetRepoMetaForDigestTags = errors.New("get repo meta failed for digest tags test")
|
||||
errSetRepoRefForHookTest = errors.New("set repo reference failed for hook test")
|
||||
)
|
||||
|
||||
// setRepoRefFailMetaDB delegates to an inner MetaDB but fails SetRepoReference for one tag (used to
|
||||
// exercise multi-tag digest rollback after a partial OnUpdateManifest success).
|
||||
type setRepoRefFailMetaDB struct {
|
||||
mTypes.MetaDB
|
||||
|
||||
failRef string
|
||||
}
|
||||
|
||||
func (w *setRepoRefFailMetaDB) SetRepoReference(
|
||||
ctx context.Context, repo, ref string, imageMeta mTypes.ImageMeta,
|
||||
) error {
|
||||
if ref == w.failRef {
|
||||
return errDigestTagsSetRepoReferenceFail
|
||||
}
|
||||
|
||||
return w.MetaDB.SetRepoReference(ctx, repo, ref, imageMeta)
|
||||
}
|
||||
|
||||
// failDeleteImageStore delegates to an inner ImageStore but forces DeleteImageManifest to return deleteErr.
|
||||
type failDeleteImageStore struct {
|
||||
stypes.ImageStore
|
||||
|
||||
deleteErr error
|
||||
}
|
||||
|
||||
func (f *failDeleteImageStore) DeleteImageManifest(repo, reference string, detectCollision bool) error {
|
||||
return f.deleteErr
|
||||
}
|
||||
|
||||
func TestOnUpdateManifestDigestTags_emptyTags(t *testing.T) {
|
||||
Convey("OnUpdateManifestDigestTags with no tags is a no-op (nil MetaDB: no GetRepoMeta/SetRepoReference path)",
|
||||
t, func() {
|
||||
log := log.NewTestLogger()
|
||||
|
||||
err := meta.OnUpdateManifestDigestTags(context.Background(), "repo", nil, ispec.MediaTypeImageManifest,
|
||||
godigest.Digest(""), nil, storage.StoreController{}, nil, log)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifestDigestTags_success(t *testing.T) {
|
||||
Convey("OnUpdateManifestDigestTags updates metadb for each digest query tag", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
storeController := storage.StoreController{}
|
||||
log := log.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
defer metrics.Stop()
|
||||
storeController.DefaultStore = local.NewImageStore(rootDir, true, true, log, metrics, nil, nil, nil, nil)
|
||||
|
||||
params := boltdb.DBParameters{RootDir: rootDir}
|
||||
boltDriver, err := boltdb.GetBoltDriver(params)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
metaDB, err := boltdb.New(boltDriver, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image := CreateDefaultImage()
|
||||
mediaType := image.ManifestDescriptor.MediaType
|
||||
if mediaType == "" {
|
||||
mediaType = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
manifestBody := image.ManifestDescriptor.Data
|
||||
manifestDigest := image.Digest()
|
||||
|
||||
err = WriteImageToFileSystem(image, "repo", "seed", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifest(context.Background(), "repo", "seed", mediaType, manifestDigest, manifestBody,
|
||||
storeController, metaDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imgStore := storeController.GetImageStore("repo")
|
||||
_, _, err = imgStore.PutImageManifest("repo", manifestDigest.String(), mediaType, manifestBody,
|
||||
[]string{"ta", "tb"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifestDigestTags(context.Background(), "repo", []string{"ta", "tb"}, mediaType,
|
||||
manifestDigest, manifestBody, storeController, metaDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
wantDigest := manifestDigest.String()
|
||||
|
||||
repoMeta, err := metaDB.GetRepoMeta(context.Background(), "repo")
|
||||
So(err, ShouldBeNil)
|
||||
So(repoMeta.Tags, ShouldContainKey, "ta")
|
||||
So(repoMeta.Tags, ShouldContainKey, "tb")
|
||||
So(repoMeta.Tags, ShouldContainKey, "seed")
|
||||
So(repoMeta.Tags["ta"].Digest, ShouldEqual, wantDigest)
|
||||
So(repoMeta.Tags["tb"].Digest, ShouldEqual, wantDigest)
|
||||
So(repoMeta.Tags["seed"].Digest, ShouldEqual, wantDigest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifestDigestTags_rollbackPartialMeta(t *testing.T) {
|
||||
Convey("OnUpdateManifestDigestTags rollback deletes all new index tags; meta rollback only for applied tags",
|
||||
t, func() {
|
||||
rootDir := t.TempDir()
|
||||
storeController := storage.StoreController{}
|
||||
log := log.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
defer metrics.Stop()
|
||||
storeController.DefaultStore = local.NewImageStore(rootDir, true, true, log, metrics, nil, nil, nil, nil)
|
||||
|
||||
params := boltdb.DBParameters{RootDir: rootDir}
|
||||
boltDriver, err := boltdb.GetBoltDriver(params)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
metaDB, err := boltdb.New(boltDriver, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image := CreateDefaultImage()
|
||||
mediaType := image.ManifestDescriptor.MediaType
|
||||
if mediaType == "" {
|
||||
mediaType = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
manifestBody := image.ManifestDescriptor.Data
|
||||
manifestDigest := image.Digest()
|
||||
|
||||
err = WriteImageToFileSystem(image, "repo", "seed", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifest(context.Background(), "repo", "seed", mediaType, manifestDigest, manifestBody,
|
||||
storeController, metaDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imgStore := storeController.GetImageStore("repo")
|
||||
_, _, err = imgStore.PutImageManifest("repo", manifestDigest.String(), mediaType, manifestBody,
|
||||
[]string{"ta", "tb"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoMetaBefore, err := metaDB.GetRepoMeta(context.Background(), "repo")
|
||||
So(err, ShouldBeNil)
|
||||
seedDigestBefore := repoMetaBefore.Tags["seed"].Digest
|
||||
So(seedDigestBefore, ShouldEqual, manifestDigest.String())
|
||||
|
||||
wrapped := &setRepoRefFailMetaDB{MetaDB: metaDB, failRef: "tb"}
|
||||
|
||||
err = meta.OnUpdateManifestDigestTags(context.Background(), "repo", []string{"ta", "tb"}, mediaType,
|
||||
manifestDigest, manifestBody, storeController, wrapped, log)
|
||||
So(err, ShouldEqual, errDigestTagsSetRepoReferenceFail)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("repo", "ta")
|
||||
So(errors.Is(err, zerr.ErrManifestNotFound), ShouldBeTrue)
|
||||
_, _, _, err = imgStore.GetImageManifest("repo", "tb")
|
||||
So(errors.Is(err, zerr.ErrManifestNotFound), ShouldBeTrue)
|
||||
|
||||
seedBody, _, _, err := imgStore.GetImageManifest("repo", "seed")
|
||||
So(err, ShouldBeNil)
|
||||
So(godigest.FromBytes(seedBody).String(), ShouldEqual, manifestDigest.String())
|
||||
|
||||
repoMeta, err := metaDB.GetRepoMeta(context.Background(), "repo")
|
||||
So(err, ShouldBeNil)
|
||||
So(repoMeta.Tags, ShouldNotContainKey, "ta")
|
||||
So(repoMeta.Tags, ShouldNotContainKey, "tb")
|
||||
So(repoMeta.Tags, ShouldContainKey, "seed")
|
||||
So(repoMeta.Tags["seed"].Digest, ShouldEqual, seedDigestBefore)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifestDigestTags_rollbackRestoresMovedTag(t *testing.T) {
|
||||
Convey("rollback restores a tag moved from digest A to digest B back to digest A when MetaDB fails later",
|
||||
t, func() {
|
||||
rootDir := t.TempDir()
|
||||
storeController := storage.StoreController{}
|
||||
log := log.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
defer metrics.Stop()
|
||||
storeController.DefaultStore = local.NewImageStore(rootDir, true, true, log, metrics, nil, nil, nil, nil)
|
||||
|
||||
params := boltdb.DBParameters{RootDir: rootDir}
|
||||
boltDriver, err := boltdb.GetBoltDriver(params)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
metaDB, err := boltdb.New(boltDriver, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imageA := CreateDefaultImage()
|
||||
imageB := CreateRandomImage()
|
||||
So(imageA.Digest(), ShouldNotEqual, imageB.Digest())
|
||||
|
||||
mediaTypeA := imageA.ManifestDescriptor.MediaType
|
||||
if mediaTypeA == "" {
|
||||
mediaTypeA = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
mediaTypeB := imageB.ManifestDescriptor.MediaType
|
||||
if mediaTypeB == "" {
|
||||
mediaTypeB = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
bodyA := imageA.ManifestDescriptor.Data
|
||||
digestA := imageA.Digest()
|
||||
bodyB := imageB.ManifestDescriptor.Data
|
||||
digestB := imageB.Digest()
|
||||
|
||||
err = WriteImageToFileSystem(imageA, "repo", "movable", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifest(context.Background(), "repo", "movable", mediaTypeA, digestA, bodyA,
|
||||
storeController, metaDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = WriteImageToFileSystem(imageB, "repo", "yardB", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifest(context.Background(), "repo", "yardB", mediaTypeB, digestB, bodyB,
|
||||
storeController, metaDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imgStore := storeController.GetImageStore("repo")
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("repo", digestB.String(), mediaTypeB, bodyB,
|
||||
[]string{"movable", "onlyB"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
wrapped := &setRepoRefFailMetaDB{MetaDB: metaDB, failRef: "onlyB"}
|
||||
|
||||
err = meta.OnUpdateManifestDigestTags(context.Background(), "repo", []string{"movable", "onlyB"}, mediaTypeB,
|
||||
digestB, bodyB, storeController, wrapped, log)
|
||||
So(err, ShouldEqual, errDigestTagsSetRepoReferenceFail)
|
||||
|
||||
movableBody, movableD, _, err := imgStore.GetImageManifest("repo", "movable")
|
||||
So(err, ShouldBeNil)
|
||||
So(movableD.String(), ShouldEqual, digestA.String())
|
||||
So(godigest.FromBytes(movableBody).String(), ShouldEqual, digestA.String())
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("repo", "onlyB")
|
||||
So(errors.Is(err, zerr.ErrManifestNotFound), ShouldBeTrue)
|
||||
|
||||
repoMeta, err := metaDB.GetRepoMeta(context.Background(), "repo")
|
||||
So(err, ShouldBeNil)
|
||||
So(repoMeta.Tags["movable"].Digest, ShouldEqual, digestA.String())
|
||||
So(repoMeta.Tags["yardB"].Digest, ShouldEqual, digestB.String())
|
||||
So(repoMeta.Tags, ShouldNotContainKey, "onlyB")
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifestDigestTags_getRepoMetaError(t *testing.T) {
|
||||
Convey("OnUpdateManifestDigestTags returns when GetRepoMeta fails with a non-ErrRepoMetaNotFound error", t, func() {
|
||||
log := log.NewTestLogger()
|
||||
metaDB := mocks.MetaDBMock{
|
||||
GetRepoMetaFn: func(context.Context, string) (mTypes.RepoMeta, error) {
|
||||
return mTypes.RepoMeta{}, errGetRepoMetaForDigestTags
|
||||
},
|
||||
}
|
||||
|
||||
d := godigest.FromString("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
|
||||
err := meta.OnUpdateManifestDigestTags(context.Background(), "repo", []string{"a"}, ispec.MediaTypeImageManifest,
|
||||
d, []byte("{}"), storage.StoreController{}, metaDB, log)
|
||||
So(errors.Is(err, errGetRepoMetaForDigestTags), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifestDigestTags_whenRepoMetaMissing(t *testing.T) {
|
||||
Convey("ErrRepoMetaNotFound during snapshot still allows digest query tag meta updates", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
storeController := storage.StoreController{}
|
||||
log := log.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
defer metrics.Stop()
|
||||
storeController.DefaultStore = local.NewImageStore(rootDir, true, true, log, metrics, nil, nil, nil, nil)
|
||||
|
||||
params := boltdb.DBParameters{RootDir: rootDir}
|
||||
boltDriver, err := boltdb.GetBoltDriver(params)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
metaDB, err := boltdb.New(boltDriver, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image := CreateDefaultImage()
|
||||
mediaType := image.ManifestDescriptor.MediaType
|
||||
if mediaType == "" {
|
||||
mediaType = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
manifestBody := image.ManifestDescriptor.Data
|
||||
manifestDigest := image.Digest()
|
||||
|
||||
err = WriteImageToFileSystem(image, "repo", "seed", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = metaDB.GetRepoMeta(context.Background(), "repo")
|
||||
So(errors.Is(err, zerr.ErrRepoMetaNotFound), ShouldBeTrue)
|
||||
|
||||
imgStore := storeController.GetImageStore("repo")
|
||||
_, _, err = imgStore.PutImageManifest("repo", manifestDigest.String(), mediaType, manifestBody,
|
||||
[]string{"ta", "tb"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifestDigestTags(context.Background(), "repo", []string{"ta", "tb"}, mediaType,
|
||||
manifestDigest, manifestBody, storeController, metaDB, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
wantDigest := manifestDigest.String()
|
||||
|
||||
repoMeta, err := metaDB.GetRepoMeta(context.Background(), "repo")
|
||||
So(err, ShouldBeNil)
|
||||
So(repoMeta.Tags, ShouldContainKey, "ta")
|
||||
So(repoMeta.Tags, ShouldContainKey, "tb")
|
||||
So(repoMeta.Tags, ShouldNotContainKey, "seed")
|
||||
So(repoMeta.Tags["ta"].Digest, ShouldEqual, wantDigest)
|
||||
So(repoMeta.Tags["tb"].Digest, ShouldEqual, wantDigest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifest_setRepoReferenceFailsRemovesManifest(t *testing.T) {
|
||||
Convey("OnUpdateManifest deletes the manifest from the store when SetRepoReference fails", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
storeController := storage.StoreController{}
|
||||
log := log.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
defer metrics.Stop()
|
||||
storeController.DefaultStore = local.NewImageStore(rootDir, true, true, log, metrics, nil, nil, nil, nil)
|
||||
|
||||
metaDB := mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(context.Context, string, string, mTypes.ImageMeta) error {
|
||||
return errSetRepoRefForHookTest
|
||||
},
|
||||
}
|
||||
|
||||
image := CreateDefaultImage()
|
||||
mediaType := image.ManifestDescriptor.MediaType
|
||||
if mediaType == "" {
|
||||
mediaType = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
err := WriteImageToFileSystem(image, "repo", "tag1", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imgStore := storeController.GetImageStore("repo")
|
||||
|
||||
err = meta.OnUpdateManifest(context.Background(), "repo", "tag1", mediaType, image.Digest(),
|
||||
image.ManifestDescriptor.Data, storeController, metaDB, log)
|
||||
So(errors.Is(err, errSetRepoRefForHookTest), ShouldBeTrue)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("repo", "tag1")
|
||||
So(errors.Is(err, zerr.ErrManifestNotFound), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifest_whenDeleteAfterMetaFailureFails(t *testing.T) {
|
||||
Convey("OnUpdateManifest returns the delete error when meta fails and store cleanup fails", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
storeController := storage.StoreController{}
|
||||
log := log.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
defer metrics.Stop()
|
||||
baseStore := local.NewImageStore(rootDir, true, true, log, metrics, nil, nil, nil, nil)
|
||||
storeController.DefaultStore = &failDeleteImageStore{
|
||||
ImageStore: baseStore,
|
||||
deleteErr: errDeleteAfterMetaHookTest,
|
||||
}
|
||||
|
||||
metaDB := mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(context.Context, string, string, mTypes.ImageMeta) error {
|
||||
return errSetRepoRefForHookTest
|
||||
},
|
||||
}
|
||||
|
||||
image := CreateDefaultImage()
|
||||
mediaType := image.ManifestDescriptor.MediaType
|
||||
if mediaType == "" {
|
||||
mediaType = ispec.MediaTypeImageManifest
|
||||
}
|
||||
|
||||
err := WriteImageToFileSystem(image, "repo", "tag1", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = meta.OnUpdateManifest(context.Background(), "repo", "tag1", mediaType, image.Digest(),
|
||||
image.ManifestDescriptor.Data, storeController, metaDB, log)
|
||||
So(errors.Is(err, errDeleteAfterMetaHookTest), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnUpdateManifest(t *testing.T) {
|
||||
Convey("On UpdateManifest", t, func() {
|
||||
|
||||
+17
-14
@@ -40,13 +40,16 @@ import (
|
||||
|
||||
const repo = "repo"
|
||||
|
||||
// errMetaTestInjected is returned from mocks in this file to assert error paths in ParseStorage and related tests.
|
||||
var errMetaTestInjected = errors.New("injected error for parse_test mocks")
|
||||
|
||||
func TestParseStorageErrors(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
Convey("ParseStorage", t, func() {
|
||||
imageStore := mocks.MockedImageStore{
|
||||
GetIndexContentFn: func(repo string) ([]byte, error) {
|
||||
return nil, ErrTestError
|
||||
return nil, errMetaTestInjected
|
||||
},
|
||||
GetRepositoriesFn: func() ([]string, error) {
|
||||
return []string{"repo1", "repo2"}, nil
|
||||
@@ -67,7 +70,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
}
|
||||
imageStore2 := mocks.MockedImageStore{
|
||||
GetRepositoriesFn: func() ([]string, error) {
|
||||
return nil, ErrTestError
|
||||
return nil, errMetaTestInjected
|
||||
},
|
||||
}
|
||||
storeController := storage.StoreController{
|
||||
@@ -82,7 +85,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("metaDB.GetAllRepoNames errors", func() {
|
||||
metaDB.GetAllRepoNamesFn = func() ([]string, error) { return nil, ErrTestError }
|
||||
metaDB.GetAllRepoNamesFn = func() ([]string, error) { return nil, errMetaTestInjected }
|
||||
|
||||
err := meta.ParseStorage(metaDB, storeController, log.NewTestLogger())
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -95,7 +98,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
storeController := storage.StoreController{DefaultStore: imageStore1}
|
||||
|
||||
metaDB.GetAllRepoNamesFn = func() ([]string, error) { return []string{"deleted"}, nil }
|
||||
metaDB.DeleteRepoMetaFn = func(repo string) error { return ErrTestError }
|
||||
metaDB.DeleteRepoMetaFn = func(repo string) error { return errMetaTestInjected }
|
||||
|
||||
err := meta.ParseStorage(metaDB, storeController, log.NewTestLogger())
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -106,7 +109,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
GetRepositoriesFn: func() ([]string, error) { return []string{"repo1", "repo2"}, nil },
|
||||
}
|
||||
imageStore1.StatIndexFn = func(repo string) (bool, int64, time.Time, error) {
|
||||
return false, 0, time.Time{}, ErrTestError
|
||||
return false, 0, time.Time{}, errMetaTestInjected
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: imageStore1}
|
||||
@@ -124,7 +127,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
|
||||
Convey("imageStore.GetIndexContent errors", func() {
|
||||
imageStore.GetIndexContentFn = func(repo string) ([]byte, error) {
|
||||
return nil, ErrTestError
|
||||
return nil, errMetaTestInjected
|
||||
}
|
||||
|
||||
err := meta.ParseRepo("repo", metaDB, storeController, log)
|
||||
@@ -144,7 +147,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
imageStore.GetIndexContentFn = func(repo string) ([]byte, error) {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
metaDB.ResetRepoReferencesFn = func(repo string, tagsToKeep map[string]bool) error { return ErrTestError }
|
||||
metaDB.ResetRepoReferencesFn = func(repo string, tagsToKeep map[string]bool) error { return errMetaTestInjected }
|
||||
err := meta.ParseRepo("repo", metaDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
@@ -184,11 +187,11 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
}
|
||||
imageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
// Return a non-missing error (not ErrBlobNotFound or PathNotFoundError)
|
||||
return nil, ErrTestError
|
||||
return nil, errMetaTestInjected
|
||||
}
|
||||
err := meta.ParseRepo("repo", metaDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, ErrTestError)
|
||||
So(err, ShouldEqual, errMetaTestInjected)
|
||||
})
|
||||
|
||||
Convey("imageStore.GetImageManifest missing blob - graceful handling", func() {
|
||||
@@ -274,7 +277,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
|
||||
Convey("metaDB.SetRepoReference", func() {
|
||||
metaDB.SetRepoReferenceFn = func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error {
|
||||
return ErrTestError
|
||||
return errMetaTestInjected
|
||||
}
|
||||
|
||||
err = meta.ParseRepo("repo", metaDB, storeController, log)
|
||||
@@ -293,7 +296,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
Convey("Image Manifest errors", func() {
|
||||
Convey("Get Config blob error", func() {
|
||||
mockImageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
return []byte{}, errMetaTestInjected
|
||||
}
|
||||
|
||||
err := meta.SetImageMetaFromInput(ctx, "repo", "tag", ispec.MediaTypeImageManifest, image.Digest(),
|
||||
@@ -326,7 +329,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
mockedMetaDB.UpdateSignaturesValidityFn = func(ctx context.Context, repo string,
|
||||
manifestDigest godigest.Digest,
|
||||
) error {
|
||||
return ErrTestError
|
||||
return errMetaTestInjected
|
||||
}
|
||||
err := meta.SetImageMetaFromInput(ctx, "repo", "tag", mediaType, goodNotationSignature.Digest(),
|
||||
goodNotationSignature.ManifestDescriptor.Data, mockImageStore, mockedMetaDB, log)
|
||||
@@ -907,7 +910,7 @@ func TestGetSignatureLayersInfo(t *testing.T) {
|
||||
Convey("GetBlobContent errors", t, func() {
|
||||
mockImageStore := mocks.MockedImageStore{}
|
||||
mockImageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return nil, ErrTestError
|
||||
return nil, errMetaTestInjected
|
||||
}
|
||||
image := CreateRandomImage()
|
||||
|
||||
@@ -930,7 +933,7 @@ func TestGetSignatureLayersInfo(t *testing.T) {
|
||||
Convey("notation GetBlobContent errors", t, func() {
|
||||
mockImageStore := mocks.MockedImageStore{}
|
||||
mockImageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return nil, ErrTestError
|
||||
return nil, errMetaTestInjected
|
||||
}
|
||||
image := CreateImageWith().RandomLayers(1, 10).RandomConfig().Build()
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package regexp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TagMaxLen is the maximum length of a manifest tag in the OCI Distribution Specification
|
||||
// (opencontainers/distribution-spec spec.md, "Pulling manifests").
|
||||
const TagMaxLen = 128
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
// alphaNumericRegexp defines the alpha numeric atom, typically a
|
||||
@@ -33,8 +38,20 @@ var (
|
||||
// FullNameRegexp is the format which matches the full string of the
|
||||
// name component of reference.
|
||||
FullNameRegexp = expression(match("^"), NameRegexp, match("$"))
|
||||
|
||||
// TagRegexp matches a manifest tag per the OCI Distribution Specification
|
||||
// (opencontainers/distribution-spec spec.md, "Pulling manifests"): a tag MUST be at most
|
||||
// TagMaxLen characters and MUST match
|
||||
// [a-zA-Z0-9_][a-zA-Z0-9._-]* with the suffix length bounded by TagMaxLen (anchored).
|
||||
TagRegexp = match(fmt.Sprintf(`^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,%d}$`, TagMaxLen-1))
|
||||
)
|
||||
|
||||
// IsDistributionSpecTag reports whether s is a valid distribution-spec tag (same grammar as the
|
||||
// <reference> component in GET /v2/<name>/manifests/<reference> when <reference> is a tag).
|
||||
func IsDistributionSpecTag(s string) bool {
|
||||
return TagRegexp.MatchString(s)
|
||||
}
|
||||
|
||||
// match compiles the string to a regular expression.
|
||||
//
|
||||
//nolint:gochecknoglobals
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package regexp_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
zreg "zotregistry.dev/zot/v2/pkg/regexp"
|
||||
)
|
||||
|
||||
func TestIsDistributionSpecTag(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
tag string
|
||||
valid bool
|
||||
}{
|
||||
{"empty", "", false},
|
||||
{"latest", "latest", true},
|
||||
{"with dots", "v1.0.0", true},
|
||||
{"with hyphen", "meta-a", true},
|
||||
{"with underscore", "my_tag", true},
|
||||
{"max length", strings.Repeat("a", zreg.TagMaxLen), true},
|
||||
{"too long", strings.Repeat("a", zreg.TagMaxLen+1), false},
|
||||
{"slash", "bad/ref", false},
|
||||
{"leading dot", ".bad", false},
|
||||
{"colon", "bad:tag", false},
|
||||
{"space", "bad tag", false},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := zreg.IsDistributionSpecTag(testCase.tag)
|
||||
if got != testCase.valid {
|
||||
t.Fatalf("IsDistributionSpecTag(%q) = %v, want %v", testCase.tag, got, testCase.valid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -59,19 +59,19 @@ func TestValidateManifest(t *testing.T) {
|
||||
body, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageConfig, body)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageConfig, body, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrBadManifest)
|
||||
})
|
||||
|
||||
Convey("empty manifest with bad media type", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageConfig, []byte(""))
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageConfig, []byte(""), nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrBadManifest)
|
||||
})
|
||||
|
||||
Convey("empty manifest with correct media type", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, []byte(""))
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, []byte(""), nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrBadManifest)
|
||||
})
|
||||
@@ -97,7 +97,7 @@ func TestValidateManifest(t *testing.T) {
|
||||
body, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var internalErr *zerr.Error
|
||||
@@ -134,7 +134,7 @@ func TestValidateManifest(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// this was actually an umoci error on config blob
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
@@ -163,7 +163,7 @@ func TestValidateManifest(t *testing.T) {
|
||||
body, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
@@ -182,7 +182,7 @@ func TestValidateManifest(t *testing.T) {
|
||||
body, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -125,7 +125,7 @@ func TestGarbageCollectManifestErrors(t *testing.T) {
|
||||
|
||||
manifestDigest := godigest.FromBytes(body)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, body)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, body, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("trigger GetIndex error in GetReferencedBlobs", func() {
|
||||
@@ -247,7 +247,7 @@ func TestGarbageCollectIndexErrors(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -264,7 +264,7 @@ func TestGarbageCollectIndexErrors(t *testing.T) {
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index, err = common.GetIndex(imgStore, repoName, log)
|
||||
|
||||
@@ -1384,7 +1384,7 @@ func TestGarbageCollectDeletion(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootIndexDigest, _, err := imgStore.PutImageManifest(repoName, "topindex", ispec.MediaTypeImageIndex,
|
||||
topIndexBlob)
|
||||
topIndexBlob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
bottomIndex1Digest := bottomIndex1.IndexDescriptor.Digest
|
||||
|
||||
+39
-39
@@ -492,7 +492,7 @@ func TestGCSDriver(t *testing.T) {
|
||||
// Upload manifest
|
||||
mblob, err := json.Marshal(image.Manifest)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, mblob)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, mblob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Verify manifest
|
||||
@@ -540,7 +540,7 @@ func TestGCSDriver(t *testing.T) {
|
||||
// Upload manifest
|
||||
mblob, err := json.Marshal(image.Manifest)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, mblob)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, mblob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest(repoName, "1.0", false)
|
||||
@@ -638,7 +638,7 @@ func TestGCSDedupe(t *testing.T) {
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe1", manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe1", manifestDigest.String())
|
||||
@@ -712,7 +712,7 @@ func TestGCSDedupe(t *testing.T) {
|
||||
|
||||
manifestDigest = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe2", manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe2", manifestDigest.String())
|
||||
@@ -905,7 +905,7 @@ func TestGCSDeleteBlobsInUse(t *testing.T) {
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest, _, err := imgStore.PutImageManifest("repo", tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
manifestDigest, _, err := imgStore.PutImageManifest("repo", tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Try to delete blob currently in use", func() {
|
||||
@@ -1189,19 +1189,19 @@ func TestGCSStorageAPIs(t *testing.T) {
|
||||
|
||||
Convey("Bad image manifest", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), "application/json",
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), ispec.MediaTypeImageManifest,
|
||||
[]byte{})
|
||||
[]byte{}, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), ispec.MediaTypeImageManifest,
|
||||
[]byte(`{"test":true}`))
|
||||
[]byte(`{"test":true}`), nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), ispec.MediaTypeImageManifest,
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
@@ -1250,20 +1250,20 @@ func TestGCSStorageAPIs(t *testing.T) {
|
||||
badMb, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, badMb)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, badMb, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// same manifest for coverage
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "2.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "2.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "3.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "3.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = imgStore.GetImageTags("inexistent")
|
||||
@@ -1434,11 +1434,11 @@ func TestGCSStorageAPIs(t *testing.T) {
|
||||
|
||||
Convey("Bad image manifest", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, []byte("bad json"))
|
||||
ispec.MediaTypeImageManifest, []byte("bad json"), nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
@@ -1475,12 +1475,12 @@ func TestGCSStorageAPIs(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// same manifest for coverage
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
@@ -1566,7 +1566,7 @@ func TestGCSStorageAPIs(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
digest = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("replace", digest.String())
|
||||
@@ -1620,7 +1620,7 @@ func TestGCSStorageAPIs(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
@@ -1893,7 +1893,7 @@ func TestGCSMandatoryAnnotations(t *testing.T) {
|
||||
},
|
||||
}, storeDriver, cacheDriver, nil, nil)
|
||||
|
||||
_, _, err = imgStoreWithLinter.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStoreWithLinter.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
@@ -1913,7 +1913,7 @@ func TestGCSMandatoryAnnotations(t *testing.T) {
|
||||
},
|
||||
}, storeDriver, nil, nil, nil)
|
||||
|
||||
_, _, err = imgStoreWithLinter.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStoreWithLinter.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
@@ -1974,7 +1974,7 @@ func pushRandomImageIndexGCS(imgStore storageTypes.ImageStore, repoName string,
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -1991,7 +1991,7 @@ func pushRandomImageIndexGCS(imgStore storageTypes.ImageStore, repoName string,
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return bdgst, digest, indexDigest, int64(len(indexContent))
|
||||
@@ -2109,7 +2109,7 @@ func TestGCSGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// put artifact referencing above image
|
||||
@@ -2150,7 +2150,7 @@ func TestGCSGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push orphan artifact (missing subject)
|
||||
@@ -2168,7 +2168,7 @@ func TestGCSGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
// push orphan artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, orphanArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = garbageCollect.CleanRepo(ctx, repoName)
|
||||
@@ -2315,7 +2315,7 @@ func TestGCSGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err := imgStore.CheckBlob(repoName, bdgst)
|
||||
@@ -2489,7 +2489,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -2525,7 +2525,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
@@ -2572,7 +2572,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
innerIndex.Manifests = append(innerIndex.Manifests, ispec.Descriptor{
|
||||
@@ -2590,7 +2590,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
So(innerIndexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, innerIndexDigest.String(),
|
||||
ispec.MediaTypeImageIndex, innerIndexContent)
|
||||
ispec.MediaTypeImageIndex, innerIndexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// add inner index into root index
|
||||
@@ -2608,7 +2608,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest := ispec.Manifest{
|
||||
@@ -2637,7 +2637,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest.Subject = &ispec.Descriptor{
|
||||
@@ -2654,7 +2654,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing a manifest from index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactManifestIndexDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestIndexBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestIndexBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest.Subject = &ispec.Descriptor{
|
||||
@@ -2671,7 +2671,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing a manifest from index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactManifestInnerIndexDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestInnerIndexBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestInnerIndexBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push artifact manifest pointing to artifact above
|
||||
@@ -2688,7 +2688,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
artifactOfArtifactManifestDigest := godigest.FromBytes(artifactManifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactOfArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push orphan artifact (missing subject)
|
||||
@@ -2706,7 +2706,7 @@ func TestGCSGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push orphan artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, orphanArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err := imgStore.CheckBlob(repoName, bdgst)
|
||||
@@ -3225,7 +3225,7 @@ func RunGCSCheckAllBlobsIntegrityTests( //nolint: thelper
|
||||
|
||||
indexBlob, err := json.Marshal(index)
|
||||
So(err, ShouldBeNil)
|
||||
indexDigest, _, err := imgStore.PutImageManifest(repoName, "", ispec.MediaTypeImageIndex, indexBlob)
|
||||
indexDigest, _, err := imgStore.PutImageManifest(repoName, "", ispec.MediaTypeImageIndex, indexBlob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
buff := bytes.NewBufferString("")
|
||||
|
||||
@@ -536,8 +536,11 @@ func (is *ImageStore) GetImageManifest(repo, reference string) ([]byte, godigest
|
||||
}
|
||||
|
||||
// PutImageManifest adds an image manifest to the repository.
|
||||
func (is *ImageStore) PutImageManifest(repo, reference, mediaType string, //nolint: gocyclo
|
||||
body []byte,
|
||||
// When extraTags is non-empty, the reference must be a digest; each entry becomes an
|
||||
// org.opencontainers.image.ref.name on a separate index descriptor (distribution-spec
|
||||
// digest push with tag query params).
|
||||
func (is *ImageStore) PutImageManifest(repo, reference, mediaType string, //nolint: gocyclo,cyclop
|
||||
body []byte, extraTags []string,
|
||||
) (godigest.Digest, godigest.Digest, error) {
|
||||
if err := is.InitRepo(repo); err != nil {
|
||||
is.log.Debug().Err(err).Msg("init repo")
|
||||
@@ -570,6 +573,12 @@ func (is *ImageStore) PutImageManifest(repo, reference, mediaType string, //noli
|
||||
return mDigest, "", err
|
||||
}
|
||||
|
||||
// Tag query parameters apply only to digest-addressed pushes (?tag= on PUT .../manifests/<digest>).
|
||||
// If the path reference is not a digest, extraTags must be empty; otherwise the request is invalid.
|
||||
if len(extraTags) > 0 {
|
||||
return "", "", zerr.ErrBadManifest
|
||||
}
|
||||
|
||||
refIsDigest = false
|
||||
}
|
||||
|
||||
@@ -610,27 +619,18 @@ func (is *ImageStore) PutImageManifest(repo, reference, mediaType string, //noli
|
||||
|
||||
artifactType = zcommon.GetManifestArtifactType(manifest)
|
||||
} else if mediaType == ispec.MediaTypeImageIndex {
|
||||
var index ispec.Index
|
||||
var imgIndex ispec.Index
|
||||
|
||||
err := json.Unmarshal(body, &index)
|
||||
err := json.Unmarshal(body, &imgIndex)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if index.Subject != nil {
|
||||
subjectDigest = index.Subject.Digest
|
||||
if imgIndex.Subject != nil {
|
||||
subjectDigest = imgIndex.Subject.Digest
|
||||
}
|
||||
|
||||
artifactType = zcommon.GetIndexArtifactType(index)
|
||||
}
|
||||
|
||||
updateIndex, oldDgst, err := common.CheckIfIndexNeedsUpdate(&index, &desc, is.log)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !updateIndex {
|
||||
return mDigest, subjectDigest, nil
|
||||
artifactType = zcommon.GetIndexArtifactType(imgIndex)
|
||||
}
|
||||
|
||||
// write manifest to "blobs"
|
||||
@@ -647,27 +647,116 @@ func (is *ImageStore) PutImageManifest(repo, reference, mediaType string, //noli
|
||||
}
|
||||
}
|
||||
|
||||
err = common.UpdateIndexWithPrunedImageManifests(is, &index, repo, desc, oldDgst, is.log)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
var (
|
||||
lintDesc ispec.Descriptor
|
||||
commitEventRefs []string
|
||||
)
|
||||
|
||||
// now update "index.json"
|
||||
for midx, manifest := range index.Manifests {
|
||||
_, ok := manifest.Annotations[ispec.AnnotationRefName]
|
||||
if !ok && manifest.Digest.String() == desc.Digest.String() {
|
||||
// matching descriptor does not have a tag, we need to remove it and add the new descriptor
|
||||
index.Manifests = append(index.Manifests[:midx], index.Manifests[midx+1:]...)
|
||||
if len(extraTags) > 0 {
|
||||
for midx := 0; midx < len(index.Manifests); {
|
||||
manifest := index.Manifests[midx]
|
||||
_, hasTag := manifest.Annotations[ispec.AnnotationRefName]
|
||||
if !hasTag && manifest.Digest.String() == mDigest.String() {
|
||||
index.Manifests = append(index.Manifests[:midx], index.Manifests[midx+1:]...)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
midx++
|
||||
}
|
||||
|
||||
anyIndexChange := false
|
||||
|
||||
changedTags := make([]string, 0, len(extraTags))
|
||||
|
||||
var (
|
||||
updateIndex bool
|
||||
oldDgst godigest.Digest
|
||||
)
|
||||
|
||||
for _, tag := range extraTags {
|
||||
descLocal := ispec.Descriptor{
|
||||
MediaType: mediaType,
|
||||
Size: desc.Size,
|
||||
Digest: mDigest,
|
||||
Annotations: map[string]string{
|
||||
ispec.AnnotationRefName: tag,
|
||||
},
|
||||
}
|
||||
|
||||
updateIndex, oldDgst, err = common.CheckIfIndexNeedsUpdate(&index, &descLocal, is.log)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !updateIndex {
|
||||
continue
|
||||
}
|
||||
|
||||
anyIndexChange = true
|
||||
|
||||
if err = common.UpdateIndexWithPrunedImageManifests(is, &index, repo, descLocal, oldDgst, is.log); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
index.Manifests = append(index.Manifests, descLocal)
|
||||
changedTags = append(changedTags, tag)
|
||||
}
|
||||
|
||||
if !anyIndexChange {
|
||||
return mDigest, subjectDigest, nil
|
||||
}
|
||||
|
||||
lintDesc = ispec.Descriptor{
|
||||
MediaType: mediaType,
|
||||
Size: desc.Size,
|
||||
Digest: mDigest,
|
||||
ArtifactType: artifactType,
|
||||
Annotations: map[string]string{
|
||||
ispec.AnnotationRefName: changedTags[0],
|
||||
},
|
||||
}
|
||||
|
||||
commitEventRefs = changedTags
|
||||
} else {
|
||||
updateIndex, oldDgst, err := common.CheckIfIndexNeedsUpdate(&index, &desc, is.log)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !updateIndex {
|
||||
return mDigest, subjectDigest, nil
|
||||
}
|
||||
|
||||
err = common.UpdateIndexWithPrunedImageManifests(is, &index, repo, desc, oldDgst, is.log)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// now update "index.json"
|
||||
for midx := 0; midx < len(index.Manifests); {
|
||||
manifest := index.Manifests[midx]
|
||||
_, ok := manifest.Annotations[ispec.AnnotationRefName]
|
||||
if !ok && manifest.Digest.String() == desc.Digest.String() {
|
||||
// matching descriptor does not have a tag, we need to remove it and add the new descriptor
|
||||
index.Manifests = append(index.Manifests[:midx], index.Manifests[midx+1:]...)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
midx++
|
||||
}
|
||||
|
||||
index.Manifests = append(index.Manifests, desc)
|
||||
|
||||
// update the descriptors artifact type in order to check for signatures when applying the linter
|
||||
desc.ArtifactType = artifactType
|
||||
|
||||
lintDesc = desc
|
||||
commitEventRefs = []string{reference}
|
||||
}
|
||||
|
||||
index.Manifests = append(index.Manifests, desc)
|
||||
|
||||
// update the descriptors artifact type in order to check for signatures when applying the linter
|
||||
desc.ArtifactType = artifactType
|
||||
|
||||
// apply linter only on images, not signatures
|
||||
pass, err := common.ApplyLinter(is, is.linter, repo, desc)
|
||||
pass, err := common.ApplyLinter(is, is.linter, repo, lintDesc)
|
||||
if !pass {
|
||||
is.log.Error().Err(err).Str("repository", repo).Str("reference", reference).
|
||||
Msg("linter didn't pass")
|
||||
@@ -684,7 +773,9 @@ func (is *ImageStore) PutImageManifest(repo, reference, mediaType string, //noli
|
||||
}
|
||||
|
||||
if is.events != nil {
|
||||
is.events.ImageUpdated(repo, reference, mDigest.String(), mediaType, string(body))
|
||||
for _, ref := range commitEventRefs {
|
||||
is.events.ImageUpdated(repo, ref, mDigest.String(), mediaType, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
return mDigest, subjectDigest, nil
|
||||
|
||||
@@ -142,7 +142,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(imgStore.RootDir(), repoName, "index.json"), 0o755)
|
||||
@@ -150,7 +150,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestPath := path.Join(imgStore.RootDir(), repoName, "blobs", digest.Algorithm().String(), digest.Encoded())
|
||||
@@ -176,7 +176,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(imgStore.RootDir(), repoName), 0o755)
|
||||
@@ -371,7 +371,7 @@ func FuzzTestPutGetImageManifest(f *testing.F) {
|
||||
|
||||
mdigest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
if err != nil && errors.Is(err, zerr.ErrBadManifest) {
|
||||
t.Errorf("the error that occurred is %v \n", err)
|
||||
}
|
||||
@@ -427,7 +427,7 @@ func FuzzTestPutDeleteImageManifest(f *testing.F) {
|
||||
|
||||
mdigest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
if err != nil && errors.Is(err, zerr.ErrBadManifest) {
|
||||
t.Errorf("the error that occurred is %v \n", err)
|
||||
}
|
||||
@@ -1178,7 +1178,7 @@ func TestDedupeLinks(t *testing.T) {
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe1", manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe1", manifestDigest.String())
|
||||
@@ -1240,7 +1240,7 @@ func TestDedupeLinks(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest2 := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe2", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe2", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe2", manifestDigest2.String())
|
||||
@@ -2477,7 +2477,7 @@ func TestGarbageCollectErrors(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -2494,7 +2494,7 @@ func TestGarbageCollectErrors(t *testing.T) {
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(imgStore.BlobPath(repoName, indexDigest), 0o000)
|
||||
@@ -2545,7 +2545,7 @@ func TestGarbageCollectErrors(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// trigger GetBlobContent error
|
||||
@@ -2604,10 +2604,10 @@ func TestGarbageCollectErrors(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
// upload again same manifest so that we trigger manifest conflict
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
+25
-25
@@ -337,7 +337,7 @@ func TestGetOCIReferrers(t *testing.T) {
|
||||
mbuflen := mbuf.Len()
|
||||
mdigest := godigest.FromBytes(mblob)
|
||||
|
||||
d, _, err := imgStore.PutImageManifest(repo, "1.0", ispec.MediaTypeImageManifest, mbuf.Bytes())
|
||||
d, _, err := imgStore.PutImageManifest(repo, "1.0", ispec.MediaTypeImageManifest, mbuf.Bytes(), nil)
|
||||
So(d, ShouldEqual, mdigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -391,7 +391,7 @@ func TestGetOCIReferrers(t *testing.T) {
|
||||
manBufLen := len(manBuf)
|
||||
manDigest := godigest.FromBytes(manBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manDigest.Encoded(), ispec.MediaTypeImageManifest, manBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repo, manDigest.Encoded(), ispec.MediaTypeImageManifest, manBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index, err := imgStore.GetReferrers(repo, mdigest, []string{artifactType})
|
||||
@@ -665,7 +665,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
||||
err = imgStore.DeleteImageManifest(testImage, "1.0", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(testImage, "1.0", "application/json", []byte{})
|
||||
_, _, err = imgStore.PutImageManifest(testImage, "1.0", "application/json", []byte{}, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = imgStore.PutBlobChunkStreamed(testImage, upload, bytes.NewBufferString(testImage))
|
||||
@@ -1042,7 +1042,7 @@ func TestS3Dedupe(t *testing.T) {
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe1", manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe1", manifestDigest.String())
|
||||
@@ -1121,7 +1121,7 @@ func TestS3Dedupe(t *testing.T) {
|
||||
|
||||
manifestDigest2 := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe2", "1.0", ispec.MediaTypeImageManifest,
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe2", manifestDigest2.String())
|
||||
@@ -1293,7 +1293,7 @@ func TestS3Dedupe(t *testing.T) {
|
||||
|
||||
manifestDigest3 := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe3", "1.0", ispec.MediaTypeImageManifest,
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe3", manifestDigest3.String())
|
||||
@@ -1468,7 +1468,7 @@ func TestS3Dedupe(t *testing.T) {
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe1", manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe1", manifestDigest.String())
|
||||
@@ -1538,7 +1538,7 @@ func TestS3Dedupe(t *testing.T) {
|
||||
|
||||
manifestDigest2 := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe2", "1.0", ispec.MediaTypeImageManifest,
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe2", manifestDigest2.String())
|
||||
@@ -1763,7 +1763,7 @@ func TestRebuildDedupeIndex(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe1", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe1", digest.String())
|
||||
@@ -1796,7 +1796,7 @@ func TestRebuildDedupeIndex(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("dedupe2", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("dedupe2", digest.String())
|
||||
@@ -2893,7 +2893,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
m1content := content
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:1.0", ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:1.0", ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// create another manifest but upload using its sha256 reference
|
||||
@@ -2937,7 +2937,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
So(digest, ShouldNotBeNil)
|
||||
m2dgst := digest
|
||||
m2size := len(content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Image index", func() {
|
||||
@@ -2978,7 +2978,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var index ispec.Index
|
||||
@@ -3002,7 +3002,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
index1dgst := digest
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageIndex, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -3046,7 +3046,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
So(digest, ShouldNotBeNil)
|
||||
m4dgst := digest
|
||||
m4size := len(content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.SchemaVersion = 2
|
||||
@@ -3069,7 +3069,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index2", ispec.MediaTypeImageIndex, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index2", ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index2")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -3100,7 +3100,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index3", ispec.MediaTypeImageIndex, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index3", ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index3")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -3122,7 +3122,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageIndex, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
@@ -3202,7 +3202,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
@@ -3222,7 +3222,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageIndex, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -3278,11 +3278,11 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:1.0", ispec.MediaTypeImageIndex, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:1.0", ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// previously an image index, try writing a manifest
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageManifest, m1content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageManifest, m1content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
@@ -3351,7 +3351,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
|
||||
m1size := len(content)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:1.0", ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", "test:1.0", ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// second config
|
||||
@@ -3386,7 +3386,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
So(m2digest, ShouldNotBeNil)
|
||||
|
||||
m2size := len(content)
|
||||
_, _, err = imgStore.PutImageManifest("index", m2digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest("index", m2digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Put image index with valid subject", func() {
|
||||
@@ -3412,7 +3412,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
idigest := godigest.FromBytes(content)
|
||||
So(idigest, ShouldNotBeNil)
|
||||
|
||||
digest1, digest2, err := imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageIndex, content)
|
||||
digest1, digest2, err := imgStore.PutImageManifest("index", "test:index1", ispec.MediaTypeImageIndex, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
So(digest1.String(), ShouldEqual, idigest.String())
|
||||
So(digest2.String(), ShouldEqual, m1digest.String())
|
||||
|
||||
@@ -490,7 +490,7 @@ func RunCheckAllBlobsIntegrityTests( //nolint: thelper
|
||||
|
||||
indexBlob, err := json.Marshal(index)
|
||||
So(err, ShouldBeNil)
|
||||
indexDigest, _, err := imgStore.PutImageManifest(repoName, "", ispec.MediaTypeImageIndex, indexBlob)
|
||||
indexDigest, _, err := imgStore.PutImageManifest(repoName, "", ispec.MediaTypeImageIndex, indexBlob, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
buff := bytes.NewBufferString("")
|
||||
|
||||
+397
-48
@@ -29,6 +29,7 @@ import (
|
||||
zerr "zotregistry.dev/zot/v2/errors"
|
||||
"zotregistry.dev/zot/v2/pkg/api/config"
|
||||
rediscfg "zotregistry.dev/zot/v2/pkg/api/config/redis"
|
||||
"zotregistry.dev/zot/v2/pkg/extensions/events"
|
||||
"zotregistry.dev/zot/v2/pkg/extensions/monitoring"
|
||||
zlog "zotregistry.dev/zot/v2/pkg/log"
|
||||
"zotregistry.dev/zot/v2/pkg/storage"
|
||||
@@ -149,6 +150,31 @@ func createObjectsStore(options createObjectStoreOpts) (
|
||||
return s3.New(s3Driver), imgStore, cacheDriver, err
|
||||
}
|
||||
|
||||
// newLocalImageStoreWithEventRecorder builds a filesystem-backed image store for tests with a non-nil
|
||||
// events.Recorder.
|
||||
func newLocalImageStoreWithEventRecorder(t *testing.T, recorder events.Recorder) storageTypes.ImageStore {
|
||||
t.Helper()
|
||||
|
||||
cacheDir := t.TempDir()
|
||||
rootDir := t.TempDir()
|
||||
log := zlog.NewTestLogger()
|
||||
|
||||
cacheDriver, err := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
||||
RootDir: cacheDir,
|
||||
Name: "cache",
|
||||
UseRelPaths: true,
|
||||
}, log)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
storeDriver := local.New(true)
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
return imagestore.NewImageStore(rootDir, cacheDir, true, true, log, metrics, nil,
|
||||
storeDriver, cacheDriver, nil, recorder)
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var testCases = []struct {
|
||||
testCaseName string
|
||||
@@ -189,6 +215,328 @@ func TestStorageNew(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
type captureImageEvents struct {
|
||||
mu sync.Mutex
|
||||
imageUpdated []imageUpdatedCall
|
||||
}
|
||||
|
||||
type imageUpdatedCall struct {
|
||||
repo, reference, digest, mediaType, manifest string
|
||||
}
|
||||
|
||||
func (c *captureImageEvents) Close() {}
|
||||
|
||||
func (c *captureImageEvents) RepositoryCreated(string) {}
|
||||
|
||||
func (c *captureImageEvents) ImageUpdated(name, reference, digest, mediaType, manifest string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.imageUpdated = append(c.imageUpdated, imageUpdatedCall{
|
||||
repo: name, reference: reference, digest: digest, mediaType: mediaType, manifest: manifest,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *captureImageEvents) ImageDeleted(string, string, string, string) {}
|
||||
|
||||
func (c *captureImageEvents) ImageLintFailed(string, string, string, string, string) {}
|
||||
|
||||
// TestPutImageManifestExtraTagsAndEvents covers extra-tag digest pushes, index updates, and ImageUpdated events.
|
||||
// One Convey uses createObjectsStore (nil recorder); the rest use newLocalImageStoreWithEventRecorder.
|
||||
func TestPutImageManifestExtraTagsAndEvents(t *testing.T) {
|
||||
countUntaggedIndexEntriesForDigest := func(t *testing.T, imgStore storageTypes.ImageStore, repo string,
|
||||
dgst godigest.Digest,
|
||||
) int {
|
||||
t.Helper()
|
||||
|
||||
raw, err := imgStore.GetIndexContent(repo)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var idx ispec.Index
|
||||
|
||||
err = json.Unmarshal(raw, &idx)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
n := 0
|
||||
|
||||
for _, m := range idx.Manifests {
|
||||
if m.Digest.String() != dgst.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := m.Annotations[ispec.AnnotationRefName]; !ok {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Uses createObjectsStore (nil event recorder); all other Conveys use newLocalImageStoreWithEventRecorder.
|
||||
Convey("non-digest path reference with extraTags returns ErrBadManifest", t, func() {
|
||||
cacheDir := t.TempDir()
|
||||
|
||||
opts := createObjectStoreOpts{
|
||||
rootDir: cacheDir,
|
||||
cacheDir: cacheDir,
|
||||
cacheType: storageConstants.BoltdbName,
|
||||
storageType: storageConstants.LocalStorageDriverName,
|
||||
}
|
||||
|
||||
_, imgStore, _, err := createObjectsStore(opts)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repo := "badtagquery"
|
||||
|
||||
cblob, cdigest := GetRandomImageConfig()
|
||||
_, _, err = imgStore.FullBlobUpload(repo, bytes.NewReader(cblob), cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layerBytes := []byte("layer")
|
||||
layerDigest := godigest.FromBytes(layerBytes)
|
||||
_, _, err = imgStore.FullBlobUpload(repo, bytes.NewReader(layerBytes), layerDigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifest := ispec.Manifest{}
|
||||
manifest.SchemaVersion = 2
|
||||
manifest.Config = ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
}
|
||||
manifest.Layers = []ispec.Descriptor{
|
||||
{MediaType: "application/vnd.oci.image.layer.v1.tar", Digest: layerDigest, Size: int64(len(layerBytes))},
|
||||
}
|
||||
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, "1.0", ispec.MediaTypeImageManifest, manifestBuf,
|
||||
[]string{"extra"})
|
||||
So(errors.Is(err, zerr.ErrBadManifest), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("digest push with multiple extra tags applies tags, emits ImageUpdated per tag, idempotent replay", t, func() {
|
||||
eventCapture := &captureImageEvents{}
|
||||
imgStore := newLocalImageStoreWithEventRecorder(t, eventCapture)
|
||||
|
||||
repo := "mquery"
|
||||
|
||||
cblob, cdigest := GetRandomImageConfig()
|
||||
_, _, err := imgStore.FullBlobUpload(repo, bytes.NewReader(cblob), cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layerBytes := []byte("layer-bytes")
|
||||
layerDigest := godigest.FromBytes(layerBytes)
|
||||
_, _, err = imgStore.FullBlobUpload(repo, bytes.NewReader(layerBytes), layerDigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifest := ispec.Manifest{}
|
||||
manifest.SchemaVersion = 2
|
||||
manifest.Config = ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
}
|
||||
manifest.Layers = []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: layerDigest,
|
||||
Size: int64(len(layerBytes)),
|
||||
},
|
||||
}
|
||||
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
extraTags := []string{"v1.2.3", "v1.2", "latest"}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, extraTags)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tags, err := imgStore.GetImageTags(repo)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
for _, qt := range extraTags {
|
||||
So(slices.Contains(tags, qt), ShouldBeTrue)
|
||||
}
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 3)
|
||||
|
||||
wantRefs := map[string]struct{}{"v1.2.3": {}, "v1.2": {}, "latest": {}}
|
||||
|
||||
for _, imageUpd := range eventCapture.imageUpdated {
|
||||
So(imageUpd.repo, ShouldEqual, repo)
|
||||
So(imageUpd.digest, ShouldEqual, manifestDigest.String())
|
||||
So(imageUpd.mediaType, ShouldEqual, ispec.MediaTypeImageManifest)
|
||||
So(imageUpd.manifest, ShouldEqual, string(manifestBuf))
|
||||
_, ok := wantRefs[imageUpd.reference]
|
||||
So(ok, ShouldBeTrue)
|
||||
|
||||
delete(wantRefs, imageUpd.reference)
|
||||
}
|
||||
|
||||
So(len(wantRefs), ShouldEqual, 0)
|
||||
eventCapture.mu.Unlock()
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, extraTags)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 3)
|
||||
eventCapture.mu.Unlock()
|
||||
})
|
||||
|
||||
Convey("digest push then digest+tags removes untagged index row for that digest", t, func() {
|
||||
eventCapture := &captureImageEvents{}
|
||||
imgStore := newLocalImageStoreWithEventRecorder(t, eventCapture)
|
||||
|
||||
repo := "strip-untagged"
|
||||
|
||||
cblob, cdigest := GetRandomImageConfig()
|
||||
_, _, err := imgStore.FullBlobUpload(repo, bytes.NewReader(cblob), cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layerBytes := []byte("layer-strip")
|
||||
layerDigest := godigest.FromBytes(layerBytes)
|
||||
_, _, err = imgStore.FullBlobUpload(repo, bytes.NewReader(layerBytes), layerDigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifest := ispec.Manifest{}
|
||||
manifest.SchemaVersion = 2
|
||||
manifest.Config = ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
}
|
||||
manifest.Layers = []ispec.Descriptor{
|
||||
{MediaType: "application/vnd.oci.image.layer.v1.tar", Digest: layerDigest, Size: int64(len(layerBytes))},
|
||||
}
|
||||
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 1)
|
||||
So(eventCapture.imageUpdated[0].repo, ShouldEqual, repo)
|
||||
So(eventCapture.imageUpdated[0].reference, ShouldEqual, manifestDigest.String())
|
||||
So(eventCapture.imageUpdated[0].digest, ShouldEqual, manifestDigest.String())
|
||||
eventCapture.mu.Unlock()
|
||||
|
||||
So(countUntaggedIndexEntriesForDigest(t, imgStore, repo, manifestDigest), ShouldEqual, 1)
|
||||
|
||||
extraTags := []string{"after-tag"}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, extraTags)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 2)
|
||||
So(eventCapture.imageUpdated[1].reference, ShouldEqual, "after-tag")
|
||||
So(eventCapture.imageUpdated[1].digest, ShouldEqual, manifestDigest.String())
|
||||
eventCapture.mu.Unlock()
|
||||
|
||||
So(countUntaggedIndexEntriesForDigest(t, imgStore, repo, manifestDigest), ShouldEqual, 0)
|
||||
|
||||
tags, err := imgStore.GetImageTags(repo)
|
||||
So(err, ShouldBeNil)
|
||||
So(slices.Contains(tags, "after-tag"), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("digest push with tag subset is no-op for index and events; new tag adds one event", t, func() {
|
||||
eventCapture := &captureImageEvents{}
|
||||
imgStore := newLocalImageStoreWithEventRecorder(t, eventCapture)
|
||||
|
||||
repo := "tag-subset"
|
||||
|
||||
cblob, cdigest := GetRandomImageConfig()
|
||||
_, _, err := imgStore.FullBlobUpload(repo, bytes.NewReader(cblob), cdigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layerBytes := []byte("layer-subset")
|
||||
layerDigest := godigest.FromBytes(layerBytes)
|
||||
_, _, err = imgStore.FullBlobUpload(repo, bytes.NewReader(layerBytes), layerDigest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifest := ispec.Manifest{}
|
||||
manifest.SchemaVersion = 2
|
||||
manifest.Config = ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
}
|
||||
manifest.Layers = []ispec.Descriptor{
|
||||
{MediaType: "application/vnd.oci.image.layer.v1.tar", Digest: layerDigest, Size: int64(len(layerBytes))},
|
||||
}
|
||||
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBuf)
|
||||
firstTags := []string{"tag-a", "tag-b", "tag-c"}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, firstTags)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tags, err := imgStore.GetImageTags(repo)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
for _, qt := range firstTags {
|
||||
So(slices.Contains(tags, qt), ShouldBeTrue)
|
||||
}
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 3)
|
||||
eventCapture.mu.Unlock()
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, []string{"tag-b"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tags, err = imgStore.GetImageTags(repo)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
for _, qt := range firstTags {
|
||||
So(slices.Contains(tags, qt), ShouldBeTrue)
|
||||
}
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 3)
|
||||
eventCapture.mu.Unlock()
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo, manifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf, []string{"tag-d"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tags, err = imgStore.GetImageTags(repo)
|
||||
So(err, ShouldBeNil)
|
||||
So(slices.Contains(tags, "tag-d"), ShouldBeTrue)
|
||||
|
||||
for _, qt := range firstTags {
|
||||
So(slices.Contains(tags, qt), ShouldBeTrue)
|
||||
}
|
||||
|
||||
eventCapture.mu.Lock()
|
||||
So(len(eventCapture.imageUpdated), ShouldEqual, 4)
|
||||
So(eventCapture.imageUpdated[3].reference, ShouldEqual, "tag-d")
|
||||
So(eventCapture.imageUpdated[3].digest, ShouldEqual, manifestDigest.String())
|
||||
So(eventCapture.imageUpdated[3].manifest, ShouldEqual, string(manifestBuf))
|
||||
eventCapture.mu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAllDedupeReposCandidates(t *testing.T) {
|
||||
for _, testcase := range testCases {
|
||||
t.Run(testcase.testCaseName, func(t *testing.T) {
|
||||
@@ -532,19 +880,19 @@ func TestStorageAPIs(t *testing.T) {
|
||||
|
||||
Convey("Bad image manifest", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), "application/json",
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), ispec.MediaTypeImageManifest,
|
||||
[]byte{})
|
||||
[]byte{}, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), ispec.MediaTypeImageManifest,
|
||||
[]byte(`{"test":true}`))
|
||||
[]byte(`{"test":true}`), nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(), ispec.MediaTypeImageManifest,
|
||||
manifestBuf)
|
||||
manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
@@ -593,20 +941,20 @@ func TestStorageAPIs(t *testing.T) {
|
||||
badMb, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, badMb)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, badMb, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// same manifest for coverage
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "2.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "2.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "3.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "3.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = imgStore.GetImageTags("inexistent")
|
||||
@@ -777,11 +1125,11 @@ func TestStorageAPIs(t *testing.T) {
|
||||
|
||||
Convey("Bad image manifest", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, []byte("bad json"))
|
||||
ispec.MediaTypeImageManifest, []byte("bad json"), nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
@@ -818,12 +1166,12 @@ func TestStorageAPIs(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// same manifest for coverage
|
||||
_, _, err = imgStore.PutImageManifest("test", digest.String(),
|
||||
ispec.MediaTypeImageManifest, manifestBuf)
|
||||
ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
@@ -918,7 +1266,7 @@ func TestStorageAPIs(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
digest = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("replace", digest.String())
|
||||
@@ -972,7 +1320,7 @@ func TestStorageAPIs(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = godigest.FromBytes(manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("replace", "1.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
@@ -1109,7 +1457,7 @@ func TestMandatoryAnnotations(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Missing mandatory annotations", func() {
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
@@ -1137,7 +1485,7 @@ func TestMandatoryAnnotations(t *testing.T) {
|
||||
}, store, cacheDriver, nil, nil)
|
||||
}
|
||||
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest("test", "1.0.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
@@ -1299,7 +1647,7 @@ func TestDeleteBlobsInUse(t *testing.T) {
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest, _, err := imgStore.PutImageManifest("repo", tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
manifestDigest, _, err := imgStore.PutImageManifest("repo", tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Try to delete blob currently in use", func() {
|
||||
@@ -1425,7 +1773,7 @@ func TestDeleteBlobsInUse(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -1442,7 +1790,8 @@ func TestDeleteBlobsInUse(t *testing.T) {
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
indexManifestDigest, _, err := imgStore.PutImageManifest(repoName, "index", ispec.MediaTypeImageIndex, indexContent)
|
||||
indexManifestDigest, _, err := imgStore.PutImageManifest(repoName, "index",
|
||||
ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Try to delete manifest being referenced by image index", func() {
|
||||
@@ -1883,7 +2232,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = gc.CleanRepo(ctx, repoName)
|
||||
@@ -1927,7 +2276,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = gc.CleanRepo(ctx, repoName)
|
||||
@@ -2066,7 +2415,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// put artifact referencing above image
|
||||
@@ -2107,7 +2456,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push artifact manifest pointing to artifact above
|
||||
@@ -2123,7 +2472,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
artifactOfArtifactManifestDigest := godigest.FromBytes(artifactManifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactOfArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push orphan artifact (missing subject)
|
||||
@@ -2141,7 +2490,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
// push orphan artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, orphanArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = gc.CleanRepo(ctx, repoName)
|
||||
@@ -2198,7 +2547,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
Convey("Garbage collect - don't gc manifests/blobs which are referenced by another image", func() {
|
||||
// upload same image with another tag
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest(repoName, tag, false)
|
||||
@@ -2317,7 +2666,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
manifestBuf, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo1Name, tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repo1Name, tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// sleep so past GC timeout
|
||||
@@ -2381,7 +2730,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
manifestBuf, err = json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo2Name, tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repo2Name, tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err = imgStore.CheckBlob(repo2Name, bdigest)
|
||||
@@ -2440,7 +2789,7 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(manifestBuf)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repo2Name, tag, ispec.MediaTypeImageManifest, manifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repo2Name, tag, ispec.MediaTypeImageManifest, manifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = gc.CleanRepo(ctx, repo2Name)
|
||||
@@ -2554,7 +2903,7 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest.Subject = &ispec.Descriptor{
|
||||
@@ -2570,7 +2919,7 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing a manifest from index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = gc.CleanRepo(ctx, repoName)
|
||||
@@ -2701,7 +3050,7 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest.Subject = &ispec.Descriptor{
|
||||
@@ -2718,7 +3067,7 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing a manifest from index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactManifestIndexDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestIndexBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestIndexBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push artifact manifest pointing to artifact above
|
||||
@@ -2734,7 +3083,7 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
artifactOfArtifactManifestDigest := godigest.FromBytes(artifactManifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactOfArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push orphan artifact (missing subject)
|
||||
@@ -2752,7 +3101,7 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
|
||||
// push orphan artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, orphanArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err := imgStore.CheckBlob(repoName, bdgst)
|
||||
@@ -3015,7 +3364,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -3051,7 +3400,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
@@ -3098,7 +3447,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
digest := godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
innerIndex.Manifests = append(innerIndex.Manifests, ispec.Descriptor{
|
||||
@@ -3116,7 +3465,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
So(innerIndexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, innerIndexDigest.String(),
|
||||
ispec.MediaTypeImageIndex, innerIndexContent)
|
||||
ispec.MediaTypeImageIndex, innerIndexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// add inner index into root index
|
||||
@@ -3134,7 +3483,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest := ispec.Manifest{
|
||||
@@ -3163,7 +3512,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest.Subject = &ispec.Descriptor{
|
||||
@@ -3180,7 +3529,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing a manifest from index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactManifestIndexDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestIndexBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestIndexBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
artifactManifest.Subject = &ispec.Descriptor{
|
||||
@@ -3197,7 +3546,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push artifact manifest referencing a manifest from index image
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactManifestInnerIndexDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestInnerIndexBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestInnerIndexBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push artifact manifest pointing to artifact above
|
||||
@@ -3214,7 +3563,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
artifactOfArtifactManifestDigest := godigest.FromBytes(artifactManifestBuf)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, artifactOfArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// push orphan artifact (missing subject)
|
||||
@@ -3232,7 +3581,7 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
|
||||
// push orphan artifact manifest
|
||||
_, _, err = imgStore.PutImageManifest(repoName, orphanArtifactManifestDigest.String(),
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf)
|
||||
ispec.MediaTypeImageManifest, artifactManifestBuf, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err := imgStore.CheckBlob(repoName, bdgst)
|
||||
@@ -3404,7 +3753,7 @@ func pushRandomImageIndex(imgStore storageTypes.ImageStore, repoName string,
|
||||
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
@@ -3421,7 +3770,7 @@ func pushRandomImageIndex(imgStore storageTypes.ImageStore, repoName string,
|
||||
indexDigest := godigest.FromBytes(indexContent)
|
||||
So(indexDigest, ShouldNotBeNil)
|
||||
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent)
|
||||
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return bdgst, digest, indexDigest, int64(len(indexContent))
|
||||
|
||||
@@ -35,7 +35,8 @@ type ImageStore interface { //nolint:interfacebloat
|
||||
GetNextRepositories(repo string, maxEntries int, fn FilterRepoFunc) ([]string, bool, error)
|
||||
GetImageTags(repo string) ([]string, error)
|
||||
GetImageManifest(repo, reference string) ([]byte, godigest.Digest, string, error)
|
||||
PutImageManifest(repo, reference, mediaType string, body []byte) (godigest.Digest, godigest.Digest, error)
|
||||
PutImageManifest(repo, reference, mediaType string, body []byte, extraTags []string) (
|
||||
godigest.Digest, godigest.Digest, error)
|
||||
DeleteImageManifest(repo, reference string, detectCollision bool) error
|
||||
BlobUploadPath(repo, uuid string) string
|
||||
StatBlobUpload(repo, uuid string) (bool, int64, time.Time, error)
|
||||
|
||||
@@ -51,7 +51,7 @@ func WriteImageToFileSystem(image Image, repoName, ref string, storeController s
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = store.PutImageManifest(repoName, ref, image.Manifest.MediaType, manifestBlob)
|
||||
_, _, err = store.PutImageManifest(repoName, ref, image.Manifest.MediaType, manifestBlob, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func WriteMultiArchImageToFileSystem(multiarchImage MultiarchImage, repoName, re
|
||||
}
|
||||
|
||||
_, _, err = store.PutImageManifest(repoName, ref, multiarchImage.Index.MediaType,
|
||||
indexBlob)
|
||||
indexBlob, nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestWriteImageToFileSystem(t *testing.T) {
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte,
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string,
|
||||
) (godigest.Digest, godigest.Digest, error) {
|
||||
return "", "", ErrTestError
|
||||
},
|
||||
|
||||
@@ -23,8 +23,8 @@ type MockedImageStore struct {
|
||||
GetNextRepositoriesFn func(lastRepo string, maxEntries int, fn storageTypes.FilterRepoFunc) ([]string, bool, error)
|
||||
GetImageTagsFn func(repo string) ([]string, error)
|
||||
GetImageManifestFn func(repo string, reference string) ([]byte, godigest.Digest, string, error)
|
||||
PutImageManifestFn func(repo string, reference string, mediaType string, body []byte) (godigest.Digest,
|
||||
godigest.Digest, error)
|
||||
PutImageManifestFn func(repo string, reference string, mediaType string, body []byte,
|
||||
extraTags []string) (godigest.Digest, godigest.Digest, error)
|
||||
DeleteImageManifestFn func(repo string, reference string, detectCollision bool) error
|
||||
BlobUploadPathFn func(repo string, uuid string) string
|
||||
StatBlobUploadFn func(repo string, uuid string) (bool, int64, time.Time, error)
|
||||
@@ -163,9 +163,10 @@ func (is MockedImageStore) PutImageManifest(
|
||||
reference string,
|
||||
mediaType string,
|
||||
body []byte,
|
||||
extraTags []string,
|
||||
) (godigest.Digest, godigest.Digest, error) {
|
||||
if is.PutImageManifestFn != nil {
|
||||
return is.PutImageManifestFn(repo, reference, mediaType, body)
|
||||
return is.PutImageManifestFn(repo, reference, mediaType, body, extraTags)
|
||||
}
|
||||
|
||||
return "", "", nil
|
||||
|
||||
+103
-38
@@ -31,7 +31,7 @@ const docTemplate = `{
|
||||
"summary": "Check API support",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok\".",
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -114,13 +114,13 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request\".",
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -157,7 +157,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -200,13 +200,13 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request\".",
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -300,9 +300,6 @@ const docTemplate = `{
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "accepted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"Location": {
|
||||
"type": "string",
|
||||
@@ -364,6 +361,19 @@ const docTemplate = `{
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "no content",
|
||||
"headers": {
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"description": "/v2/{name}/blobs/uploads/{session_id}"
|
||||
},
|
||||
"Range": {
|
||||
"type": "string",
|
||||
"description": "0-128"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -417,8 +427,15 @@ const docTemplate = `{
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "created",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"headers": {
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Digest of the committed blob"
|
||||
},
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"description": "/v2/{name}/blobs/{digest}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
@@ -461,11 +478,8 @@ const docTemplate = `{
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"204": {
|
||||
"description": "no content"
|
||||
},
|
||||
"404": {
|
||||
"description": "not found",
|
||||
@@ -509,10 +523,11 @@ const docTemplate = `{
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "accepted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"Blob-Upload-UUID": {
|
||||
"type": "string",
|
||||
"description": "Opaque blob upload session identifier"
|
||||
},
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"description": "/v2/{name}/blobs/uploads/{session_id}"
|
||||
@@ -612,10 +627,7 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "accepted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"description": "accepted"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -651,8 +663,9 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/api.ImageManifest"
|
||||
},
|
||||
"headers": {
|
||||
"constants.DistContentDigestKey": {
|
||||
"type": "object"
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the content"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -692,8 +705,9 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/api.ImageManifest"
|
||||
},
|
||||
"headers": {
|
||||
"constants.DistContentDigestKey": {
|
||||
"type": "object"
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the content"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -712,7 +726,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update an image's manifest given a reference or a digest",
|
||||
"description": "Update an image's manifest given a reference or a digest. On digest pushes with ` + "`" + `tag=` + "`" + ` query\nparameters, 201 responses repeat the ` + "`" + `OCI-Tag` + "`" + ` header once per tag value.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -734,13 +748,30 @@ const docTemplate = `{
|
||||
"name": "reference",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "additional tag(s) for digest pushes",
|
||||
"name": "tag",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "created",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"headers": {
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the uploaded content"
|
||||
},
|
||||
"OCI-Tag": {
|
||||
"type": "string",
|
||||
"description": "Echoed tag= value; this header is repeatable (one field per tag= query parameter)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
@@ -755,6 +786,12 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"414": {
|
||||
"description": "too many tag query parameters",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
@@ -789,8 +826,35 @@ const docTemplate = `{
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"202": {
|
||||
"description": "accepted"
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "not found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "method not allowed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "conflict",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -829,8 +893,9 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"constants.DistContentDigestKey": {
|
||||
"type": "object"
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the content"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -841,7 +906,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -944,7 +1009,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request\".",
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -1094,13 +1159,13 @@ const docTemplate = `{
|
||||
"summary": "Logout by removing current session",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok\".",
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
+103
-38
@@ -23,7 +23,7 @@
|
||||
"summary": "Check API support",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok\".",
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -106,13 +106,13 @@
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request\".",
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -149,7 +149,7 @@
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -192,13 +192,13 @@
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request\".",
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -292,9 +292,6 @@
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "accepted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"Location": {
|
||||
"type": "string",
|
||||
@@ -356,6 +353,19 @@
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "no content",
|
||||
"headers": {
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"description": "/v2/{name}/blobs/uploads/{session_id}"
|
||||
},
|
||||
"Range": {
|
||||
"type": "string",
|
||||
"description": "0-128"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -409,8 +419,15 @@
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "created",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"headers": {
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Digest of the committed blob"
|
||||
},
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"description": "/v2/{name}/blobs/{digest}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
@@ -453,11 +470,8 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"204": {
|
||||
"description": "no content"
|
||||
},
|
||||
"404": {
|
||||
"description": "not found",
|
||||
@@ -501,10 +515,11 @@
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "accepted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"Blob-Upload-UUID": {
|
||||
"type": "string",
|
||||
"description": "Opaque blob upload session identifier"
|
||||
},
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"description": "/v2/{name}/blobs/uploads/{session_id}"
|
||||
@@ -604,10 +619,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "accepted",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"description": "accepted"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -643,8 +655,9 @@
|
||||
"$ref": "#/definitions/api.ImageManifest"
|
||||
},
|
||||
"headers": {
|
||||
"constants.DistContentDigestKey": {
|
||||
"type": "object"
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the content"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -684,8 +697,9 @@
|
||||
"$ref": "#/definitions/api.ImageManifest"
|
||||
},
|
||||
"headers": {
|
||||
"constants.DistContentDigestKey": {
|
||||
"type": "object"
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the content"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -704,7 +718,7 @@
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Update an image's manifest given a reference or a digest",
|
||||
"description": "Update an image's manifest given a reference or a digest. On digest pushes with `tag=` query\nparameters, 201 responses repeat the `OCI-Tag` header once per tag value.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -726,13 +740,30 @@
|
||||
"name": "reference",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi",
|
||||
"description": "additional tag(s) for digest pushes",
|
||||
"name": "tag",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "created",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"headers": {
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the uploaded content"
|
||||
},
|
||||
"OCI-Tag": {
|
||||
"type": "string",
|
||||
"description": "Echoed tag= value; this header is repeatable (one field per tag= query parameter)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
@@ -747,6 +778,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"414": {
|
||||
"description": "too many tag query parameters",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
@@ -781,8 +818,35 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"202": {
|
||||
"description": "accepted"
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "not found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "method not allowed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "conflict",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -821,8 +885,9 @@
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"constants.DistContentDigestKey": {
|
||||
"type": "object"
|
||||
"Docker-Content-Digest": {
|
||||
"type": "string",
|
||||
"description": "Manifest digest of the content"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -833,7 +898,7 @@
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -936,7 +1001,7 @@
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "bad request\".",
|
||||
"description": "bad request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -1086,13 +1151,13 @@
|
||||
"summary": "Logout by removing current session",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok\".",
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error\".",
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
+82
-33
@@ -256,7 +256,7 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok".
|
||||
description: ok
|
||||
schema:
|
||||
type: string
|
||||
summary: Check API support
|
||||
@@ -310,11 +310,11 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: bad request".
|
||||
description: bad request
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error".
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Upload cosign public keys for verifying signatures
|
||||
@@ -338,7 +338,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/extensions.StrippedConfig'
|
||||
"500":
|
||||
description: internal server error".
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Get current server configuration
|
||||
@@ -366,11 +366,11 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: bad request".
|
||||
description: bad request
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error".
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Upload notation certificates for verifying signatures
|
||||
@@ -438,8 +438,6 @@ paths:
|
||||
responses:
|
||||
"202":
|
||||
description: accepted
|
||||
schema:
|
||||
type: string
|
||||
summary: Delete image blob/layer
|
||||
get:
|
||||
consumes:
|
||||
@@ -485,8 +483,9 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
headers:
|
||||
constants.DistContentDigestKey:
|
||||
type: object
|
||||
Docker-Content-Digest:
|
||||
description: Manifest digest of the content
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/api.ImageManifest'
|
||||
summary: Check image blob/layer
|
||||
@@ -513,8 +512,6 @@ paths:
|
||||
Range:
|
||||
description: 0-0
|
||||
type: string
|
||||
schema:
|
||||
type: string
|
||||
"401":
|
||||
description: unauthorized
|
||||
schema:
|
||||
@@ -547,10 +544,8 @@ paths:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok
|
||||
schema:
|
||||
type: string
|
||||
"204":
|
||||
description: no content
|
||||
"404":
|
||||
description: not found
|
||||
schema:
|
||||
@@ -580,6 +575,15 @@ paths:
|
||||
responses:
|
||||
"204":
|
||||
description: no content
|
||||
headers:
|
||||
Location:
|
||||
description: /v2/{name}/blobs/uploads/{session_id}
|
||||
type: string
|
||||
Range:
|
||||
description: 0-128
|
||||
type: string
|
||||
"400":
|
||||
description: bad request
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
@@ -612,14 +616,15 @@ paths:
|
||||
"202":
|
||||
description: accepted
|
||||
headers:
|
||||
Blob-Upload-UUID:
|
||||
description: Opaque blob upload session identifier
|
||||
type: string
|
||||
Location:
|
||||
description: /v2/{name}/blobs/uploads/{session_id}
|
||||
type: string
|
||||
Range:
|
||||
description: 0-128
|
||||
type: string
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: bad request
|
||||
schema:
|
||||
@@ -662,8 +667,13 @@ paths:
|
||||
responses:
|
||||
"201":
|
||||
description: created
|
||||
schema:
|
||||
type: string
|
||||
headers:
|
||||
Docker-Content-Digest:
|
||||
description: Digest of the committed blob
|
||||
type: string
|
||||
Location:
|
||||
description: /v2/{name}/blobs/{digest}
|
||||
type: string
|
||||
"404":
|
||||
description: not found
|
||||
schema:
|
||||
@@ -692,8 +702,26 @@ paths:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok
|
||||
"202":
|
||||
description: accepted
|
||||
"400":
|
||||
description: bad request
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: not found
|
||||
schema:
|
||||
type: string
|
||||
"405":
|
||||
description: method not allowed
|
||||
schema:
|
||||
type: string
|
||||
"409":
|
||||
description: conflict
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Delete image manifest
|
||||
@@ -718,8 +746,9 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
headers:
|
||||
constants.DistContentDigestKey:
|
||||
type: object
|
||||
Docker-Content-Digest:
|
||||
description: Manifest digest of the content
|
||||
type: string
|
||||
schema:
|
||||
$ref: '#/definitions/api.ImageManifest'
|
||||
"404":
|
||||
@@ -752,8 +781,9 @@ paths:
|
||||
"200":
|
||||
description: ok
|
||||
headers:
|
||||
constants.DistContentDigestKey:
|
||||
type: object
|
||||
Docker-Content-Digest:
|
||||
description: Manifest digest of the content
|
||||
type: string
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
@@ -761,14 +791,16 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error".
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Check image manifest
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update an image's manifest given a reference or a digest
|
||||
description: |-
|
||||
Update an image's manifest given a reference or a digest. On digest pushes with `tag=` query
|
||||
parameters, 201 responses repeat the `OCI-Tag` header once per tag value.
|
||||
parameters:
|
||||
- description: repository name
|
||||
in: path
|
||||
@@ -780,13 +812,26 @@ paths:
|
||||
name: reference
|
||||
required: true
|
||||
type: string
|
||||
- collectionFormat: multi
|
||||
description: additional tag(s) for digest pushes
|
||||
in: query
|
||||
items:
|
||||
type: string
|
||||
name: tag
|
||||
type: array
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: created
|
||||
schema:
|
||||
type: string
|
||||
headers:
|
||||
Docker-Content-Digest:
|
||||
description: Manifest digest of the uploaded content
|
||||
type: string
|
||||
OCI-Tag:
|
||||
description: Echoed tag= value; this header is repeatable (one field
|
||||
per tag= query parameter)
|
||||
type: string
|
||||
"400":
|
||||
description: bad request
|
||||
schema:
|
||||
@@ -795,6 +840,10 @@ paths:
|
||||
description: not found
|
||||
schema:
|
||||
type: string
|
||||
"414":
|
||||
description: too many tag query parameters
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
@@ -865,7 +914,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/common.ImageTags'
|
||||
"400":
|
||||
description: bad request".
|
||||
description: bad request
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
@@ -965,11 +1014,11 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok".
|
||||
description: ok
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error".
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Logout by removing current session
|
||||
|
||||
Reference in New Issue
Block a user