mirror of
https://github.com/project-zot/zot.git
synced 2025-01-13 22:50:38 -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
|
HELM := $(TOOLSDIR)/bin/helm
|
||||||
ORAS := $(TOOLSDIR)/bin/oras
|
ORAS := $(TOOLSDIR)/bin/oras
|
||||||
REGCLIENT := $(TOOLSDIR)/bin/regctl
|
REGCLIENT := $(TOOLSDIR)/bin/regctl
|
||||||
|
REGCLIENT_VERSION := v0.4.5
|
||||||
STACKER := $(TOOLSDIR)/bin/stacker
|
STACKER := $(TOOLSDIR)/bin/stacker
|
||||||
BATS := $(TOOLSDIR)/bin/bats
|
BATS := $(TOOLSDIR)/bin/bats
|
||||||
TESTDATA := $(TOP_LEVEL)/test/data
|
TESTDATA := $(TOP_LEVEL)/test/data
|
||||||
|
@ -121,7 +122,7 @@ $(HELM):
|
||||||
|
|
||||||
$(REGCLIENT):
|
$(REGCLIENT):
|
||||||
mkdir -p $(TOOLSDIR)/bin
|
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
|
cp regctl $(TOOLSDIR)/bin/regctl
|
||||||
chmod +x $(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)
|
## [**Why zot?**](COMPARISON.md)
|
||||||
|
|
||||||
## What's new?
|
## 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
|
* 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)
|
* Supports container image signatures - [cosign](https://github.com/sigstore/cosign) and [notation](https://github.com/notaryproject/notation)
|
||||||
* Multi-arch support
|
* Multi-arch support
|
||||||
* Clustering support
|
* Clustering support
|
||||||
* Image linting support
|
* Image linting support
|
||||||
* Supports push/pull OCI Artifacts
|
|
||||||
|
|
||||||
## [Demos](demos/README.md)
|
## [Demos](demos/README.md)
|
||||||
|
|
||||||
|
@ -290,7 +291,7 @@ Supports:
|
||||||
|
|
||||||
You can benchmark a zot registry or any other dist-spec conformant registry with `zb`.
|
You can benchmark a zot registry or any other dist-spec conformant registry with `zb`.
|
||||||
|
|
||||||
## Building `zb``
|
## Building `zb`
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make bench
|
$ make bench
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -29,7 +29,7 @@ require (
|
||||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
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/opencontainers/umoci v0.4.8-0.20210922062158-e60a0cc726e6
|
||||||
github.com/oras-project/artifacts-spec v1.0.0-rc.2
|
github.com/oras-project/artifacts-spec v1.0.0-rc.2
|
||||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
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/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/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/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
|
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.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
|
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-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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
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-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 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw=
|
||||||
github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
|
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=
|
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/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/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.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.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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
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(
|
resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
|
||||||
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
|
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
|
||||||
So(err, ShouldBeNil)
|
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)
|
cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
|
||||||
out, err = cmd.CombinedOutput()
|
out, err = cmd.CombinedOutput()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
@ -4084,7 +4084,7 @@ func TestImageSignatures(t *testing.T) {
|
||||||
resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
|
resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
|
||||||
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
|
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
|
||||||
So(err, ShouldBeNil)
|
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)
|
cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
|
||||||
out, err = cmd.CombinedOutput()
|
out, err = cmd.CombinedOutput()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
@ -4093,7 +4093,7 @@ func TestImageSignatures(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("GetReferrers", func() {
|
Convey("GetOrasReferrers", func() {
|
||||||
// cover error paths
|
// cover error paths
|
||||||
resp, err := resty.R().Get(
|
resp, err := resty.R().Get(
|
||||||
fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, "badRepo", "badDigest"))
|
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
|
//nolint:dupl // duplicated test code
|
||||||
func TestRouteFailures(t *testing.T) {
|
func TestRouteFailures(t *testing.T) {
|
||||||
Convey("Make a new controller", t, func() {
|
Convey("Make a new controller", t, func() {
|
||||||
|
@ -4685,7 +4980,7 @@ func TestRouteFailures(t *testing.T) {
|
||||||
request = mux.SetURLVars(request, map[string]string{})
|
request = mux.SetURLVars(request, map[string]string{})
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
rthdlr.GetReferrers(response, request)
|
rthdlr.GetOrasReferrers(response, request)
|
||||||
|
|
||||||
resp := response.Result()
|
resp := response.Result()
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
@ -4696,7 +4991,7 @@ func TestRouteFailures(t *testing.T) {
|
||||||
request = mux.SetURLVars(request, map[string]string{"name": "foo"})
|
request = mux.SetURLVars(request, map[string]string{"name": "foo"})
|
||||||
response = httptest.NewRecorder()
|
response = httptest.NewRecorder()
|
||||||
|
|
||||||
rthdlr.GetReferrers(response, request)
|
rthdlr.GetOrasReferrers(response, request)
|
||||||
|
|
||||||
resp = response.Result()
|
resp = response.Result()
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -96,6 +98,9 @@ func (rh *RouteHandler) SetupRoutes() {
|
||||||
rh.UpdateBlobUpload).Methods("PUT")
|
rh.UpdateBlobUpload).Methods("PUT")
|
||||||
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
|
prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
|
||||||
rh.DeleteBlobUpload).Methods("DELETE")
|
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,
|
prefixedRouter.HandleFunc(constants.ExtCatalogPrefix,
|
||||||
rh.ListRepositories).Methods(allowedMethods("GET")...)
|
rh.ListRepositories).Methods(allowedMethods("GET")...)
|
||||||
prefixedRouter.HandleFunc(constants.ExtOciDiscoverPrefix,
|
prefixedRouter.HandleFunc(constants.ExtOciDiscoverPrefix,
|
||||||
|
@ -104,9 +109,9 @@ func (rh *RouteHandler) SetupRoutes() {
|
||||||
rh.CheckVersionSupport).Methods(allowedMethods("GET")...)
|
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",
|
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
|
// swagger
|
||||||
debug.SetupSwaggerRoutes(rh.c.Config, rh.c.Router, rh.c.Log)
|
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
|
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 err != nil {
|
||||||
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
||||||
WriteJSON(response, http.StatusNotFound,
|
WriteJSON(response, http.StatusNotFound,
|
||||||
|
@ -375,7 +381,8 @@ func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.
|
||||||
return
|
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 err != nil {
|
||||||
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
|
||||||
WriteJSON(response, http.StatusNotFound,
|
WriteJSON(response, http.StatusNotFound,
|
||||||
|
@ -398,6 +405,117 @@ func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.
|
||||||
WriteData(response, http.StatusOK, mediaType, content)
|
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
|
// UpdateManifest godoc
|
||||||
// @Summary Update image manifest
|
// @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
|
||||||
|
@ -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.
|
// 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,
|
reference string,
|
||||||
) ([]byte, godigest.Digest, string, error) {
|
) ([]byte, godigest.Digest, string, error) {
|
||||||
content, digest, mediaType, err := imgStore.GetImageManifest(name, reference)
|
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",
|
routeHandler.c.Log.Info().Msgf("image not found, trying to get image %s:%s by syncing on demand",
|
||||||
name, reference)
|
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)
|
name, reference, false, routeHandler.c.Log)
|
||||||
if errSync != nil {
|
if errSync != nil {
|
||||||
routeHandler.c.Log.Err(errSync).Msgf("error encounter while syncing image %s:%s",
|
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.
|
// 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,
|
artifactType string,
|
||||||
) ([]artifactspec.Descriptor, error) {
|
) ([]artifactspec.Descriptor, error) {
|
||||||
refs, err := imgStore.GetReferrers(name, digest, artifactType)
|
refs, err := imgStore.GetOrasReferrers(name, digest, artifactType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if routeHandler.c.Config.Extensions != nil &&
|
if routeHandler.c.Config.Extensions != nil &&
|
||||||
routeHandler.c.Config.Extensions.Sync != 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",
|
routeHandler.c.Log.Info().Msgf("signature not found, trying to get signature %s:%s by syncing on demand",
|
||||||
name, digest.String())
|
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)
|
name, digest.String(), true, routeHandler.c.Log)
|
||||||
if errSync != nil {
|
if errSync != nil {
|
||||||
routeHandler.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to get references")
|
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
|
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"`
|
References []artifactspec.Descriptor `json:"references"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReferrers godoc
|
// GetOrasReferrers godoc
|
||||||
// @Summary Get references for an image
|
// @Summary Get references for an image
|
||||||
// @Description Get references for an image given a digest and artifact type
|
// @Description Get references for an image given a digest and artifact type
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
@ -1530,7 +1649,7 @@ type ReferenceList struct {
|
||||||
// @Failure 404 {string} string "not found"
|
// @Failure 404 {string} string "not found"
|
||||||
// @Failure 500 {string} string "internal server error"
|
// @Failure 500 {string} string "internal server error"
|
||||||
// @Router /oras/artifacts/v1/{name:%s}/manifests/{digest}/referrers [get].
|
// @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)
|
vars := mux.Vars(request)
|
||||||
name, ok := vars["name"]
|
name, ok := vars["name"]
|
||||||
|
|
||||||
|
@ -1549,15 +1668,20 @@ func (rh *RouteHandler) GetReferrers(response http.ResponseWriter, request *http
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter by artifact type
|
||||||
|
artifactType := ""
|
||||||
|
|
||||||
artifactTypes, ok := request.URL.Query()["artifactType"]
|
artifactTypes, ok := request.URL.Query()["artifactType"]
|
||||||
if !ok || len(artifactTypes) != 1 {
|
if ok {
|
||||||
|
if len(artifactTypes) != 1 {
|
||||||
rh.c.Log.Error().Msg("invalid artifact types")
|
rh.c.Log.Error().Msg("invalid artifact types")
|
||||||
response.WriteHeader(http.StatusBadRequest)
|
response.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
artifactType := artifactTypes[0]
|
artifactType = artifactTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
if artifactType != notreg.ArtifactTypeNotation {
|
if artifactType != notreg.ArtifactTypeNotation {
|
||||||
rh.c.Log.Error().Str("artifactType", artifactType).Msg("invalid artifact type")
|
rh.c.Log.Error().Str("artifactType", artifactType).Msg("invalid artifact type")
|
||||||
|
@ -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")
|
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 {
|
if err != nil {
|
||||||
rh.c.Log.Error().Err(err).Str("name", name).Str("digest", digest.String()).Msg("unable to get references")
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,6 @@ import (
|
||||||
func SetupGQLPlaygroundRoutes(conf *config.Config, router *mux.Router,
|
func SetupGQLPlaygroundRoutes(conf *config.Config, router *mux.Router,
|
||||||
storeController storage.StoreController, log log.Logger,
|
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")
|
"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,
|
func SetupSwaggerRoutes(conf *config.Config, router *mux.Router, log log.Logger,
|
||||||
) {
|
) {
|
||||||
// swagger swagger "/swagger/v2/index.html"
|
// 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")
|
"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,
|
repoName, reference string, isArtifact bool, log log.Logger,
|
||||||
) error {
|
) error {
|
||||||
log.Info().Msgf("syncing image %s:%s", repoName, reference)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ func EnableSyncExtension(ctx context.Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncOneImage ...
|
// 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,
|
repoName, reference string, isArtifact bool, log log.Logger,
|
||||||
) error {
|
) error {
|
||||||
log.Warn().Msg("skipping syncing on demand because given zot binary doesn't include this feature," +
|
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() {
|
Convey("Test dependency list for image working", t, func() {
|
||||||
// create test images
|
// create test images
|
||||||
config := ispec.Image{
|
config := ispec.Image{
|
||||||
|
Platform: ispec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: ispec.RootFS{
|
RootFS: ispec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
@ -1516,8 +1518,10 @@ func TestBaseImageList(t *testing.T) {
|
||||||
Convey("Test base image list for image working", t, func() {
|
Convey("Test base image list for image working", t, func() {
|
||||||
// create test images
|
// create test images
|
||||||
config := ispec.Image{
|
config := ispec.Image{
|
||||||
|
Platform: ispec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: ispec.RootFS{
|
RootFS: ispec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
@ -2502,8 +2506,10 @@ func TestImageList(t *testing.T) {
|
||||||
WaitTillServerReady(baseURL)
|
WaitTillServerReady(baseURL)
|
||||||
|
|
||||||
config := ispec.Image{
|
config := ispec.Image{
|
||||||
|
Platform: ispec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: ispec.RootFS{
|
RootFS: ispec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
@ -2619,8 +2625,10 @@ func TestBuildImageInfo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := ispec.Image{
|
config := ispec.Image{
|
||||||
|
Platform: ispec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: ispec.RootFS{
|
RootFS: ispec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
|
|
@ -201,7 +201,7 @@ func (olu BaseOciLayoutUtils) checkNotarySignature(name string, digest godigest.
|
||||||
imageStore := olu.StoreController.GetImageStore(name)
|
imageStore := olu.StoreController.GetImageStore(name)
|
||||||
mediaType := notreg.ArtifactTypeNotation
|
mediaType := notreg.ArtifactTypeNotation
|
||||||
|
|
||||||
_, err := imageStore.GetReferrers(name, digest, mediaType)
|
_, err := imageStore.GetOrasReferrers(name, digest, mediaType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
olu.Log.Info().Err(err).Str("repo", name).Str("digest",
|
olu.Log.Info().Err(err).Str("repo", name).Str("digest",
|
||||||
digest.String()).Str("mediatype", mediaType).Msg("invalid notary signature")
|
digest.String()).Str("mediatype", mediaType).Msg("invalid notary signature")
|
||||||
|
|
|
@ -322,8 +322,10 @@ func TestExtractImageDetails(t *testing.T) {
|
||||||
testLogger := log.NewLogger("debug", "")
|
testLogger := log.NewLogger("debug", "")
|
||||||
layerDigest := godigest.FromBytes(content)
|
layerDigest := godigest.FromBytes(content)
|
||||||
config := ispec.Image{
|
config := ispec.Image{
|
||||||
|
Platform: ispec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: ispec.RootFS{
|
RootFS: ispec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (di *demandedImages) delete(key string) {
|
||||||
di.syncedMap.Delete(key)
|
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,
|
repo, reference string, isArtifact bool, log log.Logger,
|
||||||
) error {
|
) error {
|
||||||
// guard against multiple parallel requests
|
// guard against multiple parallel requests
|
||||||
|
@ -73,7 +73,7 @@ func OneImage(cfg Config, storeController storage.StoreController,
|
||||||
defer demandedImgs.delete(demandedImage)
|
defer demandedImgs.delete(demandedImage)
|
||||||
defer close(imageChannel)
|
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
|
err, ok := <-imageChannel
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -83,7 +83,7 @@ func OneImage(cfg Config, storeController storage.StoreController,
|
||||||
return err
|
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,
|
localRepo, reference string, isArtifact bool, log log.Logger,
|
||||||
) {
|
) {
|
||||||
var credentialsFile CredentialsFile
|
var credentialsFile CredentialsFile
|
||||||
|
@ -248,7 +248,7 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
|
||||||
demandedImageRef, copyErr)
|
demandedImageRef, copyErr)
|
||||||
time.Sleep(retryOptions.Delay)
|
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)
|
_, err := syncRun(regCfg, localRepo, upstreamRepo, reference, syncContextUtils, sig, log)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -340,7 +340,7 @@ func (sig *signaturesCopier) canSkipNotarySignature(localRepo, digestStr string,
|
||||||
|
|
||||||
// check notary signature already synced
|
// check notary signature already synced
|
||||||
if len(refs.References) > 0 {
|
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 err != nil {
|
||||||
if errors.Is(err, zerr.ErrManifestNotFound) {
|
if errors.Is(err, zerr.ErrManifestNotFound) {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
notreg "github.com/notaryproject/notation-go/registry"
|
notreg "github.com/notaryproject/notation-go/registry"
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
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"
|
perr "github.com/pkg/errors"
|
||||||
"github.com/sigstore/cosign/cmd/cosign/cli/generate"
|
"github.com/sigstore/cosign/cmd/cosign/cli/generate"
|
||||||
"github.com/sigstore/cosign/cmd/cosign/cli/options"
|
"github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||||
|
@ -2404,7 +2404,7 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Trigger error on notary signature", func() {
|
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")
|
notaryURLPath := path.Join("/oras/artifacts/v1/", repoName, "manifests", imageManifestDigest.String(), "referrers")
|
||||||
|
|
||||||
// based on image manifest digest get referrers
|
// based on image manifest digest get referrers
|
||||||
|
@ -2422,7 +2422,7 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
// read manifest
|
// read manifest
|
||||||
var artifactManifest artifactspec.Manifest
|
var artifactManifest oraspec.Manifest
|
||||||
for _, ref := range referrers.References {
|
for _, ref := range referrers.References {
|
||||||
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
||||||
body, err := os.ReadFile(refPath)
|
body, err := os.ReadFile(refPath)
|
||||||
|
@ -2450,6 +2450,53 @@ func TestPeriodicallySignaturesErr(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 400)
|
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))
|
err = os.RemoveAll(path.Join(destDir, repoName))
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
var artifactManifest artifactspec.Manifest
|
var artifactManifest oraspec.Manifest
|
||||||
for _, ref := range referrers.References {
|
for _, ref := range referrers.References {
|
||||||
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
refPath := path.Join(srcDir, repoName, "blobs", string(ref.Digest.Algorithm()), ref.Digest.Encoded())
|
||||||
body, err := os.ReadFile(refPath)
|
body, err := os.ReadFile(refPath)
|
||||||
|
@ -4405,6 +4452,42 @@ func pushRepo(url, repoName string) godigest.Digest {
|
||||||
panic(err)
|
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
|
return digest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/notaryproject/notation-go"
|
"github.com/notaryproject/notation-go"
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
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/rs/zerolog"
|
||||||
"github.com/sigstore/cosign/pkg/oci/remote"
|
"github.com/sigstore/cosign/pkg/oci/remote"
|
||||||
|
|
||||||
|
@ -63,7 +63,8 @@ func ValidateManifest(imgStore ImageStore, repo, reference, mediaType string, bo
|
||||||
return "", zerr.ErrBadManifest
|
return "", zerr.ErrBadManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
if mediaType == ispec.MediaTypeImageManifest {
|
switch mediaType {
|
||||||
|
case ispec.MediaTypeImageManifest:
|
||||||
var manifest ispec.Manifest
|
var manifest ispec.Manifest
|
||||||
if err := json.Unmarshal(body, &manifest); err != nil {
|
if err := json.Unmarshal(body, &manifest); err != nil {
|
||||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
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
|
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
|
var m notation.Descriptor
|
||||||
if err := json.Unmarshal(body, &m); err != nil {
|
if err := json.Unmarshal(body, &m); err != nil {
|
||||||
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
log.Error().Err(err).Msg("unable to unmarshal JSON")
|
||||||
|
|
||||||
return "", zerr.ErrBadManifest
|
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
|
return "", nil
|
||||||
|
@ -423,5 +449,6 @@ func ApplyLinter(imgStore ImageStore, linter Lint, repo string, manifestDesc isp
|
||||||
func IsSupportedMediaType(mediaType string) bool {
|
func IsSupportedMediaType(mediaType string) bool {
|
||||||
return mediaType == ispec.MediaTypeImageIndex ||
|
return mediaType == ispec.MediaTypeImageIndex ||
|
||||||
mediaType == ispec.MediaTypeImageManifest ||
|
mediaType == ispec.MediaTypeImageManifest ||
|
||||||
mediaType == artifactspec.MediaTypeArtifactManifest
|
mediaType == ispec.MediaTypeArtifactManifest ||
|
||||||
|
mediaType == oras.MediaTypeArtifactManifest
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,4 +18,6 @@ const (
|
||||||
DBExtensionName = ".db"
|
DBExtensionName = ".db"
|
||||||
DBCacheLockCheckTimeout = 10 * time.Second
|
DBCacheLockCheckTimeout = 10 * time.Second
|
||||||
BoltdbName = "cache"
|
BoltdbName = "cache"
|
||||||
|
ReferrerFilterAnnotation = "org.opencontainers.references.filtersApplied"
|
||||||
|
//
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,10 +20,11 @@ import (
|
||||||
guuid "github.com/gofrs/uuid"
|
guuid "github.com/gofrs/uuid"
|
||||||
"github.com/minio/sha256-simd"
|
"github.com/minio/sha256-simd"
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
imeta "github.com/opencontainers/image-spec/specs-go"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/opencontainers/umoci"
|
"github.com/opencontainers/umoci"
|
||||||
"github.com/opencontainers/umoci/oci/casext"
|
"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"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
@ -39,6 +40,7 @@ import (
|
||||||
const (
|
const (
|
||||||
DefaultFilePerms = 0o600
|
DefaultFilePerms = 0o600
|
||||||
DefaultDirPerms = 0o700
|
DefaultDirPerms = 0o700
|
||||||
|
defaultSchemaVersion = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImageStoreLocal provides the image storage operations.
|
// 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
|
// "index.json" file - create if it doesn't exist
|
||||||
indexPath := path.Join(repoDir, "index.json")
|
indexPath := path.Join(repoDir, "index.json")
|
||||||
if _, err := os.Stat(indexPath); err != nil {
|
if _, err := os.Stat(indexPath); err != nil {
|
||||||
index := ispec.Index{}
|
index := ispec.Index{Versioned: imeta.Versioned{SchemaVersion: defaultSchemaVersion}}
|
||||||
index.SchemaVersion = 2
|
|
||||||
|
|
||||||
buf, err := json.Marshal(index)
|
buf, err := json.Marshal(index)
|
||||||
if err != nil {
|
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,
|
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
|
var lockLatency time.Time
|
||||||
|
|
||||||
if err := gdigest.Validate(); err != nil {
|
if err := gdigest.Validate(); err != nil {
|
||||||
|
@ -1361,10 +1475,10 @@ func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, ar
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
result := []artifactspec.Descriptor{}
|
result := []oras.Descriptor{}
|
||||||
|
|
||||||
for _, manifest := range index.Manifests {
|
for _, manifest := range index.Manifests {
|
||||||
if manifest.MediaType != artifactspec.MediaTypeArtifactManifest {
|
if manifest.MediaType != oras.MediaTypeArtifactManifest {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1381,18 +1495,23 @@ func (is *ImageStoreLocal) GetReferrers(repo string, gdigest godigest.Digest, ar
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var artManifest artifactspec.Manifest
|
var artManifest oras.Manifest
|
||||||
if err := json.Unmarshal(buf, &artManifest); err != nil {
|
if err := json.Unmarshal(buf, &artManifest); err != nil {
|
||||||
is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
|
is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifactType != artManifest.ArtifactType || gdigest != artManifest.Subject.Digest {
|
if artManifest.Subject.Digest != gdigest {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, artifactspec.Descriptor{
|
// filter by artifact type
|
||||||
|
if artifactType != "" && artManifest.ArtifactType != artifactType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, oras.Descriptor{
|
||||||
MediaType: manifest.MediaType,
|
MediaType: manifest.MediaType,
|
||||||
ArtifactType: artManifest.ArtifactType,
|
ArtifactType: artManifest.ArtifactType,
|
||||||
Digest: manifest.Digest,
|
Digest: manifest.Digest,
|
||||||
|
|
|
@ -147,14 +147,14 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalid GetReferrers
|
// invalid GetOrasReferrers
|
||||||
_, err = imgStore.GetReferrers("invalid", "invalid", "invalid")
|
_, err = imgStore.GetOrasReferrers("invalid", "invalid", "invalid")
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
_, err = imgStore.GetReferrers(repoName, "invalid", "invalid")
|
_, err = imgStore.GetOrasReferrers(repoName, "invalid", "invalid")
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
_, err = imgStore.GetReferrers(repoName, digest, "invalid")
|
_, err = imgStore.GetOrasReferrers(repoName, digest, "invalid")
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
// invalid DeleteImageManifest
|
// invalid DeleteImageManifest
|
||||||
|
@ -175,7 +175,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetReferrers(t *testing.T) {
|
func TestGetOrasReferrers(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
log := log.Logger{Logger: zerolog.New(os.Stdout)}
|
log := log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||||
|
@ -218,7 +218,7 @@ func TestGetReferrers(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
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(err, ShouldBeNil)
|
||||||
So(descriptors, ShouldNotBeEmpty)
|
So(descriptors, ShouldNotBeEmpty)
|
||||||
So(descriptors[0].ArtifactType, ShouldEqual, "signature-example")
|
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) {
|
f.Fuzz(func(t *testing.T, data string) {
|
||||||
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
|
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
|
||||||
metrics := monitoring.NewMetricsServer(false, *log)
|
metrics := monitoring.NewMetricsServer(false, *log)
|
||||||
|
@ -1033,7 +1033,7 @@ func FuzzGetReferrers(f *testing.F) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
_, err = imgStore.GetReferrers("zot-test", digest, data)
|
_, err = imgStore.GetOrasReferrers("zot-test", digest, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, zerr.ErrManifestNotFound) || isKnownErr(err) {
|
if errors.Is(err, zerr.ErrManifestNotFound) || isKnownErr(err) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -1224,7 +1224,12 @@ func (is *ObjectStorage) GetBlobContent(repo string, digest godigest.Digest) ([]
|
||||||
return buf.Bytes(), nil
|
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) {
|
) ([]artifactspec.Descriptor, error) {
|
||||||
return nil, zerr.ErrMethodNotSupported
|
return nil, zerr.ErrMethodNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -917,6 +917,18 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zerr.ErrMethodNotSupported)
|
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"
|
"time"
|
||||||
|
|
||||||
godigest "github.com/opencontainers/go-digest"
|
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"
|
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||||
|
|
||||||
"zotregistry.io/zot/pkg/scheduler"
|
"zotregistry.io/zot/pkg/scheduler"
|
||||||
|
@ -48,7 +49,8 @@ type ImageStore interface { //nolint:interfacebloat
|
||||||
DeleteBlob(repo string, digest godigest.Digest) error
|
DeleteBlob(repo string, digest godigest.Digest) error
|
||||||
GetIndexContent(repo string) ([]byte, error)
|
GetIndexContent(repo string) ([]byte, error)
|
||||||
GetBlobContent(repo string, digest godigest.Digest) ([]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
|
RunGCRepo(repo string) error
|
||||||
RunGCPeriodically(interval time.Duration, sch *scheduler.Scheduler)
|
RunGCPeriodically(interval time.Duration, sch *scheduler.Scheduler)
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,8 +212,10 @@ func GetRandomImageConfig() ([]byte, godigest.Digest) {
|
||||||
randomAuthor := randomString(maxLen)
|
randomAuthor := randomString(maxLen)
|
||||||
|
|
||||||
config := imagespec.Image{
|
config := imagespec.Image{
|
||||||
|
Platform: imagespec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: imagespec.RootFS{
|
RootFS: imagespec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
@ -231,10 +233,25 @@ func GetRandomImageConfig() ([]byte, godigest.Digest) {
|
||||||
return configBlobContent, configBlobDigestRaw
|
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) {
|
func GetImageConfig() ([]byte, godigest.Digest) {
|
||||||
config := imagespec.Image{
|
config := imagespec.Image{
|
||||||
|
Platform: imagespec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: imagespec.RootFS{
|
RootFS: imagespec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
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) {
|
func GetImageComponents(layerSize int) (imagespec.Image, [][]byte, imagespec.Manifest, error) {
|
||||||
config := imagespec.Image{
|
config := imagespec.Image{
|
||||||
|
Platform: imagespec.Platform{
|
||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
|
},
|
||||||
RootFS: imagespec.RootFS{
|
RootFS: imagespec.RootFS{
|
||||||
Type: "layers",
|
Type: "layers",
|
||||||
DiffIDs: []godigest.Digest{},
|
DiffIDs: []godigest.Digest{},
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
godigest "github.com/opencontainers/go-digest"
|
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"
|
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
|
||||||
|
|
||||||
"zotregistry.io/zot/pkg/scheduler"
|
"zotregistry.io/zot/pkg/scheduler"
|
||||||
|
@ -39,7 +40,8 @@ type MockedImageStore struct {
|
||||||
DeleteBlobFn func(repo string, digest godigest.Digest) error
|
DeleteBlobFn func(repo string, digest godigest.Digest) error
|
||||||
GetIndexContentFn func(repo string) ([]byte, error)
|
GetIndexContentFn func(repo string) ([]byte, error)
|
||||||
GetBlobContentFn func(repo string, digest godigest.Digest) ([]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)
|
URLForPathFn func(path string) (string, error)
|
||||||
RunGCRepoFn func(repo string) error
|
RunGCRepoFn func(repo string) error
|
||||||
RunGCPeriodicallyFn func(interval time.Duration, sch *scheduler.Scheduler)
|
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(
|
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,
|
repo string,
|
||||||
digest godigest.Digest,
|
digest godigest.Digest,
|
||||||
mediaType string,
|
artifactType string,
|
||||||
) ([]artifactspec.Descriptor, error) {
|
) ([]artifactspec.Descriptor, error) {
|
||||||
if is.GetReferrersFn != nil {
|
if is.GetOrasReferrersFn != nil {
|
||||||
return is.GetReferrersFn(repo, digest, mediaType)
|
return is.GetOrasReferrersFn(repo, digest, artifactType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []artifactspec.Descriptor{}, nil
|
return []artifactspec.Descriptor{}, nil
|
||||||
|
|
|
@ -203,23 +203,50 @@ EOF
|
||||||
[ "${lines[-1]}" == "this is an artifact" ]
|
[ "${lines[-1]}" == "this is an artifact" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "list OCI artifacts with regclient" {
|
@test "push OCI artifact references with regclient" {
|
||||||
run regctl artifact list localhost:8080/test-regclient --format raw-body
|
run regctl artifact put localhost:8080/manifest-ref:demo <<EOF
|
||||||
[ "$status" -eq 0 ]
|
test artifact
|
||||||
[ $(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
|
|
||||||
EOF
|
EOF
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
run regctl artifact list localhost:8080/manifest-ref:demo --format raw-body
|
||||||
run regctl artifact put --refers localhost:8080/test-regclient <<EOF
|
[ "$status" -eq 0 ]
|
||||||
second artifact with subject
|
[ $(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
|
EOF
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
# with artifact media-type
|
||||||
# list OCI artifacts of an image
|
run regctl artifact put localhost:8080/artifact-ref:demo <<EOF
|
||||||
run regctl artifact list localhost:8080/test-regclient --format raw-body
|
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 ]
|
[ "$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…
Add table
Reference in a new issue