mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
feat(artifact): add OCI references support (#936)
Thanks @jdolitsky et al for kicking off these changes at: https://github.com/oci-playground/zot/commits/main Thanks @sudo-bmitch for reviewing the patch Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
This commit is contained in:
parent
eb722905cb
commit
c0f93caacb
26 changed files with 865 additions and 118 deletions
3
Makefile
3
Makefile
|
@ -16,6 +16,7 @@ COSIGN := $(TOOLSDIR)/bin/cosign
|
|||
HELM := $(TOOLSDIR)/bin/helm
|
||||
ORAS := $(TOOLSDIR)/bin/oras
|
||||
REGCLIENT := $(TOOLSDIR)/bin/regctl
|
||||
REGCLIENT_VERSION := v0.4.5
|
||||
STACKER := $(TOOLSDIR)/bin/stacker
|
||||
BATS := $(TOOLSDIR)/bin/bats
|
||||
TESTDATA := $(TOP_LEVEL)/test/data
|
||||
|
@ -121,7 +122,7 @@ $(HELM):
|
|||
|
||||
$(REGCLIENT):
|
||||
mkdir -p $(TOOLSDIR)/bin
|
||||
curl -Lo regctl https://github.com/regclient/regclient/releases/download/v0.4.4/regctl-linux-amd64
|
||||
curl -Lo regctl https://github.com/regclient/regclient/releases/download/$(REGCLIENT_VERSION)/regctl-linux-amd64
|
||||
cp regctl $(TOOLSDIR)/bin/regctl
|
||||
chmod +x $(TOOLSDIR)/bin/regctl
|
||||
|
||||
|
|
|
@ -16,13 +16,14 @@ The following document refers on the **core dist-spec**, see also the [zot-speci
|
|||
## [**Why zot?**](COMPARISON.md)
|
||||
|
||||
## What's new?
|
||||
* Support content range for pull requests
|
||||
* Supports push/pull OCI and ORAS Artifacts
|
||||
* Supports OCI references
|
||||
* Supports content range for pull requests
|
||||
* Selectively add extensions on top of minimal build
|
||||
* Supports container image signatures - [cosign](https://github.com/sigstore/cosign) and [notation](https://github.com/notaryproject/notation)
|
||||
* Multi-arch support
|
||||
* Clustering support
|
||||
* Image linting support
|
||||
* Supports push/pull OCI Artifacts
|
||||
|
||||
## [Demos](demos/README.md)
|
||||
|
||||
|
@ -290,7 +291,7 @@ Supports:
|
|||
|
||||
You can benchmark a zot registry or any other dist-spec conformant registry with `zb`.
|
||||
|
||||
## Building `zb``
|
||||
## Building `zb`
|
||||
|
||||
```console
|
||||
$ make bench
|
||||
|
|
3
go.mod
3
go.mod
|
@ -29,7 +29,7 @@ require (
|
|||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8
|
||||
github.com/opencontainers/umoci v0.4.8-0.20210922062158-e60a0cc726e6
|
||||
github.com/oras-project/artifacts-spec v1.0.0-rc.2
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||
|
@ -413,7 +413,6 @@ replace (
|
|||
github.com/containers/image/v5 => github.com/anuvu/image/v5 v5.0.0-20220520105616-e594853d6471
|
||||
github.com/hashicorp/go-getter => github.com/hashicorp/go-getter v1.6.1
|
||||
github.com/open-policy-agent/opa => github.com/open-policy-agent/opa v0.44.0
|
||||
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2
|
||||
go.etcd.io/etcd/v3 => go.etcd.io/etcd/v3 v3.5.4
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.26.1
|
||||
|
|
10
go.sum
10
go.sum
|
@ -1839,8 +1839,15 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ
|
|||
github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 h1:q37d91F6BO4Jp1UqWiun0dUFYaqv6WsKTLTCaWv+8LY=
|
||||
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84/go.mod h1:Qnt1q4cjDNQI9bT832ziho5Iw2BhK8o1KwLOwW56VP4=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8 h1:l9vfzobI7tZtG164u1Jf6NqDErHZoqAw8rlvBYQJpVI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw=
|
||||
github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
|
@ -2030,6 +2037,7 @@ github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
|||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
|
|
@ -4060,7 +4060,7 @@ func TestImageSignatures(t *testing.T) {
|
|||
resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
|
||||
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
|
||||
out, err = cmd.CombinedOutput()
|
||||
So(err, ShouldNotBeNil)
|
||||
|
@ -4084,7 +4084,7 @@ func TestImageSignatures(t *testing.T) {
|
|||
resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
|
||||
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
|
||||
out, err = cmd.CombinedOutput()
|
||||
So(err, ShouldNotBeNil)
|
||||
|
@ -4093,7 +4093,7 @@ func TestImageSignatures(t *testing.T) {
|
|||
})
|
||||
})
|
||||
|
||||
Convey("GetReferrers", func() {
|
||||
Convey("GetOrasReferrers", func() {
|
||||
// cover error paths
|
||||
resp, err := resty.R().Get(
|
||||
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, "badRepo", "badDigest"))
|
||||
|
@ -4123,6 +4123,301 @@ func TestImageSignatures(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestArtifactReferences(t *testing.T) {
|
||||
Convey("Validate Artifact References", t, func() {
|
||||
// start a new server
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
dir := t.TempDir()
|
||||
ctlr.Config.Storage.RootDirectory = dir
|
||||
go func(controller *api.Controller) {
|
||||
// this blocks
|
||||
if err := controller.Run(context.Background()); err != nil {
|
||||
return
|
||||
}
|
||||
}(ctlr)
|
||||
// wait till ready
|
||||
for {
|
||||
_, err := resty.R().Get(baseURL)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
defer func(controller *api.Controller) {
|
||||
ctx := context.Background()
|
||||
_ = controller.Server.Shutdown(ctx)
|
||||
}(ctlr)
|
||||
|
||||
repoName := "artifact-repo"
|
||||
|
||||
// create a blob/layer
|
||||
resp, err := resty.R().Post(baseURL + fmt.Sprintf("/v2/%s/blobs/uploads/", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||
loc := test.Location(baseURL, resp)
|
||||
So(loc, ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Get(loc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 204)
|
||||
content := []byte("this is a blob")
|
||||
digest := godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
// monolithic blob upload: success
|
||||
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
||||
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
blobLoc := resp.Header().Get("Location")
|
||||
So(blobLoc, ShouldNotBeEmpty)
|
||||
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
||||
So(resp.Header().Get(constants.DistContentDigestKey), ShouldNotBeEmpty)
|
||||
|
||||
// upload image config blob
|
||||
resp, err = resty.R().Post(baseURL + fmt.Sprintf("/v2/%s/blobs/uploads/", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||
loc = test.Location(baseURL, resp)
|
||||
cblob, cdigest := test.GetRandomImageConfig()
|
||||
|
||||
resp, err = resty.R().
|
||||
SetContentLength(true).
|
||||
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
|
||||
SetHeader("Content-Type", "application/octet-stream").
|
||||
SetQueryParam("digest", cdigest.String()).
|
||||
SetBody(cblob).
|
||||
Put(loc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
// create a manifest
|
||||
manifest := ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
},
|
||||
}
|
||||
manifest.SchemaVersion = 2
|
||||
content, err = json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/1.0", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
d := resp.Header().Get(constants.DistContentDigestKey)
|
||||
So(d, ShouldNotBeEmpty)
|
||||
So(d, ShouldEqual, digest.String())
|
||||
|
||||
artifactType := "application/vnd.example.icecream.v1"
|
||||
|
||||
Convey("Validate Image Manifest Reference", func() {
|
||||
resp, err = resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
// now upload a reference
|
||||
|
||||
// upload image config blob
|
||||
resp, err = resty.R().Post(baseURL + fmt.Sprintf("/v2/%s/blobs/uploads/", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||
loc = test.Location(baseURL, resp)
|
||||
cblob, cdigest := test.GetEmptyImageConfig()
|
||||
|
||||
resp, err = resty.R().
|
||||
SetContentLength(true).
|
||||
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
|
||||
SetHeader("Content-Type", "application/octet-stream").
|
||||
SetQueryParam("digest", cdigest.String()).
|
||||
SetBody(cblob).
|
||||
Put(loc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
// create a manifest
|
||||
manifest := ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: artifactType,
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
},
|
||||
Subject: &ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"key": "val",
|
||||
},
|
||||
}
|
||||
manifest.SchemaVersion = 2
|
||||
|
||||
Convey("Using invalid content", func() {
|
||||
resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeImageManifest).
|
||||
SetBody([]byte("invalid data")).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/1.0", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifactType": artifactType}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
})
|
||||
Convey("Using valid content", func() {
|
||||
content, err = json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeImageManifest).
|
||||
SetBody(content).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/1.0", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
resp, err = resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifact": "invalid"}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifactType": "invalid"}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifactType": artifactType}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get("Content-Type"), ShouldEqual, ispec.MediaTypeImageIndex)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Validate Artifact Manifest Reference", func() {
|
||||
resp, err = resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
// now upload a reference
|
||||
|
||||
// upload image config blob
|
||||
resp, err = resty.R().Post(baseURL + fmt.Sprintf("/v2/%s/blobs/uploads/", repoName))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
|
||||
loc = test.Location(baseURL, resp)
|
||||
cblob, cdigest := test.GetEmptyImageConfig()
|
||||
|
||||
resp, err = resty.R().
|
||||
SetContentLength(true).
|
||||
SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))).
|
||||
SetHeader("Content-Type", "application/octet-stream").
|
||||
SetQueryParam("digest", cdigest.String()).
|
||||
SetBody(cblob).
|
||||
Put(loc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
// create a artifact
|
||||
manifest := ispec.Artifact{
|
||||
MediaType: ispec.MediaTypeArtifactManifest,
|
||||
ArtifactType: artifactType,
|
||||
Blobs: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
},
|
||||
Subject: &ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"key": "val",
|
||||
},
|
||||
}
|
||||
Convey("Using invalid content", func() {
|
||||
content := []byte("invalid data")
|
||||
So(err, ShouldBeNil)
|
||||
mdigest := godigest.FromBytes(content)
|
||||
So(mdigest, ShouldNotBeNil)
|
||||
resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeArtifactManifest).
|
||||
SetBody(content).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/%s", repoName, mdigest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifactType": artifactType}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
})
|
||||
Convey("Using valid content", func() {
|
||||
content, err = json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
mdigest := godigest.FromBytes(content)
|
||||
So(mdigest, ShouldNotBeNil)
|
||||
resp, err = resty.R().SetHeader("Content-Type", ispec.MediaTypeArtifactManifest).
|
||||
SetBody(content).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/%s", repoName, mdigest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
resp, err = resty.R().Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifact": "invalid"}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifactType": "invalid"}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
resp, err = resty.R().SetQueryParams(map[string]string{"artifactType": artifactType}).
|
||||
Get(baseURL + fmt.Sprintf("/v2/%s/referrers/%s", repoName, digest.String()))
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get("Content-Type"), ShouldEqual, ispec.MediaTypeImageIndex)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:dupl // duplicated test code
|
||||
func TestRouteFailures(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
|
@ -4685,7 +4980,7 @@ func TestRouteFailures(t *testing.T) {
|
|||
request = mux.SetURLVars(request, map[string]string{})
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
rthdlr.GetReferrers(response, request)
|
||||
rthdlr.GetOrasReferrers(response, request)
|
||||
|
||||
resp := response.Result()
|
||||
defer resp.Body.Close()
|
||||
|
@ -4696,7 +4991,7 @@ func TestRouteFailures(t *testing.T) {
|
|||
request = mux.SetURLVars(request, map[string]string{"name": "foo"})
|
||||
response = httptest.NewRecorder()
|
||||
|
||||
rthdlr.GetReferrers(response, request)
|
||||
rthdlr.GetOrasReferrers(response, request)
|
||||
|
||||
resp = response.Result()
|
||||
defer resp.Body.Close()
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -96,6 +98,9 @@ func (rh *RouteHandler) SetupRoutes() {
|
|||
rh.UpdateBlobUpload).Methods("PUT")
|
||||
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
|
||||
rh.DeleteBlobUpload).Methods("DELETE")
|
||||
// support for OCI artifact references
|
||||
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/referrers/{digest}", NameRegexp.String()),
|
||||
rh.GetReferrers).Methods(allowedMethods("GET")...)
|
||||
prefixedRouter.HandleFunc(constants.ExtCatalogPrefix,
|
||||
rh.ListRepositories).Methods(allowedMethods("GET")...)
|
||||
prefixedRouter.HandleFunc(constants.ExtOciDiscoverPrefix,
|
||||
|
@ -104,9 +109,9 @@ func (rh *RouteHandler) SetupRoutes() {
|
|||
rh.CheckVersionSupport).Methods(allowedMethods("GET")...)
|
||||
}
|
||||
|
||||
// support for oras artifact reference types (alpha 1) - image signature use case
|
||||
// support for ORAS artifact reference types (alpha 1) - image signature use case
|
||||
rh.c.Router.HandleFunc(fmt.Sprintf("%s/{name:%s}/manifests/{digest}/referrers",
|
||||
constants.ArtifactSpecRoutePrefix, NameRegexp.String()), rh.GetReferrers).Methods("GET")
|
||||
constants.ArtifactSpecRoutePrefix, NameRegexp.String()), rh.GetOrasReferrers).Methods("GET")
|
||||
|
||||
// swagger
|
||||
debug.SetupSwaggerRoutes(rh.c.Config, rh.c.Router, rh.c.Log)
|
||||
|
@ -310,7 +315,8 @@ func (rh *RouteHandler) CheckManifest(response http.ResponseWriter, request *htt
|
|||
return
|
||||
}
|
||||
|
||||
content, digest, mediaType, err := getImageManifest(rh, imgStore, name, reference) //nolint:contextcheck
|
||||
content, digest, mediaType, err := getImageManifest(request.Context(), rh, imgStore,
|
||||
name, reference) //nolint:contextcheck
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
||||
WriteJSON(response, http.StatusNotFound,
|
||||
|
@ -375,7 +381,8 @@ func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.
|
|||
return
|
||||
}
|
||||
|
||||
content, digest, mediaType, err := getImageManifest(rh, imgStore, name, reference) //nolint: contextcheck
|
||||
content, digest, mediaType, err := getImageManifest(request.Context(), rh,
|
||||
imgStore, name, reference) //nolint: contextcheck
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
||||
WriteJSON(response, http.StatusNotFound,
|
||||
|
@ -398,6 +405,117 @@ func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.
|
|||
WriteData(response, http.StatusOK, mediaType, content)
|
||||
}
|
||||
|
||||
type ImageIndex struct {
|
||||
ispec.Index
|
||||
}
|
||||
|
||||
func getReferrers(ctx context.Context, routeHandler *RouteHandler,
|
||||
imgStore storage.ImageStore, name string, digest godigest.Digest,
|
||||
artifactType string,
|
||||
) (ispec.Index, error) {
|
||||
// first get the subject and then all its referrers
|
||||
references, err := imgStore.GetReferrers(name, digest, artifactType)
|
||||
if err != nil {
|
||||
if routeHandler.c.Config.Extensions != nil &&
|
||||
routeHandler.c.Config.Extensions.Sync != nil &&
|
||||
*routeHandler.c.Config.Extensions.Sync.Enable {
|
||||
routeHandler.c.Log.Info().Msgf("referrers not found, trying to get referrers to %s:%s by syncing on demand",
|
||||
name, digest)
|
||||
|
||||
errSync := ext.SyncOneImage(ctx, routeHandler.c.Config, routeHandler.c.StoreController,
|
||||
name, digest.String(), false, routeHandler.c.Log)
|
||||
if errSync != nil {
|
||||
routeHandler.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to get references")
|
||||
|
||||
return ispec.Index{}, err
|
||||
}
|
||||
|
||||
for _, ref := range references.Manifests {
|
||||
errSync := ext.SyncOneImage(ctx, routeHandler.c.Config, routeHandler.c.StoreController,
|
||||
name, ref.Digest.String(), false, routeHandler.c.Log)
|
||||
if errSync != nil {
|
||||
routeHandler.c.Log.Error().Err(err).Str("name", name).
|
||||
Str("digest", ref.Digest.String()).Msg("unable to get references")
|
||||
|
||||
return ispec.Index{}, err
|
||||
}
|
||||
}
|
||||
|
||||
references, err = imgStore.GetReferrers(name, digest, artifactType)
|
||||
}
|
||||
}
|
||||
|
||||
return references, err
|
||||
}
|
||||
|
||||
// GetReferrers godoc
|
||||
// @Summary Get references for a given digest
|
||||
// @Description Get references given a digest
|
||||
// @Accept json
|
||||
// @Produce application/vnd.oci.image.index.v1+json
|
||||
// @Param name path string true "repository name"
|
||||
// @Param digest path string true "digest"
|
||||
// @Success 200 {object} api.ImageIndex
|
||||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/{name}/references/{digest} [get].
|
||||
func (rh *RouteHandler) GetReferrers(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
|
||||
name, ok := vars["name"]
|
||||
if !ok || name == "" {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
digestStr, ok := vars["digest"]
|
||||
digest, err := godigest.Parse(digestStr)
|
||||
|
||||
if !ok || digestStr == "" || err != nil {
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
artifactType := ""
|
||||
|
||||
artifactTypes, ok := request.URL.Query()["artifactType"]
|
||||
if ok {
|
||||
if len(artifactTypes) != 1 {
|
||||
rh.c.Log.Error().Msg("invalid artifact types")
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
artifactType = artifactTypes[0]
|
||||
}
|
||||
|
||||
rh.c.Log.Info().Str("digest", digest.String()).Str("artifactType", artifactType).Msg("getting manifest")
|
||||
|
||||
imgStore := rh.getImageStore(name)
|
||||
|
||||
referrers, err := getReferrers(request.Context(), rh, imgStore, name, digest, artifactType)
|
||||
if err != nil {
|
||||
rh.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to get references")
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
out, err := json.Marshal(referrers)
|
||||
if err != nil {
|
||||
rh.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to marshal json")
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
WriteData(response, http.StatusOK, ispec.MediaTypeImageIndex, out)
|
||||
}
|
||||
|
||||
// UpdateManifest godoc
|
||||
// @Summary Update image manifest
|
||||
// @Description Update an image's manifest given a reference or a digest
|
||||
|
@ -1458,7 +1576,7 @@ func (rh *RouteHandler) getImageStore(name string) storage.ImageStore {
|
|||
}
|
||||
|
||||
// will sync on demand if an image is not found, in case sync extensions is enabled.
|
||||
func getImageManifest(routeHandler *RouteHandler, imgStore storage.ImageStore, name,
|
||||
func getImageManifest(ctx context.Context, routeHandler *RouteHandler, imgStore storage.ImageStore, name,
|
||||
reference string,
|
||||
) ([]byte, godigest.Digest, string, error) {
|
||||
content, digest, mediaType, err := imgStore.GetImageManifest(name, reference)
|
||||
|
@ -1470,7 +1588,7 @@ func getImageManifest(routeHandler *RouteHandler, imgStore storage.ImageStore, n
|
|||
routeHandler.c.Log.Info().Msgf("image not found, trying to get image %s:%s by syncing on demand",
|
||||
name, reference)
|
||||
|
||||
errSync := ext.SyncOneImage(routeHandler.c.Config, routeHandler.c.StoreController,
|
||||
errSync := ext.SyncOneImage(ctx, routeHandler.c.Config, routeHandler.c.StoreController,
|
||||
name, reference, false, routeHandler.c.Log)
|
||||
if errSync != nil {
|
||||
routeHandler.c.Log.Err(errSync).Msgf("error encounter while syncing image %s:%s",
|
||||
|
@ -1488,10 +1606,11 @@ func getImageManifest(routeHandler *RouteHandler, imgStore storage.ImageStore, n
|
|||
}
|
||||
|
||||
// will sync referrers on demand if they are not found, in case sync extensions is enabled.
|
||||
func getReferrers(routeHandler *RouteHandler, imgStore storage.ImageStore, name string, digest godigest.Digest,
|
||||
func getOrasReferrers(ctx context.Context, routeHandler *RouteHandler,
|
||||
imgStore storage.ImageStore, name string, digest godigest.Digest,
|
||||
artifactType string,
|
||||
) ([]artifactspec.Descriptor, error) {
|
||||
refs, err := imgStore.GetReferrers(name, digest, artifactType)
|
||||
refs, err := imgStore.GetOrasReferrers(name, digest, artifactType)
|
||||
if err != nil {
|
||||
if routeHandler.c.Config.Extensions != nil &&
|
||||
routeHandler.c.Config.Extensions.Sync != nil &&
|
||||
|
@ -1499,7 +1618,7 @@ func getReferrers(routeHandler *RouteHandler, imgStore storage.ImageStore, name
|
|||
routeHandler.c.Log.Info().Msgf("signature not found, trying to get signature %s:%s by syncing on demand",
|
||||
name, digest.String())
|
||||
|
||||
errSync := ext.SyncOneImage(routeHandler.c.Config, routeHandler.c.StoreController,
|
||||
errSync := ext.SyncOneImage(ctx, routeHandler.c.Config, routeHandler.c.StoreController,
|
||||
name, digest.String(), true, routeHandler.c.Log)
|
||||
if errSync != nil {
|
||||
routeHandler.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to get references")
|
||||
|
@ -1507,7 +1626,7 @@ func getReferrers(routeHandler *RouteHandler, imgStore storage.ImageStore, name
|
|||
return []artifactspec.Descriptor{}, err
|
||||
}
|
||||
|
||||
refs, err = imgStore.GetReferrers(name, digest, artifactType)
|
||||
refs, err = imgStore.GetOrasReferrers(name, digest, artifactType)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1518,7 +1637,7 @@ type ReferenceList struct {
|
|||
References []artifactspec.Descriptor `json:"references"`
|
||||
}
|
||||
|
||||
// GetReferrers godoc
|
||||
// GetOrasReferrers godoc
|
||||
// @Summary Get references for an image
|
||||
// @Description Get references for an image given a digest and artifact type
|
||||
// @Accept json
|
||||
|
@ -1530,7 +1649,7 @@ type ReferenceList struct {
|
|||
// @Failure 404 {string} string "not found"
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /oras/artifacts/v1/{name:%s}/manifests/{digest}/referrers [get].
|
||||
func (rh *RouteHandler) GetReferrers(response http.ResponseWriter, request *http.Request) {
|
||||
func (rh *RouteHandler) GetOrasReferrers(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
name, ok := vars["name"]
|
||||
|
||||
|
@ -1549,16 +1668,21 @@ func (rh *RouteHandler) GetReferrers(response http.ResponseWriter, request *http
|
|||
return
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
artifactType := ""
|
||||
|
||||
artifactTypes, ok := request.URL.Query()["artifactType"]
|
||||
if !ok || len(artifactTypes) != 1 {
|
||||
rh.c.Log.Error().Msg("invalid artifact types")
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
if ok {
|
||||
if len(artifactTypes) != 1 {
|
||||
rh.c.Log.Error().Msg("invalid artifact types")
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
artifactType = artifactTypes[0]
|
||||
}
|
||||
|
||||
artifactType := artifactTypes[0]
|
||||
|
||||
if artifactType != notreg.ArtifactTypeNotation {
|
||||
rh.c.Log.Error().Str("artifactType", artifactType).Msg("invalid artifact type")
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
@ -1570,10 +1694,10 @@ func (rh *RouteHandler) GetReferrers(response http.ResponseWriter, request *http
|
|||
|
||||
rh.c.Log.Info().Str("digest", digest.String()).Str("artifactType", artifactType).Msg("getting manifest")
|
||||
|
||||
refs, err := getReferrers(rh, imgStore, name, digest, artifactType) //nolint:contextcheck
|
||||
refs, err := getOrasReferrers(request.Context(), rh, imgStore, name, digest, artifactType) //nolint:contextcheck
|
||||
if err != nil {
|
||||
rh.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to get references")
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ import (
|
|||
func SetupGQLPlaygroundRoutes(conf *config.Config, router *mux.Router,
|
||||
storeController storage.StoreController, log log.Logger,
|
||||
) {
|
||||
log.Warn().Msg("skipping enabling graphql playground extension because given zot binary" +
|
||||
log.Warn().Msg("skipping enabling graphql playground extension because given zot binary " +
|
||||
"doesn't include this feature, please build a binary that does so")
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ import (
|
|||
func SetupSwaggerRoutes(conf *config.Config, router *mux.Router, log log.Logger,
|
||||
) {
|
||||
// swagger swagger "/swagger/v2/index.html"
|
||||
log.Warn().Msg("skipping enabling swagger because given zot binary" +
|
||||
log.Warn().Msg("skipping enabling swagger because given zot binary " +
|
||||
"doesn't include this feature, please build a binary that does so")
|
||||
}
|
||||
|
|
|
@ -25,12 +25,12 @@ func EnableSyncExtension(ctx context.Context, config *config.Config, wg *goSync.
|
|||
}
|
||||
}
|
||||
|
||||
func SyncOneImage(config *config.Config, storeController storage.StoreController,
|
||||
func SyncOneImage(ctx context.Context, config *config.Config, storeController storage.StoreController,
|
||||
repoName, reference string, isArtifact bool, log log.Logger,
|
||||
) error {
|
||||
log.Info().Msgf("syncing image %s:%s", repoName, reference)
|
||||
|
||||
err := sync.OneImage(*config.Extensions.Sync, storeController, repoName, reference, isArtifact, log)
|
||||
err := sync.OneImage(ctx, *config.Extensions.Sync, storeController, repoName, reference, isArtifact, log)
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ func EnableSyncExtension(ctx context.Context,
|
|||
}
|
||||
|
||||
// SyncOneImage ...
|
||||
func SyncOneImage(config *config.Config, storeController storage.StoreController,
|
||||
func SyncOneImage(ctx context.Context, config *config.Config, storeController storage.StoreController,
|
||||
repoName, reference string, isArtifact bool, log log.Logger,
|
||||
) error {
|
||||
log.Warn().Msg("skipping syncing on demand because given zot binary doesn't include this feature," +
|
||||
|
|
|
@ -1101,8 +1101,10 @@ func TestDerivedImageList(t *testing.T) {
|
|||
Convey("Test dependency list for image working", t, func() {
|
||||
// create test images
|
||||
config := ispec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
@ -1516,8 +1518,10 @@ func TestBaseImageList(t *testing.T) {
|
|||
Convey("Test base image list for image working", t, func() {
|
||||
// create test images
|
||||
config := ispec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
@ -2502,8 +2506,10 @@ func TestImageList(t *testing.T) {
|
|||
WaitTillServerReady(baseURL)
|
||||
|
||||
config := ispec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
@ -2619,8 +2625,10 @@ func TestBuildImageInfo(t *testing.T) {
|
|||
}
|
||||
|
||||
config := ispec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
|
|
@ -201,7 +201,7 @@ func (olu BaseOciLayoutUtils) checkNotarySignature(name string, digest godigest.
|
|||
imageStore := olu.StoreController.GetImageStore(name)
|
||||
mediaType := notreg.ArtifactTypeNotation
|
||||
|
||||
_, err := imageStore.GetReferrers(name, digest, mediaType)
|
||||
_, err := imageStore.GetOrasReferrers(name, digest, mediaType)
|
||||
if err != nil {
|
||||
olu.Log.Info().Err(err).Str("repo", name).Str("digest",
|
||||
digest.String()).Str("mediatype", mediaType).Msg("invalid notary signature")
|
||||
|
|
|
@ -322,8 +322,10 @@ func TestExtractImageDetails(t *testing.T) {
|
|||
testLogger := log.NewLogger("debug", "")
|
||||
layerDigest := godigest.FromBytes(content)
|
||||
config := ispec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
|
|
@ -50,7 +50,7 @@ func (di *demandedImages) delete(key string) {
|
|||
di.syncedMap.Delete(key)
|
||||
}
|
||||
|
||||
func OneImage(cfg Config, storeController storage.StoreController,
|
||||
func OneImage(ctx context.Context, cfg Config, storeController storage.StoreController,
|
||||
repo, reference string, isArtifact bool, log log.Logger,
|
||||
) error {
|
||||
// guard against multiple parallel requests
|
||||
|
@ -73,7 +73,7 @@ func OneImage(cfg Config, storeController storage.StoreController,
|
|||
defer demandedImgs.delete(demandedImage)
|
||||
defer close(imageChannel)
|
||||
|
||||
go syncOneImage(imageChannel, cfg, storeController, repo, reference, isArtifact, log)
|
||||
go syncOneImage(ctx, imageChannel, cfg, storeController, repo, reference, isArtifact, log)
|
||||
|
||||
err, ok := <-imageChannel
|
||||
if !ok {
|
||||
|
@ -83,7 +83,7 @@ func OneImage(cfg Config, storeController storage.StoreController,
|
|||
return err
|
||||
}
|
||||
|
||||
func syncOneImage(imageChannel chan error, cfg Config, storeController storage.StoreController,
|
||||
func syncOneImage(ctx context.Context, imageChannel chan error, cfg Config, storeController storage.StoreController,
|
||||
localRepo, reference string, isArtifact bool, log log.Logger,
|
||||
) {
|
||||
var credentialsFile CredentialsFile
|
||||
|
@ -248,7 +248,7 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
|
|||
demandedImageRef, copyErr)
|
||||
time.Sleep(retryOptions.Delay)
|
||||
|
||||
if err = retry.RetryIfNecessary(context.Background(), func() error {
|
||||
if err = retry.RetryIfNecessary(ctx, func() error {
|
||||
_, err := syncRun(regCfg, localRepo, upstreamRepo, reference, syncContextUtils, sig, log)
|
||||
|
||||
return err
|
||||
|
|
|
@ -340,7 +340,7 @@ func (sig *signaturesCopier) canSkipNotarySignature(localRepo, digestStr string,
|
|||
|
||||
// check notary signature already synced
|
||||
if len(refs.References) > 0 {
|
||||
localRefs, err := imageStore.GetReferrers(localRepo, digest, notreg.ArtifactTypeNotation)
|
||||
localRefs, err := imageStore.GetOrasReferrers(localRepo, digest, notreg.ArtifactTypeNotation)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrManifestNotFound) {
|
||||
return false, nil
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
notreg "github.com/notaryproject/notation-go/registry"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
oraspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
perr "github.com/pkg/errors"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/generate"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
|
@ -2404,7 +2404,7 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("Trigger error on notary signature", func() {
|
||||
// trigger permission error on cosign signature on upstream
|
||||
// trigger permission error on notary signature on upstream
|
||||
notaryURLPath := path.Join("/oras/artifacts/v1/", repoName, "manifests", imageManifestDigest.String(), "referrers")
|
||||
|
||||
// based on image manifest digest get referrers
|
||||
|
@ -2422,7 +2422,7 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
// read manifest
|
||||
var artifactManifest artifactspec.Manifest
|
||||
var artifactManifest oraspec.Manifest
|
||||
for _, ref := range referrers.References {
|
||||
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
||||
body, err := os.ReadFile(refPath)
|
||||
|
@ -2450,6 +2450,53 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 400)
|
||||
})
|
||||
|
||||
Convey("Trigger error on artifact references", func() {
|
||||
// trigger permission denied on image manifest
|
||||
manifestPath := path.Join(srcDir, repoName, "blobs",
|
||||
string(imageManifestDigest.Algorithm()), imageManifestDigest.Encoded())
|
||||
err = os.Chmod(manifestPath, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// trigger permission error on upstream
|
||||
artifactURLPath := path.Join("/v2", repoName, "referrers", imageManifestDigest.String())
|
||||
|
||||
// based on image manifest digest get referrers
|
||||
resp, err := resty.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetQueryParam("artifactType", "application/vnd.cncf.icecream").
|
||||
Get(srcBaseURL + artifactURLPath)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeEmpty)
|
||||
|
||||
var referrers ispec.Index
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &referrers)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// read manifest
|
||||
for _, ref := range referrers.Manifests {
|
||||
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
||||
_, err = os.ReadFile(refPath)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// triggers perm denied on artifact blobs
|
||||
err = os.Chmod(refPath, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
// start downstream server
|
||||
dctlr, destBaseURL, _, _ := startDownstreamServer(t, false, syncConfig)
|
||||
defer dctlr.Shutdown()
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// should not be synced nor sync on demand
|
||||
resp, err = resty.R().Get(destBaseURL + artifactURLPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2590,7 +2637,7 @@ func TestSignatures(t *testing.T) {
|
|||
err = os.RemoveAll(path.Join(destDir, repoName))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var artifactManifest artifactspec.Manifest
|
||||
var artifactManifest oraspec.Manifest
|
||||
for _, ref := range referrers.References {
|
||||
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
||||
body, err := os.ReadFile(refPath)
|
||||
|
@ -4405,6 +4452,42 @@ func pushRepo(url, repoName string) godigest.Digest {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
// push a referrer artifact
|
||||
manifest = ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.cncf.icecream",
|
||||
Digest: cdigest,
|
||||
Size: int64(len(cblob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
},
|
||||
Subject: &ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
}
|
||||
|
||||
manifest.SchemaVersion = 2
|
||||
|
||||
content, err = json.Marshal(manifest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
adigest := godigest.FromBytes(content)
|
||||
|
||||
_, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Put(url + fmt.Sprintf("/v2/%s/manifests/%s", repoName, adigest.String()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return digest
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/notaryproject/notation-go"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
oras "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sigstore/cosign/pkg/oci/remote"
|
||||
|
||||
|
@ -63,7 +63,8 @@ func ValidateManifest(imgStore ImageStore, repo, reference, mediaType string, bo
|
|||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
|
||||
if mediaType == ispec.MediaTypeImageManifest {
|
||||
switch mediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
var manifest ispec.Manifest
|
||||
if err := json.Unmarshal(body, &manifest); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
@ -79,13 +80,38 @@ func ValidateManifest(imgStore ImageStore, repo, reference, mediaType string, bo
|
|||
return digest, err
|
||||
}
|
||||
}
|
||||
} else if mediaType == artifactspec.MediaTypeArtifactManifest {
|
||||
|
||||
if manifest.Subject != nil {
|
||||
var m ispec.Descriptor
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
}
|
||||
case oras.MediaTypeArtifactManifest:
|
||||
var m notation.Descriptor
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
case ispec.MediaTypeArtifactManifest:
|
||||
var artifact ispec.Artifact
|
||||
if err := json.Unmarshal(body, &artifact); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
|
||||
if artifact.Subject != nil {
|
||||
var m ispec.Descriptor
|
||||
if err := json.Unmarshal(body, &m); err != nil {
|
||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||
|
||||
return "", zerr.ErrBadManifest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
|
@ -423,5 +449,6 @@ func ApplyLinter(imgStore ImageStore, linter Lint, repo string, manifestDesc isp
|
|||
func IsSupportedMediaType(mediaType string) bool {
|
||||
return mediaType == ispec.MediaTypeImageIndex ||
|
||||
mediaType == ispec.MediaTypeImageManifest ||
|
||||
mediaType == artifactspec.MediaTypeArtifactManifest
|
||||
mediaType == ispec.MediaTypeArtifactManifest ||
|
||||
mediaType == oras.MediaTypeArtifactManifest
|
||||
}
|
||||
|
|
|
@ -6,16 +6,18 @@ import (
|
|||
|
||||
const (
|
||||
// BlobUploadDir defines the upload directory for blob uploads.
|
||||
BlobUploadDir = ".uploads"
|
||||
SchemaVersion = 2
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
RLOCK = "RLock"
|
||||
RWLOCK = "RWLock"
|
||||
BlobsCache = "blobs"
|
||||
DuplicatesBucket = "duplicates"
|
||||
OriginalBucket = "original"
|
||||
DBExtensionName = ".db"
|
||||
DBCacheLockCheckTimeout = 10 * time.Second
|
||||
BoltdbName = "cache"
|
||||
BlobUploadDir = ".uploads"
|
||||
SchemaVersion = 2
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
RLOCK = "RLock"
|
||||
RWLOCK = "RWLock"
|
||||
BlobsCache = "blobs"
|
||||
DuplicatesBucket = "duplicates"
|
||||
OriginalBucket = "original"
|
||||
DBExtensionName = ".db"
|
||||
DBCacheLockCheckTimeout = 10 * time.Second
|
||||
BoltdbName = "cache"
|
||||
ReferrerFilterAnnotation = "org.opencontainers.references.filtersApplied"
|
||||
//
|
||||
)
|
||||
|
|
|
@ -20,10 +20,11 @@ import (
|
|||
guuid "github.com/gofrs/uuid"
|
||||
"github.com/minio/sha256-simd"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
imeta "github.com/opencontainers/image-spec/specs-go"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/umoci"
|
||||
"github.com/opencontainers/umoci/oci/casext"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
oras "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
|
@ -37,8 +38,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
DefaultFilePerms = 0o600
|
||||
DefaultDirPerms = 0o700
|
||||
defaultSchemaVersion = 2
|
||||
)
|
||||
|
||||
// ImageStoreLocal provides the image storage operations.
|
||||
|
@ -186,8 +188,7 @@ func (is *ImageStoreLocal) initRepo(name string) error {
|
|||
// "index.json" file - create if it doesn't exist
|
||||
indexPath := path.Join(repoDir, "index.json")
|
||||
if _, err := os.Stat(indexPath); err != nil {
|
||||
index := ispec.Index{}
|
||||
index.SchemaVersion = 2
|
||||
index := ispec.Index{Versioned: imeta.Versioned{SchemaVersion: defaultSchemaVersion}}
|
||||
|
||||
buf, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
|
@ -1339,7 +1340,120 @@ func (is *ImageStoreLocal) DeleteBlob(repo string, digest godigest.Digest) error
|
|||
}
|
||||
|
||||
func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, artifactType string,
|
||||
) ([]artifactspec.Descriptor, error) {
|
||||
) (ispec.Index, error) {
|
||||
var lockLatency time.Time
|
||||
|
||||
nilIndex := ispec.Index{}
|
||||
|
||||
if err := gdigest.Validate(); err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
dir := path.Join(is.rootDir, repo)
|
||||
if !is.DirExists(dir) {
|
||||
return nilIndex, zerr.ErrRepoNotFound
|
||||
}
|
||||
|
||||
index, err := storage.GetIndex(is, repo, is.log)
|
||||
if err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
is.RLock(&lockLatency)
|
||||
defer is.RUnlock(&lockLatency)
|
||||
|
||||
found := false
|
||||
|
||||
result := []ispec.Descriptor{}
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.Digest == gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
p := path.Join(dir, "blobs", manifest.Digest.Algorithm().String(), manifest.Digest.Encoded())
|
||||
|
||||
buf, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
is.log.Error().Err(err).Str("blob", p).Msg("failed to read manifest")
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
return nilIndex, zerr.ErrManifestNotFound
|
||||
}
|
||||
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if manifest.MediaType == ispec.MediaTypeImageManifest {
|
||||
var mfst ispec.Manifest
|
||||
if err := json.Unmarshal(buf, &mfst); err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if mfst.Subject == nil || mfst.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
if artifactType != "" && mfst.Config.MediaType != artifactType {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
ArtifactType: mfst.Config.MediaType,
|
||||
Size: manifest.Size,
|
||||
Digest: manifest.Digest,
|
||||
Annotations: mfst.Annotations,
|
||||
})
|
||||
} else if manifest.MediaType == ispec.MediaTypeArtifactManifest {
|
||||
var art ispec.Artifact
|
||||
if err := json.Unmarshal(buf, &art); err != nil {
|
||||
return nilIndex, err
|
||||
}
|
||||
|
||||
if art.Subject == nil || art.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by artifact type
|
||||
if artifactType != "" && art.ArtifactType != artifactType {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
ArtifactType: art.ArtifactType,
|
||||
Size: manifest.Size,
|
||||
Digest: manifest.Digest,
|
||||
Annotations: art.Annotations,
|
||||
})
|
||||
}
|
||||
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nilIndex, zerr.ErrManifestNotFound
|
||||
}
|
||||
|
||||
index = ispec.Index{
|
||||
Versioned: imeta.Versioned{SchemaVersion: defaultSchemaVersion},
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
Manifests: result,
|
||||
Annotations: map[string]string{},
|
||||
}
|
||||
|
||||
// response was filtered by artifactType
|
||||
if artifactType != "" {
|
||||
index.Annotations[storageConstants.ReferrerFilterAnnotation] = artifactType
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (is *ImageStoreLocal) GetOrasReferrers(repo string, gdigest godigest.Digest, artifactType string,
|
||||
) ([]oras.Descriptor, error) {
|
||||
var lockLatency time.Time
|
||||
|
||||
if err := gdigest.Validate(); err != nil {
|
||||
|
@ -1361,10 +1475,10 @@ func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, ar
|
|||
|
||||
found := false
|
||||
|
||||
result := []artifactspec.Descriptor{}
|
||||
result := []oras.Descriptor{}
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.MediaType != artifactspec.MediaTypeArtifactManifest {
|
||||
if manifest.MediaType != oras.MediaTypeArtifactManifest {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1381,18 +1495,23 @@ func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, ar
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var artManifest artifactspec.Manifest
|
||||
var artManifest oras.Manifest
|
||||
if err := json.Unmarshal(buf, &artManifest); err != nil {
|
||||
is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if artifactType != artManifest.ArtifactType || gdigest != artManifest.Subject.Digest {
|
||||
if artManifest.Subject.Digest != gdigest {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, artifactspec.Descriptor{
|
||||
// filter by artifact type
|
||||
if artifactType != "" && artManifest.ArtifactType != artifactType {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, oras.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
ArtifactType: artManifest.ArtifactType,
|
||||
Digest: manifest.Digest,
|
||||
|
|
|
@ -147,14 +147,14 @@ func TestStorageFSAPIs(t *testing.T) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
// invalid GetReferrers
|
||||
_, err = imgStore.GetReferrers("invalid", "invalid", "invalid")
|
||||
// invalid GetOrasReferrers
|
||||
_, err = imgStore.GetOrasReferrers("invalid", "invalid", "invalid")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = imgStore.GetReferrers(repoName, "invalid", "invalid")
|
||||
_, err = imgStore.GetOrasReferrers(repoName, "invalid", "invalid")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = imgStore.GetReferrers(repoName, digest, "invalid")
|
||||
_, err = imgStore.GetOrasReferrers(repoName, digest, "invalid")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// invalid DeleteImageManifest
|
||||
|
@ -175,7 +175,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestGetReferrers(t *testing.T) {
|
||||
func TestGetOrasReferrers(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
log := log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||
|
@ -218,7 +218,7 @@ func TestGetReferrers(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
descriptors, err := imgStore.GetReferrers("zot-test", digest, "signature-example")
|
||||
descriptors, err := imgStore.GetOrasReferrers("zot-test", digest, "signature-example")
|
||||
So(err, ShouldBeNil)
|
||||
So(descriptors, ShouldNotBeEmpty)
|
||||
So(descriptors[0].ArtifactType, ShouldEqual, "signature-example")
|
||||
|
@ -982,7 +982,7 @@ func FuzzGetBlobContent(f *testing.F) {
|
|||
})
|
||||
}
|
||||
|
||||
func FuzzGetReferrers(f *testing.F) {
|
||||
func FuzzGetOrasReferrers(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data string) {
|
||||
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||
metrics := monitoring.NewMetricsServer(false, *log)
|
||||
|
@ -1033,7 +1033,7 @@ func FuzzGetReferrers(f *testing.F) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = imgStore.GetReferrers("zot-test", digest, data)
|
||||
_, err = imgStore.GetOrasReferrers("zot-test", digest, data)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrManifestNotFound) || isKnownErr(err) {
|
||||
return
|
||||
|
|
|
@ -1224,7 +1224,12 @@ func (is *ObjectStorage) GetBlobContent(repo string, digest godigest.Digest) ([]
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (is *ObjectStorage) GetReferrers(repo string, digest godigest.Digest, mediaType string,
|
||||
func (is *ObjectStorage) GetReferrers(repo string, digest godigest.Digest, artifactType string,
|
||||
) (ispec.Index, error) {
|
||||
return ispec.Index{}, zerr.ErrMethodNotSupported
|
||||
}
|
||||
|
||||
func (is *ObjectStorage) GetOrasReferrers(repo string, digest godigest.Digest, artifactType string,
|
||||
) ([]artifactspec.Descriptor, error) {
|
||||
return nil, zerr.ErrMethodNotSupported
|
||||
}
|
||||
|
|
|
@ -917,6 +917,18 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
|||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrMethodNotSupported)
|
||||
})
|
||||
|
||||
Convey("Test GetOrasReferrers", func(c C) {
|
||||
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{
|
||||
DeleteFn: func(ctx context.Context, path string) error {
|
||||
return errS3
|
||||
},
|
||||
})
|
||||
d := godigest.FromBytes([]byte(""))
|
||||
_, err := imgStore.GetOrasReferrers(testImage, d, "application/image")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrMethodNotSupported)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
|
@ -48,7 +49,8 @@ type ImageStore interface { //nolint:interfacebloat
|
|||
DeleteBlob(repo string, digest godigest.Digest) error
|
||||
GetIndexContent(repo string) ([]byte, error)
|
||||
GetBlobContent(repo string, digest godigest.Digest) ([]byte, error)
|
||||
GetReferrers(repo string, digest godigest.Digest, mediaType string) ([]artifactspec.Descriptor, error)
|
||||
GetReferrers(repo string, digest godigest.Digest, artifactType string) (ispec.Index, error)
|
||||
GetOrasReferrers(repo string, digest godigest.Digest, artifactType string) ([]artifactspec.Descriptor, error)
|
||||
RunGCRepo(repo string) error
|
||||
RunGCPeriodically(interval time.Duration, sch *scheduler.Scheduler)
|
||||
}
|
||||
|
|
|
@ -212,8 +212,10 @@ func GetRandomImageConfig() ([]byte, godigest.Digest) {
|
|||
randomAuthor := randomString(maxLen)
|
||||
|
||||
config := imagespec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: imagespec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: imagespec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
@ -231,10 +233,25 @@ func GetRandomImageConfig() ([]byte, godigest.Digest) {
|
|||
return configBlobContent, configBlobDigestRaw
|
||||
}
|
||||
|
||||
func GetEmptyImageConfig() ([]byte, godigest.Digest) {
|
||||
config := imagespec.Image{}
|
||||
|
||||
configBlobContent, err := json.MarshalIndent(&config, "", "\t")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
configBlobDigestRaw := godigest.FromBytes(configBlobContent)
|
||||
|
||||
return configBlobContent, configBlobDigestRaw
|
||||
}
|
||||
|
||||
func GetImageConfig() ([]byte, godigest.Digest) {
|
||||
config := imagespec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: imagespec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: imagespec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
@ -305,8 +322,10 @@ func GetOciLayoutDigests(imagePath string) (godigest.Digest, godigest.Digest, go
|
|||
|
||||
func GetImageComponents(layerSize int) (imagespec.Image, [][]byte, imagespec.Manifest, error) {
|
||||
config := imagespec.Image{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
Platform: imagespec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: imagespec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
|
@ -39,7 +40,8 @@ type MockedImageStore struct {
|
|||
DeleteBlobFn func(repo string, digest godigest.Digest) error
|
||||
GetIndexContentFn func(repo string) ([]byte, error)
|
||||
GetBlobContentFn func(repo string, digest godigest.Digest) ([]byte, error)
|
||||
GetReferrersFn func(repo string, digest godigest.Digest, mediaType string) ([]artifactspec.Descriptor, error)
|
||||
GetReferrersFn func(repo string, digest godigest.Digest, artifactType string) (ispec.Index, error)
|
||||
GetOrasReferrersFn func(repo string, digest godigest.Digest, artifactType string) ([]artifactspec.Descriptor, error)
|
||||
URLForPathFn func(path string) (string, error)
|
||||
RunGCRepoFn func(repo string) error
|
||||
RunGCPeriodicallyFn func(interval time.Duration, sch *scheduler.Scheduler)
|
||||
|
@ -287,12 +289,23 @@ func (is MockedImageStore) GetBlobContent(repo string, digest godigest.Digest) (
|
|||
}
|
||||
|
||||
func (is MockedImageStore) GetReferrers(
|
||||
repo string, digest godigest.Digest,
|
||||
artifactType string,
|
||||
) (ispec.Index, error) {
|
||||
if is.GetReferrersFn != nil {
|
||||
return is.GetReferrersFn(repo, digest, artifactType)
|
||||
}
|
||||
|
||||
return ispec.Index{}, nil
|
||||
}
|
||||
|
||||
func (is MockedImageStore) GetOrasReferrers(
|
||||
repo string,
|
||||
digest godigest.Digest,
|
||||
mediaType string,
|
||||
artifactType string,
|
||||
) ([]artifactspec.Descriptor, error) {
|
||||
if is.GetReferrersFn != nil {
|
||||
return is.GetReferrersFn(repo, digest, mediaType)
|
||||
if is.GetOrasReferrersFn != nil {
|
||||
return is.GetOrasReferrersFn(repo, digest, artifactType)
|
||||
}
|
||||
|
||||
return []artifactspec.Descriptor{}, nil
|
||||
|
|
|
@ -203,23 +203,50 @@ EOF
|
|||
[ "${lines[-1]}" == "this is an artifact" ]
|
||||
}
|
||||
|
||||
@test "list OCI artifacts with regclient" {
|
||||
run regctl artifact list localhost:8080/test-regclient --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests') == '[]' ]
|
||||
# push OCI artifacts on an image
|
||||
run regctl artifact put --refers localhost:8080/test-regclient <<EOF
|
||||
first artifact with subject
|
||||
@test "push OCI artifact references with regclient" {
|
||||
run regctl artifact put localhost:8080/manifest-ref:demo <<EOF
|
||||
test artifact
|
||||
EOF
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run regctl artifact put --refers localhost:8080/test-regclient <<EOF
|
||||
second artifact with subject
|
||||
run regctl artifact list localhost:8080/manifest-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
|
||||
run regctl artifact put --annotation demo=true --annotation format=oci --artifact-type "application/vnd.example.icecream.v1" --subject localhost:8080/manifest-ref:demo << EOF
|
||||
test reference
|
||||
EOF
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# list OCI artifacts of an image
|
||||
run regctl artifact list localhost:8080/test-regclient --format raw-body
|
||||
# with artifact media-type
|
||||
run regctl artifact put localhost:8080/artifact-ref:demo <<EOF
|
||||
test artifact
|
||||
EOF
|
||||
[ "$status" -eq 0 ]
|
||||
run regctl artifact list localhost:8080/artifact-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
|
||||
run regctl artifact put --media-type "application/vnd.oci.artifact.manifest.v1+json" --annotation demo=true --annotation format=oci --artifact-type "application/vnd.example.icecream.v1" --subject localhost:8080/artifact-ref:demo << EOF
|
||||
test reference
|
||||
EOF
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 2 ]
|
||||
}
|
||||
|
||||
@test "pull OCI artifact references with regclient" {
|
||||
run regctl artifact list localhost:8080/manifest-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
|
||||
run regctl artifact list --filter-artifact-type "application/vnd.example.icecream.v1" localhost:8080/manifest-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
|
||||
run regctl artifact list --filter-artifact-type "application/invalid" localhost:8080/manifest-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
|
||||
# with artifact media-type
|
||||
run regctl artifact list localhost:8080/artifact-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
|
||||
run regctl artifact list --filter-artifact-type "application/vnd.example.icecream.v1" localhost:8080/artifact-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
|
||||
run regctl artifact list --filter-artifact-type "application/invalid" localhost:8080/artifact-ref:demo --format raw-body
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue