From 36c9631000221c733b89bac4b61a72a40dbc3550 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 24 Feb 2022 12:31:36 -0800 Subject: [PATCH] ext: use distribution spec route prefix for extension api Following the spec defined here https://github.com/opencontainers/distribution-spec/tree/main/extensions Signed-off-by: Shivam Mishra --- .github/workflows/ci-cd.yml | 2 +- Makefile | 4 +- cmd/zb/perf.go | 3 +- examples/config-allextensions.json | 49 ++--- examples/config-bearer-auth.json | 14 +- examples/config-bench.json | 2 +- examples/config-commit.json | 2 +- examples/config-conformance.json | 14 +- examples/config-cve.json | 2 +- examples/config-default-authz.json | 61 +++--- examples/config-example.json | 40 ++-- examples/config-example.yaml | 2 +- examples/config-gc-periodic.json | 2 +- examples/config-gc.json | 2 +- examples/config-metrics.json | 2 +- examples/config-minimal.json | 2 +- examples/config-multiple-cve.json | 2 +- examples/config-multiple.json | 2 +- examples/config-policy.json | 82 +++++++-- examples/config-ratelimit.json | 2 +- examples/config-s3.json | 2 +- examples/config-scrub.json | 2 +- examples/config-sync.json | 120 ++++++------ examples/config-test.json | 14 +- examples/config-tls.json | 20 +- go.mod | 17 +- go.sum | 25 +-- pkg/api/authz.go | 4 +- pkg/api/constants/consts.go | 15 +- pkg/api/constants/extensions.go | 9 + pkg/api/controller_test.go | 80 +++++++- pkg/api/routes.go | 23 ++- pkg/cli/client_elevated_test.go | 5 +- pkg/cli/client_test.go | 17 +- pkg/cli/config_cmd_test.go | 5 - pkg/cli/cve_cmd_test.go | 3 +- pkg/cli/root.go | 3 +- pkg/cli/service.go | 6 +- pkg/compliance/v1_0_0/check.go | 7 +- pkg/extensions/_search.md | 194 ++++++++++++++++++++ pkg/extensions/extensions.go | 39 +++- pkg/extensions/minimal.go | 9 +- pkg/extensions/search/common/common_test.go | 31 ++-- pkg/extensions/search/cve/cve_test.go | 73 ++++---- pkg/extensions/search/digest/digest_test.go | 28 ++- pkg/extensions/sync/sync.go | 3 +- pkg/extensions/sync/sync_test.go | 13 +- swagger/docs.go | 163 +++++++++++++--- swagger/swagger.json | 141 +++++++++++--- swagger/swagger.yaml | 109 ++++++++--- 50 files changed, 1076 insertions(+), 395 deletions(-) create mode 100644 pkg/api/constants/extensions.go create mode 100644 pkg/extensions/_search.md diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index d82518ac..d38e559c 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -56,7 +56,7 @@ jobs: - name: Install other dependencies run: | cd $GITHUB_WORKSPACE - go install github.com/swaggo/swag/cmd/swag@latest + go install github.com/swaggo/swag/cmd/swag@v1.6.3 sudo apt-get update sudo apt-get install rpm sudo apt-get install snapd diff --git a/Makefile b/Makefile index f18063bf..9a163add 100644 --- a/Makefile +++ b/Makefile @@ -102,11 +102,11 @@ check: ./golangcilint.yaml $(GOLINTER) $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags stress,extended,containers_image_openpgp ./... swagger/docs.go: - swag -v || go install github.com/swaggo/swag/cmd/swag + swag -v || go install github.com/swaggo/swag/cmd/swag@1.6.3 swag init -o swagger -g pkg/api/routes.go .PHONY: swagger -swagger: swagger/docs.go +swagger: swagger/docs.go pkg/api/routes.go .PHONY: update-licenses update-licenses: diff --git a/cmd/zb/perf.go b/cmd/zb/perf.go index 21db825b..ced94e13 100644 --- a/cmd/zb/perf.go +++ b/cmd/zb/perf.go @@ -20,6 +20,7 @@ import ( jsoniter "github.com/json-iterator/go" godigest "github.com/opencontainers/go-digest" "gopkg.in/resty.v1" + "zotregistry.io/zot/pkg/api/constants" ) const ( @@ -323,7 +324,7 @@ func GetCatalog(workdir, url, auth, repo string, requests int, config testConfig }() // send request and get response - resp, err := client.R().Get(url + "/v2/_catalog") + resp, err := client.R().Get(url + constants.RoutePrefix + constants.ExtCatalogPrefix) latency = time.Since(start) diff --git a/examples/config-allextensions.json b/examples/config-allextensions.json index f960a6b3..8a4de7c7 100644 --- a/examples/config-allextensions.json +++ b/examples/config-allextensions.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, @@ -11,31 +11,34 @@ "level": "debug" }, "extensions": { - "metrics": { - }, + "metrics": {}, "sync": { "credentialsFile": "./examples/sync-auth-filepath.json", - "registries": [{ - "urls": ["https://registry1:5000"], - "onDemand": false, - "pollInterval": "6h", - "tlsVerify": true, - "certDir": "/home/user/certs", - "maxRetries": 3, - "retryDelay": "15m", - "content":[ - { - "prefix":"/repo1/repo", - "tags":{ - "regex":"4.*", - "semver":true + "registries": [ + { + "urls": [ + "https://registry1:5000" + ], + "onDemand": false, + "pollInterval": "6h", + "tlsVerify": true, + "certDir": "/home/user/certs", + "maxRetries": 3, + "retryDelay": "15m", + "content": [ + { + "prefix": "/repo1/repo", + "tags": { + "regex": "4.*", + "semver": true + } + }, + { + "prefix": "/repo2/repo" } - }, - { - "prefix":"/repo2/repo" - } - ] - }] + ] + } + ] }, "search": { "cve": { diff --git a/examples/config-bearer-auth.json b/examples/config-bearer-auth.json index df89c2da..513e4f9e 100644 --- a/examples/config-bearer-auth.json +++ b/examples/config-bearer-auth.json @@ -1,11 +1,11 @@ { - "distSpecVersion":"1.0.1", - "storage":{ - "rootDirectory":"/tmp/zot" + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot" }, "http": { - "address":"127.0.0.1", - "port":"8080", + "address": "127.0.0.1", + "port": "8080", "auth": { "bearer": { "realm": "https://auth.myreg.io/auth/token", @@ -14,7 +14,7 @@ } } }, - "log":{ - "level":"debug" + "log": { + "level": "debug" } } diff --git a/examples/config-bench.json b/examples/config-bench.json index 5c5fe2f7..99045d3a 100644 --- a/examples/config-bench.json +++ b/examples/config-bench.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, diff --git a/examples/config-commit.json b/examples/config-commit.json index 404d9bd4..90c2d962 100644 --- a/examples/config-commit.json +++ b/examples/config-commit.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot", "commit": true diff --git a/examples/config-conformance.json b/examples/config-conformance.json index 104c49b7..6dc1641a 100644 --- a/examples/config-conformance.json +++ b/examples/config-conformance.json @@ -1,15 +1,15 @@ { - "distSpecVersion":"1.0.1", - "storage":{ - "rootDirectory":"/tmp/zot", + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot", "gc": false, "dedupe": false }, "http": { - "address":"0.0.0.0", - "port":"8080" + "address": "0.0.0.0", + "port": "8080" }, - "log":{ - "level":"debug" + "log": { + "level": "debug" } } diff --git a/examples/config-cve.json b/examples/config-cve.json index 4ba2191d..2c3a5cc5 100644 --- a/examples/config-cve.json +++ b/examples/config-cve.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, diff --git a/examples/config-default-authz.json b/examples/config-default-authz.json index 5c7d7304..ea41232d 100644 --- a/examples/config-default-authz.json +++ b/examples/config-default-authz.json @@ -1,30 +1,39 @@ { - "distSpecVersion": "1.0.1", - "storage": { - "rootDirectory": "/tmp/zot" - }, - "http": { - "address": "127.0.0.1", - "port": "8080", - "realm": "zot", - "accessControl": { - "**": { - "defaultPolicy": ["read", "create"] - }, - "tmp/**": { - "defaultPolicy": ["read", "create", "update"] - }, - "infra/**": { - "defaultPolicy": ["read"] - }, - "repos2/repo": { - "defaultPolicy": ["read"] - } + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot" + }, + "http": { + "address": "127.0.0.1", + "port": "8080", + "realm": "zot", + "accessControl": { + "**": { + "defaultPolicy": [ + "read", + "create" + ] + }, + "tmp/**": { + "defaultPolicy": [ + "read", + "create", + "update" + ] + }, + "infra/**": { + "defaultPolicy": [ + "read" + ] + }, + "repos2/repo": { + "defaultPolicy": [ + "read" + ] } - }, - "log": { - "level": "debug" } + }, + "log": { + "level": "debug" } - - \ No newline at end of file +} diff --git a/examples/config-example.json b/examples/config-example.json index 105b1861..39267dab 100644 --- a/examples/config-example.json +++ b/examples/config-example.json @@ -1,27 +1,27 @@ { - "distSpecVersion":"1.0.1", - "storage":{ - "rootDirectory":"/tmp/zot" + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot" }, "http": { - "address":"127.0.0.1", - "port":"8080", - "realm":"zot", + "address": "127.0.0.1", + "port": "8080", + "realm": "zot", "tls": { - "cert":"test/data/server.cert", - "key":"test/data/server.key" + "cert": "test/data/server.cert", + "key": "test/data/server.key" }, "auth": { "ldap": { - "address":"ldap.example.org", - "port":389, - "startTLS":false, - "baseDN":"ou=Users,dc=example,dc=org", - "userAttribute":"uid", - "bindDN":"cn=ldap-searcher,ou=Users,dc=example,dc=org", - "bindPassword":"ldap-searcher-password", - "skipVerify":false, - "subtreeSearch":true + "address": "ldap.example.org", + "port": 389, + "startTLS": false, + "baseDN": "ou=Users,dc=example,dc=org", + "userAttribute": "uid", + "bindDN": "cn=ldap-searcher,ou=Users,dc=example,dc=org", + "bindPassword": "ldap-searcher-password", + "skipVerify": false, + "subtreeSearch": true }, "htpasswd": { "path": "test/data/htpasswd" @@ -30,9 +30,9 @@ }, "allowReadAccess": false }, - "log":{ - "level":"debug", - "output":"/tmp/zot.log", + "log": { + "level": "debug", + "output": "/tmp/zot.log", "audit": "/tmp/zot-audit.log" } } diff --git a/examples/config-example.yaml b/examples/config-example.yaml index 818f4818..6ab3bd7a 100644 --- a/examples/config-example.yaml +++ b/examples/config-example.yaml @@ -1,4 +1,4 @@ -distspecversion: 1.0.1 +distspecversion: 1.0.1-dev http: address: 127.0.0.1 allowreadaccess: false diff --git a/examples/config-gc-periodic.json b/examples/config-gc-periodic.json index d96eb4e6..f4cf4da6 100644 --- a/examples/config-gc-periodic.json +++ b/examples/config-gc-periodic.json @@ -1,5 +1,5 @@ { - "distSpecVersion":"1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot", "gc": true, diff --git a/examples/config-gc.json b/examples/config-gc.json index b64d035b..b123722a 100644 --- a/examples/config-gc.json +++ b/examples/config-gc.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot", "gc": true, diff --git a/examples/config-metrics.json b/examples/config-metrics.json index ede05b49..3c558973 100644 --- a/examples/config-metrics.json +++ b/examples/config-metrics.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, diff --git a/examples/config-minimal.json b/examples/config-minimal.json index 9f4e0011..3596f0ff 100644 --- a/examples/config-minimal.json +++ b/examples/config-minimal.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, diff --git a/examples/config-multiple-cve.json b/examples/config-multiple-cve.json index 35c16f69..8ba86c5d 100644 --- a/examples/config-multiple-cve.json +++ b/examples/config-multiple-cve.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot", "dedupe": true, diff --git a/examples/config-multiple.json b/examples/config-multiple.json index 759486a4..f6f2f2ad 100644 --- a/examples/config-multiple.json +++ b/examples/config-multiple.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot", "dedupe": true, diff --git a/examples/config-policy.json b/examples/config-policy.json index 23efb2b1..d481d394 100644 --- a/examples/config-policy.json +++ b/examples/config-policy.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, @@ -17,44 +17,91 @@ "**": { "policies": [ { - "users": ["charlie"], - "actions": ["read", "create", "update"] + "users": [ + "charlie" + ], + "actions": [ + "read", + "create", + "update" + ] } ], - "defaultPolicy": ["read", "create"] + "defaultPolicy": [ + "read", + "create" + ] }, "tmp/**": { - "defaultPolicy": ["read", "create", "update"] + "defaultPolicy": [ + "read", + "create", + "update" + ] }, "infra/**": { "policies": [ { - "users": ["alice", "bob"], - "actions": ["create", "read", "update", "delete"] + "users": [ + "alice", + "bob" + ], + "actions": [ + "create", + "read", + "update", + "delete" + ] }, { - "users": ["mallory"], - "actions": ["create", "read"] + "users": [ + "mallory" + ], + "actions": [ + "create", + "read" + ] } ], - "defaultPolicy": ["read"] + "defaultPolicy": [ + "read" + ] }, "repos2/repo": { "policies": [ { - "users": ["charlie"], - "actions": ["read", "create"] + "users": [ + "charlie" + ], + "actions": [ + "read", + "create" + ] }, { - "users": ["mallory"], - "actions": ["create", "read"] + "users": [ + "mallory" + ], + "actions": [ + "create", + "read" + ] } ], - "defaultPolicy": ["read"] + "defaultPolicy": [ + "read" + ] }, "adminPolicy": { - "users": ["admin"], - "actions": ["read", "create", "update", "delete"] + "users": [ + "admin" + ], + "actions": [ + "read", + "create", + "update", + "delete" + ] } } }, @@ -63,4 +110,3 @@ "output": "/tmp/zot.log" } } - diff --git a/examples/config-ratelimit.json b/examples/config-ratelimit.json index 89348d86..62ef488b 100644 --- a/examples/config-ratelimit.json +++ b/examples/config-ratelimit.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, diff --git a/examples/config-s3.json b/examples/config-s3.json index d1def347..aef3273a 100644 --- a/examples/config-s3.json +++ b/examples/config-s3.json @@ -1,5 +1,5 @@ { - "distSpecVersion": "1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/zot", "storageDriver": { diff --git a/examples/config-scrub.json b/examples/config-scrub.json index 6df19468..0ee65bc9 100644 --- a/examples/config-scrub.json +++ b/examples/config-scrub.json @@ -1,5 +1,5 @@ { - "distSpecVersion":"1.0.1", + "distSpecVersion": "1.0.1-dev", "storage": { "rootDirectory": "/tmp/zot" }, diff --git a/examples/config-sync.json b/examples/config-sync.json index 9789d3a7..ae8a18c1 100644 --- a/examples/config-sync.json +++ b/examples/config-sync.json @@ -1,68 +1,76 @@ { - "distSpecVersion":"1.0.1", - "storage":{ - "rootDirectory":"/tmp/zot" + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot" }, - "http":{ - "address":"127.0.0.1", - "port":"8080" + "http": { + "address": "127.0.0.1", + "port": "8080" }, - "log":{ - "level":"debug" + "log": { + "level": "debug" }, - "extensions":{ + "extensions": { "sync": { "enable": true, "credentialsFile": "./examples/sync-auth-filepath.json", - "registries": [{ - "urls": ["https://registry1:5000"], - "onDemand": false, - "pollInterval": "6h", - "tlsVerify": true, - "certDir": "/home/user/certs", - "maxRetries": 3, - "retryDelay": "5m", - "onlySigned": true, - "content":[ - { - "prefix":"/repo1/repo", - "tags":{ - "regex":"4.*", - "semver":true + "registries": [ + { + "urls": [ + "https://registry1:5000" + ], + "onDemand": false, + "pollInterval": "6h", + "tlsVerify": true, + "certDir": "/home/user/certs", + "maxRetries": 3, + "retryDelay": "5m", + "onlySigned": true, + "content": [ + { + "prefix": "/repo1/repo", + "tags": { + "regex": "4.*", + "semver": true + } + }, + { + "prefix": "/repo1/repo", + "destination": "/repo", + "stripPrefix": true + }, + { + "prefix": "/repo2/repo" } - }, - { - "prefix":"/repo1/repo", - "destination": "/repo", - "stripPrefix": true - }, - { - "prefix":"/repo2/repo" - } - ] - }, - { - "urls": ["https://registry2:5000", "https://registry3:5000"], - "pollInterval": "12h", - "tlsVerify": false, - "onDemand": false, - "content":[ - { - "prefix":"/repo2", - "tags":{ - "semver":true + ] + }, + { + "urls": [ + "https://registry2:5000", + "https://registry3:5000" + ], + "pollInterval": "12h", + "tlsVerify": false, + "onDemand": false, + "content": [ + { + "prefix": "/repo2", + "tags": { + "semver": true + } } - } - ] - }, - { - "urls": ["https://docker.io/library"], - "onDemand": true, - "tlsVerify": true, - "maxRetries": 6, - "retryDelay": "5m" - } - ] + ] + }, + { + "urls": [ + "https://docker.io/library" + ], + "onDemand": true, + "tlsVerify": true, + "maxRetries": 6, + "retryDelay": "5m" + } + ] } } } diff --git a/examples/config-test.json b/examples/config-test.json index 5fb19100..ab2dd4ec 100644 --- a/examples/config-test.json +++ b/examples/config-test.json @@ -1,13 +1,13 @@ { - "distSpecVersion":"1.0.1", - "storage":{ - "rootDirectory":"/tmp/zot" + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot" }, "http": { - "address":"127.0.0.1", - "port":"8080" + "address": "127.0.0.1", + "port": "8080" }, - "log":{ - "level":"debug" + "log": { + "level": "debug" } } diff --git a/examples/config-tls.json b/examples/config-tls.json index 0fb3e29f..6ebfa697 100644 --- a/examples/config-tls.json +++ b/examples/config-tls.json @@ -1,18 +1,18 @@ { - "distSpecVersion":"1.0.1", - "storage":{ - "rootDirectory":"/tmp/zot" + "distSpecVersion": "1.0.1-dev", + "storage": { + "rootDirectory": "/tmp/zot" }, "http": { - "address":"127.0.0.1", - "port":"8080", - "realm":"zot", + "address": "127.0.0.1", + "port": "8080", + "realm": "zot", "tls": { - "cert":"test/data/server.cert", - "key":"test/data/server.key" + "cert": "test/data/server.cert", + "key": "test/data/server.key" } }, - "log":{ - "level":"debug" + "log": { + "level": "debug" } } diff --git a/go.mod b/go.mod index 15b52c64..d794e435 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,6 @@ require ( github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.11.0 github.com/stretchr/testify v1.7.1 - github.com/swaggo/http-swagger v1.2.6 github.com/swaggo/swag v1.8.1 github.com/urfave/cli/v2 v2.4.0 github.com/vektah/gqlparser/v2 v2.4.1 @@ -57,9 +56,11 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) +require github.com/open-policy-agent/opa v0.37.0 // indirect + require ( - github.com/open-policy-agent/opa v0.37.0 // indirect - github.com/opencontainers/distribution-spec v1.0.1 + github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220217185014-dd38b7ed8a99 + github.com/swaggo/http-swagger v1.2.8 ) require ( @@ -90,8 +91,6 @@ require ( github.com/PaesslerAG/gval v1.0.0 // indirect github.com/PaesslerAG/jsonpath v0.1.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect @@ -179,10 +178,10 @@ require ( github.com/go-openapi/analysis v0.21.2 // indirect github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/loads v0.21.1 // indirect github.com/go-openapi/runtime v0.23.3 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/strfmt v0.21.2 // indirect github.com/go-openapi/swag v0.21.1 // indirect github.com/go-openapi/validate v0.21.0 // indirect @@ -361,10 +360,10 @@ require ( go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect diff --git a/go.sum b/go.sum index d3ca117e..02985802 100644 --- a/go.sum +++ b/go.sum @@ -303,10 +303,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C6 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ReneKroon/ttlcache/v2 v2.10.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= @@ -1068,8 +1066,9 @@ github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3Hfo github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -1103,8 +1102,10 @@ github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFu github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= @@ -2142,8 +2143,8 @@ github.com/open-policy-agent/opa v0.32.0/go.mod h1:5sJdtc+1/U8zy/j30njpQl6u9rM4M github.com/open-policy-agent/opa v0.35.0/go.mod h1:xEmekKlk6/c+so5HF9wtPnGPXDfBuBsrMGhSHOHEF+U= github.com/open-policy-agent/opa v0.37.0 h1:OUXB+RAcxQpmXeNW2BN1wYzQQvVCPF1T9zv+QXGr9Wg= github.com/open-policy-agent/opa v0.37.0/go.mod h1:xX3NUCZuXK8f0CNhFQvhm4495mZLptf94pIkWRLaFqo= -github.com/opencontainers/distribution-spec v1.0.1 h1:hT6tF6uKZAQh+HH3BrJqn7/xHhMoDHzahIg4KxD2DqM= -github.com/opencontainers/distribution-spec v1.0.1/go.mod h1:copR2flp+jTEvQIFMb6MIx45OkrxzqyjszPDT3hx/5Q= +github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220217185014-dd38b7ed8a99 h1:yGqpYh1h3ZXxGg4Mqd5ZwhlwQ9wXOgvuodxbxJA7iMY= +github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220217185014-dd38b7ed8a99/go.mod h1:aA4vdXRS8E1TG7pLZOz85InHi3BiPdErh8IpJN6E0x4= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -2546,9 +2547,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/http-swagger v1.2.6 h1:ihTjChUoSRMpFMjWw+0AkL1Ti4r6v8pCgVYLmQVRlRw= -github.com/swaggo/http-swagger v1.2.6/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA= -github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/http-swagger v1.2.8 h1:TVjxLU7qoqofJ9qynJazmpTGs/p4Kx9FTp7YYwOkJb0= +github.com/swaggo/http-swagger v1.2.8/go.mod h1:FrQwV7rx+A5t11PIX8d+tFJa2GKx11RdAXQptllPQHg= github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -3099,8 +3099,10 @@ golang.org/x/net v0.0.0-20220127074510-2fabfed7e28f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -3310,8 +3312,9 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/pkg/api/authz.go b/pkg/api/authz.go index dcac48a4..fbe078ab 100644 --- a/pkg/api/authz.go +++ b/pkg/api/authz.go @@ -3,6 +3,7 @@ package api import ( "context" "encoding/base64" + "fmt" "net/http" "strings" "time" @@ -10,6 +11,7 @@ import ( glob "github.com/bmatcuk/doublestar/v4" "github.com/gorilla/mux" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/common" "zotregistry.io/zot/pkg/log" ) @@ -191,7 +193,7 @@ func AuthzHandler(ctlr *Controller) mux.MiddlewareFunc { ctx := acCtrlr.getContext(username, request) // will return only repos on which client is authorized to read - if request.RequestURI == "/v2/_catalog" { + if request.RequestURI == fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtCatalogPrefix) { next.ServeHTTP(response, request.WithContext(ctx)) return diff --git a/pkg/api/constants/consts.go b/pkg/api/constants/consts.go index f7f8ddc1..05ac0ec3 100644 --- a/pkg/api/constants/consts.go +++ b/pkg/api/constants/consts.go @@ -1,11 +1,12 @@ package constants const ( - ArtifactSpecRoutePrefix = "/oras/artifacts/v1" - RoutePrefix = "/v2" - DistAPIVersion = "Docker-Distribution-API-Version" - DistContentDigestKey = "Docker-Content-Digest" - BlobUploadUUID = "Blob-Upload-UUID" - DefaultMediaType = "application/json" - BinaryMediaType = "application/octet-stream" + ArtifactSpecRoutePrefix = "/oras/artifacts/v1" + RoutePrefix = "/v2" + DistAPIVersion = "Docker-Distribution-API-Version" + DistContentDigestKey = "Docker-Content-Digest" + BlobUploadUUID = "Blob-Upload-UUID" + DefaultMediaType = "application/json" + BinaryMediaType = "application/octet-stream" + DefaultMetricsExtensionRoute = "/metrics" ) diff --git a/pkg/api/constants/extensions.go b/pkg/api/constants/extensions.go new file mode 100644 index 00000000..d1fe7d58 --- /dev/null +++ b/pkg/api/constants/extensions.go @@ -0,0 +1,9 @@ +package constants + +// https://github.com/opencontainers/distribution-spec/tree/main/extensions#extensions-api-for-distribution +const ( + ExtCatalogPrefix = "/_catalog" + ExtOciDiscoverPrefix = "/_oci/ext/discover" + // zot specific extensions. + ExtSearchPrefix = RoutePrefix + "/_search" +) diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index 698390b4..c22640d4 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -32,6 +32,7 @@ import ( "github.com/mitchellh/mapstructure" vldap "github.com/nmcclain/ldap" notreg "github.com/notaryproject/notation/pkg/registry" + distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" 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" @@ -47,6 +48,7 @@ import ( "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/constants" + extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/storage" "zotregistry.io/zot/pkg/test" ) @@ -1878,7 +1880,7 @@ func TestAuthorizationWithBasicAuth(t *testing.T) { // everybody should have access to /v2/_catalog resp, err = resty.R().SetBasicAuth(username, passphrase). - Get(baseURL + "/v2/_catalog") + Get(baseURL + constants.RoutePrefix + constants.ExtCatalogPrefix) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusOK) @@ -3195,7 +3197,7 @@ func TestParallelRequests(t *testing.T) { assert.Equal(t, tagResponse.StatusCode(), http.StatusOK, "response status code should return success code") repoResponse, err := client.R().SetBasicAuth(username, passphrase). - Get(baseURL + "/v2/_catalog") + Get(baseURL + constants.RoutePrefix + constants.ExtCatalogPrefix) assert.Equal(t, err, nil, "Error should be nil") assert.Equal(t, repoResponse.StatusCode(), http.StatusOK, "response status code should return success code") }) @@ -4812,6 +4814,80 @@ func TestPeriodicGC(t *testing.T) { }) } +func TestDistSpecExtensions(t *testing.T) { + Convey("start zot server with search extension", t, func(c C) { + conf := config.New() + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + + conf.HTTP.Port = port + + defaultVal := true + + searchConfig := &extconf.SearchConfig{ + Enable: &defaultVal, + } + + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + logFile, err := ioutil.TempFile("", "zot-log*.txt") + So(err, ShouldBeNil) + conf.Log.Output = logFile.Name() + defer os.Remove(logFile.Name()) // clean up + + ctlr := api.NewController(conf) + + ctlr.Config.Storage.RootDirectory = t.TempDir() + + go startServer(ctlr) + defer stopServer(ctlr) + test.WaitTillServerReady(baseURL) + + var extensionList distext.ExtensionList + + resp, err := resty.R().Get(baseURL + constants.RoutePrefix + constants.ExtOciDiscoverPrefix) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + err = json.Unmarshal(resp.Body(), &extensionList) + So(err, ShouldBeNil) + So(len(extensionList.Extensions), ShouldEqual, 1) + }) + + Convey("start minimal zot server", t, func(c C) { + conf := config.New() + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + + conf.HTTP.Port = port + + logFile, err := ioutil.TempFile("", "zot-log*.txt") + So(err, ShouldBeNil) + conf.Log.Output = logFile.Name() + defer os.Remove(logFile.Name()) // clean up + + ctlr := api.NewController(conf) + + ctlr.Config.Storage.RootDirectory = t.TempDir() + + go startServer(ctlr) + defer stopServer(ctlr) + test.WaitTillServerReady(baseURL) + + var extensionList distext.ExtensionList + resp, err := resty.R().Get(baseURL + constants.RoutePrefix + constants.ExtOciDiscoverPrefix) + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + err = json.Unmarshal(resp.Body(), &extensionList) + So(err, ShouldBeNil) + So(len(extensionList.Extensions), ShouldEqual, 0) + }) +} + func getAllBlobs(imagePath string) []string { blobList := make([]string, 0) diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 69a5afbd..bf13d90f 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -25,6 +25,7 @@ import ( "github.com/gorilla/mux" jsoniter "github.com/json-iterator/go" notreg "github.com/notaryproject/notation/pkg/registry" + "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" ispec "github.com/opencontainers/image-spec/specs-go/v1" artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" httpSwagger "github.com/swaggo/http-swagger" @@ -69,6 +70,7 @@ func (rh *RouteHandler) SetupRoutes() { rh.c.Router.Use(AuthzHandler(rh.c)) } + // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#endpoints prefixedRouter := rh.c.Router.PathPrefix(constants.RoutePrefix).Subrouter() { prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/tags/list", NameRegexp.String()), @@ -97,8 +99,10 @@ func (rh *RouteHandler) SetupRoutes() { rh.UpdateBlobUpload).Methods("PUT") prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()), rh.DeleteBlobUpload).Methods("DELETE") - prefixedRouter.HandleFunc("/_catalog", + prefixedRouter.HandleFunc(constants.ExtCatalogPrefix, rh.ListRepositories).Methods(allowedMethods("GET")...) + prefixedRouter.HandleFunc(constants.ExtOciDiscoverPrefix, + rh.ListExtensions).Methods(allowedMethods("GET")...) prefixedRouter.HandleFunc("/", rh.CheckVersionSupport).Methods(allowedMethods("GET")...) } @@ -336,6 +340,10 @@ type ImageManifest struct { ispec.Manifest } +type ExtensionList struct { + extensions.ExtensionList +} + // GetManifest godoc // @Summary Get image manifest // @Description Get an image's manifest given a reference or a digest @@ -1249,6 +1257,19 @@ func (rh *RouteHandler) ListRepositories(response http.ResponseWriter, request * WriteJSON(response, http.StatusOK, is) } +// ListExtensions godoc +// @Summary List Registry level extensions +// @Description List all extensions present on registry +// @Accept json +// @Produce json +// @Success 200 {object} api.ExtensionList +// @Router /v2/_oci/ext/discover [get]. +func (rh *RouteHandler) ListExtensions(w http.ResponseWriter, r *http.Request) { + extensionList := ext.GetExtensions(rh.c.Config) + + WriteJSON(w, http.StatusOK, extensionList) +} + func (rh *RouteHandler) GetMetrics(w http.ResponseWriter, r *http.Request) { m := rh.c.Metrics.ReceiveMetrics() WriteJSON(w, http.StatusOK, m) diff --git a/pkg/cli/client_elevated_test.go b/pkg/cli/client_elevated_test.go index 139e7e6c..3d0eb6fe 100644 --- a/pkg/cli/client_elevated_test.go +++ b/pkg/cli/client_elevated_test.go @@ -20,6 +20,7 @@ import ( "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" ) func TestElevatedPrivilegesTLSNewControllerPrivilegedCert(t *testing.T) { @@ -106,8 +107,8 @@ func TestElevatedPrivilegesTLSNewControllerPrivilegedCert(t *testing.T) { Convey("Certs in privileged path", func() { configPath := makeConfigFile( - fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s/v2/_catalog","showspinner":false}]}`, - BaseSecureURL2)) + fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, + BaseSecureURL2, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) args := []string{"imagetest"} diff --git a/pkg/cli/client_test.go b/pkg/cli/client_test.go index be151177..b0d302ab 100644 --- a/pkg/cli/client_test.go +++ b/pkg/cli/client_test.go @@ -19,6 +19,7 @@ import ( "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/test" ) @@ -114,8 +115,8 @@ func TestTLSWithAuth(t *testing.T) { args = []string{"imagetest"} configPath = makeConfigFile( - fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s/v2/_catalog","showspinner":false}]}`, - BaseSecureURL1)) + fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, + BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) imageCmd = NewImageCommand(new(searchService)) imageBuff = bytes.NewBufferString("") @@ -129,8 +130,8 @@ func TestTLSWithAuth(t *testing.T) { user := fmt.Sprintf("%s:%s", username, passphrase) args = []string{"imagetest", "-u", user} configPath = makeConfigFile( - fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s/v2/_catalog","showspinner":false}]}`, - BaseSecureURL1)) + fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, + BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) imageCmd = NewImageCommand(new(searchService)) imageBuff = bytes.NewBufferString("") @@ -185,8 +186,8 @@ func TestTLSWithoutAuth(t *testing.T) { Convey("Certs in user's home", func() { configPath := makeConfigFile( - fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s/v2/_catalog","showspinner":false}]}`, - BaseSecureURL1)) + fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, + BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) home := os.Getenv("HOME") @@ -250,8 +251,8 @@ func TestTLSBadCerts(t *testing.T) { Convey("Test with system certs", func() { configPath := makeConfigFile( - fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s/v2/_catalog","showspinner":false}]}`, - BaseSecureURL3)) + fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, + BaseSecureURL3, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) args := []string{"imagetest"} diff --git a/pkg/cli/config_cmd_test.go b/pkg/cli/config_cmd_test.go index 631246ac..c98e6fc7 100644 --- a/pkg/cli/config_cmd_test.go +++ b/pkg/cli/config_cmd_test.go @@ -5,7 +5,6 @@ package cli //nolint:testpackage import ( "bytes" - "fmt" "io/ioutil" "os" "strings" @@ -216,8 +215,6 @@ func TestConfigCmdMain(t *testing.T) { cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) - fmt.Println(err) - fmt.Println(buff.String()) So(buff.String(), ShouldContainSubstring, "does not exist") }) }) @@ -254,8 +251,6 @@ func TestConfigCmdMain(t *testing.T) { cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) - fmt.Println(err) - fmt.Println(buff.String()) So(buff.String(), ShouldContainSubstring, "does not exist") }) }) diff --git a/pkg/cli/cve_cmd_test.go b/pkg/cli/cve_cmd_test.go index 9d758049..b3b17665 100644 --- a/pkg/cli/cve_cmd_test.go +++ b/pkg/cli/cve_cmd_test.go @@ -19,6 +19,7 @@ import ( zotErrors "zotregistry.io/zot/errors" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/test" ) @@ -320,7 +321,7 @@ func TestServerCVEResponse(t *testing.T) { }(ctlr) // wait till ready for { - res, err := resty.R().Get(url + "/query") + res, err := resty.R().Get(url + constants.ExtSearchPrefix) if err == nil && res.StatusCode() == 200 { break } diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 40f2127f..bc63fa6e 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -16,6 +16,7 @@ import ( "zotregistry.io/zot/errors" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/extensions/monitoring" "zotregistry.io/zot/pkg/storage" @@ -332,7 +333,7 @@ func applyDefaultValues(config *config.Config, viperInstance *viper.Viper) { } if config.Extensions.Metrics.Prometheus == nil { - config.Extensions.Metrics.Prometheus = &extconf.PrometheusConfig{Path: "/metrics"} + config.Extensions.Metrics.Prometheus = &extconf.PrometheusConfig{Path: constants.DefaultMetricsExtensionRoute} } } } diff --git a/pkg/cli/service.go b/pkg/cli/service.go index 400eb07e..f0946c67 100644 --- a/pkg/cli/service.go +++ b/pkg/cli/service.go @@ -18,6 +18,7 @@ import ( "github.com/olekukonko/tablewriter" "gopkg.in/yaml.v2" zotErrors "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/api/constants" ) type SearchService interface { @@ -70,7 +71,8 @@ func (service searchService) getAllImages(ctx context.Context, config searchConf catalog := &catalogResponse{} - catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, "/v2/_catalog") + catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s", + constants.RoutePrefix, constants.ExtCatalogPrefix)) if err != nil { if isContextDone(ctx) { return @@ -453,7 +455,7 @@ func (service searchService) makeGraphQLQuery(ctx context.Context, config search username, password, query string, resultPtr interface{}, ) error { - endPoint, err := combineServerAndEndpointURL(*config.servURL, "/query") + endPoint, err := combineServerAndEndpointURL(*config.servURL, constants.ExtSearchPrefix) if err != nil { return err } diff --git a/pkg/compliance/v1_0_0/check.go b/pkg/compliance/v1_0_0/check.go index f47adb2c..0f8c53e1 100644 --- a/pkg/compliance/v1_0_0/check.go +++ b/pkg/compliance/v1_0_0/check.go @@ -50,14 +50,14 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { Convey("Make API calls to the controller", t, func(c C) { Convey("Check version", func() { _, _ = Print("\nCheck version") - resp, err := resty.R().Get(baseURL + "/v2/") + resp, err := resty.R().Get(baseURL + constants.RoutePrefix + "/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusOK) }) Convey("Get repository catalog", func() { _, _ = Print("\nGet repository catalog") - resp, err := resty.R().Get(baseURL + "/v2/_catalog") + resp, err := resty.R().Get(baseURL + constants.RoutePrefix + constants.ExtCatalogPrefix) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusOK) So(resp.String(), ShouldNotBeEmpty) @@ -77,7 +77,8 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) - resp, err = resty.R().SetResult(&api.RepositoryList{}).Get(baseURL + "/v2/_catalog") + resp, err = resty.R().SetResult(&api.RepositoryList{}).Get(baseURL + + constants.RoutePrefix + constants.ExtCatalogPrefix) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusOK) So(resp.String(), ShouldNotBeEmpty) diff --git a/pkg/extensions/_search.md b/pkg/extensions/_search.md new file mode 100644 index 00000000..5f5e9ae9 --- /dev/null +++ b/pkg/extensions/_search.md @@ -0,0 +1,194 @@ +`search` extension +=== + +`search` extension provides efficient and enhanced registry search capabilities using graphQL backend. + + +Table of Contents +=== + +| Supported queries | Input | Ouput | Description | graphQL query | +| --- | --- | --- | --- | --- | +| [Search images by digest](#search-images-by-digest) | digest | image list | Search all repositories in the registry and return list of images that matches given digest (manifest, config or layers) | ImageListForDigest | +| [Search images affected by a given CVE id](#search-images-affected-by-a-given-cve-id) | CVE id | image list | Search the entire registry and return list of images affected by given CVE | ImagesListForCVE | +| [List CVEs for a given image](#list-cves-of-given-image) | image | CVE list | Scan given image and return list of CVEs affecting the image | CVEListForImage | +| [List images not affected by a given CVE id](#list-images-not-affected-by-a-given-cve-id) | repository, CVE id | image list | Scan all images in a given repository and return list of latest (by date) images not affected by the given CVE |ImagesListWithCVEFixed| +| [List the latest image across every repository](#list-the-latest-image-across-every-repository) | \ | image list | Search entire registry and return a list containing the latest (by date) image in each repository | ImageListWithLatestTag | +| [List all images with expanded information for a given repository](#list-all-images-with-expanded-information-for-a-given-repository) | repository | image list | List expanded image information for all images (including manifest, all layers, etc) in a given repository | ExpandedRepoInfo | + +# Search images by digest + +**Sample request** + +``` +curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageListForDigest (id:\"63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29\") { Name Tags } }" }' http://localhost:8080/v2/_search +``` + +**Sample response** + +``` + { + "data": { + "ImageListForDigest": [{ + "Name": "centos", + "Tags": ["8"] + }, { + "Name": "v2/centos", + "Tags": ["8"] + }] + } + } +``` + +# Search images affected by a given CVE id + +**Sample request** + +``` +curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageListForCVE (id:\"CVE-2002-1119\") { Name Tags } }" }' http://localhost:8080/v2/_search +``` + +**Sample response** + +``` +{ + "data": { + "ImageListForCVE": [{ + "Name": "centos", + "Tags": ["8"] + }, { + "Name": "v2/centos", + "Tags": ["7", "8"] + }] + } +} +``` +# List CVEs of given image + +**Sample reques**t + + +``` +curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ CVEListForImage (image:\"centos\" ) { Tag CVEList { Id Title Description Severity PackageList {Name InstalledVersion FixedVersion } } } }" }' http://localhost:8080/v2/_search +``` + +**Sample response** + +``` +{ + "data": { + "CVEListForImage": { + "Tag": "", + "CVEList": [{ + "Id": "CVE-2021-3712", + "Title": "openssl: Read buffer overruns processing ASN.1 strings", + "Description": "ASN.1 strings are represented internally within OpenSSL as an ASN1_STRING structure which contains a buffer. Fixed in OpenSSL 1.1.1l (Affected 1.1.1-1.1.1k). Fixed in OpenSSL 1.0.2za (Affected 1.0.2-1.0.2y).", + "Severity": "MEDIUM", + "PackageList": [{ + "Name": "openssl-libs", + "InstalledVersion": "1:1.1.1g-11.el8", + "FixedVersion": "1:1.1.1k-5.el8_5" + }] + }] + } + } + } +``` +# List images not affected by a given CVE id + +**Sample request** + +``` +curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageListWithCVEFixed (id:\"CVE-2021-3713\",image:\"centos\") { Tags {Name Digest Timestamp} } }" }' http://localhost:8080/v2/_search +``` + +**Sample response** + +``` +{ + "data": { + "ImageListWithCVEFixed": { + "Tags": [{ + "Name": "8", + "Digest": "sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29", + "Timestamp": "2020-12-08T00:22:52.526672082Z" + }] + } + } +} +``` + +# List the latest image across every repository + +**Sample request** + +``` +curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageListWithLatestTag () { Name Latest LastUpdated Description Licenses Vendor Size Labels} }" }' http://localhost:8080/v2/_search +``` + +**Sample response** + +``` +{ + "data": { + "ImageListWithLatestTag": [{ + "Name": "centos", + "Latest": "8", + "LastUpdated": "2020-12-08T00:22:52.526672082Z", + "Description": "", + "Licenses": "GPLv2", + "Vendor": "CentOS", + "Size": "1074", + "Labels": "" + }, { + "Name": "v2/centos", + "Latest": "8", + "LastUpdated": "2020-12-08T00:22:52.526672082Z", + "Description": "", + "Licenses": "GPLv2", + "Vendor": "CentOS", + "Size": "1074", + "Labels": "" + }] + } +} +``` + +# List all images with expanded information for a given repository + +Sample request + +``` +curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ExpandedRepoInfo (repo:\"v2/centos\") { Manifests {Digest Tag IsSigned Layers {Size Digest}}} }" }' http://localhost:8080/v2/_search +``` + +**Sample response** + +``` +{ + "data": { + "ExpandedRepoInfo": { + "Manifests": [{ + "Digest": "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396", + "Tag": "7", + "IsSigned": false, + "Layers": [{ + "Size": "76097157", + "Digest": "2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc" + }] + }, { + "Digest": "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29", + "Tag": "8", + "IsSigned": false, + "Layers": [{ + "Size": "75181999", + "Digest": "7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621" + }] + }] + } + } +} +``` + +# References +[1] https://github.com/opencontainers/distribution-spec/tree/main/extensions diff --git a/pkg/extensions/extensions.go b/pkg/extensions/extensions.go index 790f5084..630eb9f7 100644 --- a/pkg/extensions/extensions.go +++ b/pkg/extensions/extensions.go @@ -5,13 +5,16 @@ package extensions import ( "context" + "fmt" goSync "sync" "time" gqlHandler "github.com/99designs/gqlgen/graphql/handler" "github.com/gorilla/mux" + distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" "github.com/prometheus/client_golang/prometheus/promhttp" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/extensions/scrub" "zotregistry.io/zot/pkg/extensions/search" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" @@ -61,9 +64,10 @@ func EnableExtensions(config *config.Config, log log.Logger, rootDir string) { *config.Extensions.Metrics.Enable && config.Extensions.Metrics.Prometheus != nil { if config.Extensions.Metrics.Prometheus.Path == "" { - config.Extensions.Metrics.Prometheus.Path = "/metrics" + config.Extensions.Metrics.Prometheus.Path = constants.DefaultMetricsExtensionRoute - log.Warn().Msg("Prometheus instrumentation Path not set, changing to '/metrics'.") + log.Warn().Msg(fmt.Sprintf("Prometheus instrumentation Path not set, changing to %s.", + constants.DefaultMetricsExtensionRoute)) } } else { log.Info().Msg("Metrics config not provided, skipping Metrics config update") @@ -108,9 +112,34 @@ func EnableScrubExtension(config *config.Config, storeController storage.StoreCo } } +func getExtension(name, url, description string) distext.Extension { + return distext.Extension{ + Name: name, + URL: url, + Description: description, + } +} + +func GetExtensions(config *config.Config) distext.ExtensionList { + extensionList := distext.ExtensionList{} + + extensions := make([]distext.Extension, 0) + + if config.Extensions != nil && config.Extensions.Search != nil { + searchExt := getExtension("search", + "https://github.com/project-zot/zot/tree/main/pkg/extensions/search/_search.md", + "search extension to provide various search feature e.g cve") + + extensions = append(extensions, searchExt) + } + + extensionList.Extensions = extensions + + return extensionList +} + // SetupRoutes ... -func SetupRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, - l log.Logger, +func SetupRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, l log.Logger, ) { // fork a new zerolog child to avoid data race log := log.Logger{Logger: l.With().Caller().Timestamp().Logger()} @@ -125,7 +154,7 @@ func SetupRoutes(config *config.Config, router *mux.Router, storeController stor resConfig = search.GetResolverConfig(log, storeController, false) } - router.PathPrefix("/query").Methods("GET", "POST", "OPTIONS"). + router.PathPrefix(constants.ExtSearchPrefix).Methods("OPTIONS", "GET", "POST"). Handler(gqlHandler.NewDefaultServer(search.NewExecutableSchema(resConfig))) } diff --git a/pkg/extensions/minimal.go b/pkg/extensions/minimal.go index 2126bc5a..b5f1cfff 100644 --- a/pkg/extensions/minimal.go +++ b/pkg/extensions/minimal.go @@ -9,6 +9,7 @@ import ( "time" "github.com/gorilla/mux" + distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/storage" @@ -25,6 +26,11 @@ func EnableExtensions(config *config.Config, log log.Logger, rootDir string) { "any extensions, please build zot full binary for this feature") } +// GetExtensions... +func GetExtensions(config *config.Config) distext.ExtensionList { + return distext.ExtensionList{} +} + // EnableSyncExtension ... func EnableSyncExtension(ctx context.Context, config *config.Config, wg *goSync.WaitGroup, storeController storage.StoreController, log log.Logger, @@ -42,7 +48,8 @@ func EnableScrubExtension(config *config.Config, storeController storage.StoreCo } // SetupRoutes ... -func SetupRoutes(conf *config.Config, router *mux.Router, storeController storage.StoreController, log log.Logger) { +func SetupRoutes(conf *config.Config, router *mux.Router, storeController storage.StoreController, log log.Logger, +) { log.Warn().Msg("skipping setting up extensions routes because given zot binary doesn't support " + "any extensions, please build zot full binary for this feature") } diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index cb834312..765ea2b9 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -16,6 +16,7 @@ import ( "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/extensions/monitoring" "zotregistry.io/zot/pkg/extensions/search/common" @@ -24,6 +25,10 @@ import ( . "zotregistry.io/zot/pkg/test" ) +const ( + graphqlQueryPrefix = constants.ExtSearchPrefix +) + // nolint:gochecknoglobals var ( rootDir string @@ -225,12 +230,12 @@ func TestLatestTagSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -243,7 +248,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { images := responseStruct.ImgListWithLatestTag.Images So(images[0].Latest, ShouldEqual, "0.0.1") - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) @@ -252,7 +257,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -273,7 +278,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -284,7 +289,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -295,7 +300,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -307,7 +312,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + "/query?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -363,14 +368,14 @@ func TestExpandedRepoInfo(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) query := "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}" - resp, err = resty.R().Get(baseURL + "/query?query=" + query) + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -384,13 +389,13 @@ func TestExpandedRepoInfo(t *testing.T) { query = "{ExpandedRepoInfo(repo:\"\"){Manifests%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}" - resp, err = resty.R().Get(baseURL + "/query?query=" + query) + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) query = "{ExpandedRepoInfo(repo:\"a/zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}" - resp, err = resty.R().Get(baseURL + "/query?query=" + query) + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -408,7 +413,7 @@ func TestExpandedRepoInfo(t *testing.T) { query = "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}" - resp, err = resty.R().Get(baseURL + "/query?query=" + query) + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) diff --git a/pkg/extensions/search/cve/cve_test.go b/pkg/extensions/search/cve/cve_test.go index 1cd086cf..b52113ae 100644 --- a/pkg/extensions/search/cve/cve_test.go +++ b/pkg/extensions/search/cve/cve_test.go @@ -20,6 +20,7 @@ import ( "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/extensions/monitoring" "zotregistry.io/zot/pkg/extensions/search/common" @@ -430,7 +431,7 @@ func TestCVESearch(t *testing.T) { err = json.Unmarshal(resp.Body(), &apiErr) So(err, ShouldBeNil) - resp, err = resty.R().Get(baseURL + "/query/") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix) So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 401) @@ -446,11 +447,11 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -461,7 +462,7 @@ func TestCVESearch(t *testing.T) { cvid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -470,7 +471,7 @@ func TestCVESearch(t *testing.T) { So(err, ShouldBeNil) So(len(imgFixedCVEResult.ImgResults.ImgResultForFixedCVE.Tags), ShouldEqual, 0) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-cve-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-cve-test\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -478,11 +479,11 @@ func TestCVESearch(t *testing.T) { So(err, ShouldBeNil) So(len(imgFixedCVEResult.ImgResults.ImgResultForFixedCVE.Tags), ShouldEqual, 0) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"b/zot-squashfs-test:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"b/zot-squashfs-test:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -491,108 +492,108 @@ func TestCVESearch(t *testing.T) { So(err, ShouldBeNil) So(len(cveSquashFSResult.ImgList.CVEResultForImage.CVEList), ShouldBeZeroValue) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-squashfs-noindex:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-noindex:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noindex\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noindex\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-squashfs-invalid-index:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-invalid-index:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-index\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-index\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-squashfs-noblobs:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-noblobs:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noblob\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noblob\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-test\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-squashfs-invalid-blob:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-invalid-blob:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-blob\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-blob\"){Tags{Name%20Timestamp}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-squashfs-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"cntos\"){Tag%20CVEList{Id%20Description%20Severity}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"cntos\"){Tag%20CVEList{Id%20Description%20Severity}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListForCVE(id:\"CVE-201-20482\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description%20Severity}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description%20Severity}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description%20Severity}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description%20Severity}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Severity}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Severity}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) // Testing Invalid Search URL - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(image:\"zot-test:0.0.1\"){Ta%20CVEList{Id%20Description%20Severity}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Ta%20CVEList{Id%20Description%20Severity}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListForCVE(tet:\"CVE-2018-20482\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(tet:\"CVE-2018-20482\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageistForCVE(id:\"CVE-2018-20482\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageistForCVE(id:\"CVE-2018-20482\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListForCVE(id:\"CVE-2018-20482\"){ame%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"CVE-2018-20482\"){ame%20Tags}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={CVEListForImage(reo:\"zot-test:1.0.0\"){Tag%20CVEList{Id%20Description%20Severity}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={CVEListForImage(reo:\"zot-test:1.0.0\"){Tag%20CVEList{Id%20Description%20Severity}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/query?query={ImageListForCVE(id:\"" + cvid + "\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"" + cvid + "\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) @@ -648,11 +649,11 @@ func TestCVEConfig(t *testing.T) { time.Sleep(100 * time.Millisecond) } - resp, _ := resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/") + resp, _ := resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.RoutePrefix + "/") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/_catalog") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.RoutePrefix + constants.ExtCatalogPrefix) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -721,7 +722,7 @@ func TestHTTPOptionsResponse(t *testing.T) { time.Sleep(100 * time.Millisecond) } - resp, _ := resty.R().Options(baseURL + "/v2/_catalog") + resp, _ := resty.R().Options(baseURL + constants.RoutePrefix + constants.ExtCatalogPrefix) So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, http.StatusNoContent) diff --git a/pkg/extensions/search/digest/digest_test.go b/pkg/extensions/search/digest/digest_test.go index efebfc24..ad2d3033 100644 --- a/pkg/extensions/search/digest/digest_test.go +++ b/pkg/extensions/search/digest/digest_test.go @@ -16,6 +16,7 @@ import ( "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/extensions/monitoring" digestinfo "zotregistry.io/zot/pkg/extensions/search/digest" @@ -179,13 +180,14 @@ func TestDigestSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) // "sha" should match all digests in all images - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -200,7 +202,8 @@ func TestDigestSearchHTTP(t *testing.T) { // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}} // "2bacca16" should match the manifest of 1 image - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"2bacca16\"){Name%20Tags}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"2bacca16\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -215,7 +218,8 @@ func TestDigestSearchHTTP(t *testing.T) { // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}} // "adf3bb6c" should match the config of 1 image - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"adf3bb6c\"){Name%20Tags}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"adf3bb6c\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -230,7 +234,8 @@ func TestDigestSearchHTTP(t *testing.T) { // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}} // "7a0437f0" should match the layer of 1 image - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"7a0437f0\"){Name%20Tags}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"7a0437f0\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -245,7 +250,8 @@ func TestDigestSearchHTTP(t *testing.T) { // Call should return {"data":{"ImageListForDigest":[]}} // "1111111" should match 0 images - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"1111111\"){Name%20Tags}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"1111111\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -256,7 +262,8 @@ func TestDigestSearchHTTP(t *testing.T) { So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 0) // Call should return {"errors": [{....}]", data":null}} - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"1111111\"){Name%20Tag343s}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"1111111\"){Name%20Tag343s}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 422) @@ -321,12 +328,13 @@ func TestDigestSearchHTTPSubPaths(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + + "?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -380,7 +388,7 @@ func TestDigestSearchDisabled(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/query") + resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) diff --git a/pkg/extensions/sync/sync.go b/pkg/extensions/sync/sync.go index ad64be74..e31aea9c 100644 --- a/pkg/extensions/sync/sync.go +++ b/pkg/extensions/sync/sync.go @@ -21,6 +21,7 @@ import ( ispec "github.com/opencontainers/image-spec/specs-go/v1" "gopkg.in/resty.v1" zerr "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/storage" "zotregistry.io/zot/pkg/test" @@ -77,7 +78,7 @@ type Tags struct { func getUpstreamCatalog(client *resty.Client, upstreamURL string, log log.Logger) (catalog, error) { var catalog catalog - registryCatalogURL := fmt.Sprintf("%s%s", upstreamURL, "/v2/_catalog") + registryCatalogURL := fmt.Sprintf("%s%s%s", upstreamURL, constants.RoutePrefix, constants.ExtCatalogPrefix) resp, err := client.R().SetHeader("Content-Type", "application/json").Get(registryCatalogURL) if err != nil { diff --git a/pkg/extensions/sync/sync_test.go b/pkg/extensions/sync/sync_test.go index a699b2e1..9fd0f772 100644 --- a/pkg/extensions/sync/sync_test.go +++ b/pkg/extensions/sync/sync_test.go @@ -37,6 +37,7 @@ import ( "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/cli" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/extensions/sync" @@ -1315,11 +1316,11 @@ func TestNoImagesByRegex(t *testing.T) { dctlr.Shutdown() }() - resp, err := destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag) + resp, err := destClient.R().Get(destBaseURL + constants.RoutePrefix + testImage + "/manifests/" + testImageTag) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) - resp, err = destClient.R().Get(destBaseURL + "/v2/_catalog") + resp, err = destClient.R().Get(destBaseURL + constants.RoutePrefix + constants.ExtCatalogPrefix) So(err, ShouldBeNil) So(resp, ShouldNotBeEmpty) So(resp.StatusCode(), ShouldEqual, 200) @@ -1743,7 +1744,7 @@ func TestSubPaths(t *testing.T) { var destTagsList TagsList for { - resp, err := resty.R().Get(destBaseURL + "/v2" + path.Join(subpath, testImage) + "/tags/list") + resp, err := resty.R().Get(destBaseURL + constants.RoutePrefix + path.Join(subpath, testImage) + "/tags/list") if err != nil { panic(err) } @@ -2169,7 +2170,7 @@ func TestPeriodicallySignaturesErr(t *testing.T) { cosignTag := string(imageManifestDigest.Algorithm()) + "-" + imageManifestDigest.Hex() + "." + remote.SignatureTagSuffix - getCosignManifestURL := srcBaseURL + path.Join("/v2", repoName, "manifests", cosignTag) + getCosignManifestURL := srcBaseURL + path.Join(constants.RoutePrefix, repoName, "manifests", cosignTag) mResp, err := resty.R().Get(getCosignManifestURL) So(err, ShouldBeNil) @@ -2444,7 +2445,7 @@ func TestSignatures(t *testing.T) { // test cosign signatures errors // based on manifest digest get cosign manifest cosignEncodedDigest := strings.Replace(digest.String(), ":", "-", 1) + ".sig" - getCosignManifestURL := srcBaseURL + path.Join("/v2", repoName, "manifests", cosignEncodedDigest) + getCosignManifestURL := srcBaseURL + path.Join(constants.RoutePrefix, repoName, "manifests", cosignEncodedDigest) mResp, err := resty.R().Get(getCosignManifestURL) So(err, ShouldBeNil) @@ -3057,7 +3058,7 @@ func TestSignaturesOnDemand(t *testing.T) { // test negative case cosignEncodedDigest := strings.Replace(digest.String(), ":", "-", 1) + ".sig" - getCosignManifestURL := srcBaseURL + path.Join("/v2", repoName, "manifests", cosignEncodedDigest) + getCosignManifestURL := srcBaseURL + path.Join(constants.RoutePrefix, repoName, "manifests", cosignEncodedDigest) mResp, err := resty.R().Get(getCosignManifestURL) So(err, ShouldBeNil) diff --git a/swagger/docs.go b/swagger/docs.go index 657b7ad1..c86ebffe 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -1,12 +1,13 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2019-12-11 12:03:05.055900322 -0800 PST m=+0.052058015 +// 2022-05-20 22:28:21.990630149 +0000 UTC m=+0.151110280 package swagger import ( "bytes" "encoding/json" + "strings" "github.com/alecthomas/template" "github.com/swaggo/swag" @@ -16,8 +17,8 @@ var doc = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { - "description": "APIs for Open Container Initiative Distribution Specification", - "title": "Open Container Initiative Distribution Specification", + "description": "{{.Description}}", + "title": "{{.Title}}", "contact": { "name": "API Support", "url": "http://www.swagger.io/support", @@ -27,11 +28,66 @@ var doc = `{ "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "v0.1.0-dev" + "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/oras/artifacts/v1/{name": { + "get": { + "description": "Get references for an image given a digest and artifact type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get references for an image", + "parameters": [ + { + "type": "string", + "description": "repository name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "image digest", + "name": "digest", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "artifact type", + "name": "artifactType", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "404": { + "description": "not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "internal server error", + "schema": { + "type": "string" + } + } + } + } + }, "/v2/": { "get": { "description": "Check if this API version is supported", @@ -44,7 +100,7 @@ var doc = `{ "summary": "Check API support", "responses": { "200": { - "description": "ok", + "description": "ok\".", "schema": { "type": "string" } @@ -66,7 +122,6 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.RepositoryList" } }, @@ -79,6 +134,26 @@ var doc = `{ } } }, + "/v2/_oci/ext/discover": { + "get": { + "description": "List all extensions present on registry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Registry level extensions", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.ExtensionList" + } + } + } + } + }, "/v2/{name}/blobs/uploads": { "post": { "description": "Create a new image blob/layer upload", @@ -107,7 +182,7 @@ var doc = `{ "headers": { "Location": { "type": "string", - "description": "/v2/{name}/blobs/uploads/{uuid}" + "description": "/v2/{name}/blobs/uploads/{session_id}" }, "Range": { "type": "string", @@ -130,9 +205,9 @@ var doc = `{ } } }, - "/v2/{name}/blobs/uploads/{uuid}": { + "/v2/{name}/blobs/uploads/{session_id}": { "get": { - "description": "Get an image's blob/layer upload given a uuid", + "description": "Get an image's blob/layer upload given a session_id", "consumes": [ "application/json" ], @@ -150,8 +225,8 @@ var doc = `{ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true } @@ -196,8 +271,8 @@ var doc = `{ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true }, @@ -249,8 +324,8 @@ var doc = `{ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true } @@ -277,7 +352,7 @@ var doc = `{ } }, "patch": { - "description": "Resume an image's blob/layer upload given an uuid", + "description": "Resume an image's blob/layer upload given an session_id", "consumes": [ "application/json" ], @@ -295,8 +370,8 @@ var doc = `{ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true } @@ -310,7 +385,7 @@ var doc = `{ "headers": { "Location": { "type": "string", - "description": "/v2/{name}/blobs/uploads/{uuid}" + "description": "/v2/{name}/blobs/uploads/{session_id}" }, "Range": { "type": "string", @@ -375,7 +450,6 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageManifest" } } @@ -444,11 +518,10 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageManifest" }, "headers": { - "api.DistContentDigestKey": { + "constants.DistContentDigestKey": { "type": "object", "description": "OK" } @@ -487,11 +560,10 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageManifest" }, "headers": { - "api.DistContentDigestKey": { + "constants.DistContentDigestKey": { "type": "object", "description": "OK" } @@ -629,7 +701,7 @@ var doc = `{ "type": "string" }, "headers": { - "api.DistContentDigestKey": { + "cosntants.DistContentDigestKey": { "type": "object", "description": "OK" } @@ -642,7 +714,7 @@ var doc = `{ } }, "500": { - "description": "internal server error", + "description": "internal server error\".", "schema": { "type": "string" } @@ -667,16 +739,35 @@ var doc = `{ "name": "name", "in": "path", "required": true + }, + { + "type": "integer", + "description": "limit entries for pagination", + "name": "n", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "last tag value for pagination", + "name": "last", + "in": "query", + "required": true } ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageTags" } }, + "400": { + "description": "bad request\".", + "schema": { + "type": "string" + } + }, "404": { "description": "not found", "schema": { @@ -688,6 +779,9 @@ var doc = `{ } }, "definitions": { + "api.ExtensionList": { + "type": "object" + }, "api.ImageManifest": { "type": "object" }, @@ -729,7 +823,14 @@ type swaggerInfo struct { } // SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = swaggerInfo{Schemes: []string{}} +var SwaggerInfo = swaggerInfo{ + Version: "v0.1.0-dev", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Open Container Initiative Distribution Specification", + Description: "APIs for Open Container Initiative Distribution Specification", +} type s struct{} @@ -737,7 +838,11 @@ func New() *s { return &s{} } + func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + t, err := template.New("swagger_info").Funcs(template.FuncMap{ "marshal": func(v interface{}) string { a, _ := json.Marshal(v) @@ -749,7 +854,7 @@ func (s *s) ReadDoc() string { } var tpl bytes.Buffer - if err := t.Execute(&tpl, SwaggerInfo); err != nil { + if err := t.Execute(&tpl, sInfo); err != nil { return doc } diff --git a/swagger/swagger.json b/swagger/swagger.json index cb98df5c..054bb4ae 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -14,9 +14,62 @@ }, "version": "v0.1.0-dev" }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", "paths": { + "/oras/artifacts/v1/{name": { + "get": { + "description": "Get references for an image given a digest and artifact type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get references for an image", + "parameters": [ + { + "type": "string", + "description": "repository name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "image digest", + "name": "digest", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "artifact type", + "name": "artifactType", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "404": { + "description": "not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "internal server error", + "schema": { + "type": "string" + } + } + } + } + }, "/v2/": { "get": { "description": "Check if this API version is supported", @@ -29,7 +82,7 @@ "summary": "Check API support", "responses": { "200": { - "description": "ok", + "description": "ok\".", "schema": { "type": "string" } @@ -51,7 +104,6 @@ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.RepositoryList" } }, @@ -64,6 +116,26 @@ } } }, + "/v2/_oci/ext/discover": { + "get": { + "description": "List all extensions present on registry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "List Registry level extensions", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.ExtensionList" + } + } + } + } + }, "/v2/{name}/blobs/uploads": { "post": { "description": "Create a new image blob/layer upload", @@ -92,7 +164,7 @@ "headers": { "Location": { "type": "string", - "description": "/v2/{name}/blobs/uploads/{uuid}" + "description": "/v2/{name}/blobs/uploads/{session_id}" }, "Range": { "type": "string", @@ -115,9 +187,9 @@ } } }, - "/v2/{name}/blobs/uploads/{uuid}": { + "/v2/{name}/blobs/uploads/{session_id}": { "get": { - "description": "Get an image's blob/layer upload given a uuid", + "description": "Get an image's blob/layer upload given a session_id", "consumes": [ "application/json" ], @@ -135,8 +207,8 @@ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true } @@ -181,8 +253,8 @@ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true }, @@ -234,8 +306,8 @@ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true } @@ -262,7 +334,7 @@ } }, "patch": { - "description": "Resume an image's blob/layer upload given an uuid", + "description": "Resume an image's blob/layer upload given an session_id", "consumes": [ "application/json" ], @@ -280,8 +352,8 @@ }, { "type": "string", - "description": "upload uuid", - "name": "uuid", + "description": "upload session_id", + "name": "session_id", "in": "path", "required": true } @@ -295,7 +367,7 @@ "headers": { "Location": { "type": "string", - "description": "/v2/{name}/blobs/uploads/{uuid}" + "description": "/v2/{name}/blobs/uploads/{session_id}" }, "Range": { "type": "string", @@ -360,7 +432,6 @@ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageManifest" } } @@ -429,11 +500,10 @@ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageManifest" }, "headers": { - "api.DistContentDigestKey": { + "constants.DistContentDigestKey": { "type": "object", "description": "OK" } @@ -472,11 +542,10 @@ "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageManifest" }, "headers": { - "api.DistContentDigestKey": { + "constants.DistContentDigestKey": { "type": "object", "description": "OK" } @@ -614,7 +683,7 @@ "type": "string" }, "headers": { - "api.DistContentDigestKey": { + "cosntants.DistContentDigestKey": { "type": "object", "description": "OK" } @@ -627,7 +696,7 @@ } }, "500": { - "description": "internal server error", + "description": "internal server error\".", "schema": { "type": "string" } @@ -652,16 +721,35 @@ "name": "name", "in": "path", "required": true + }, + { + "type": "integer", + "description": "limit entries for pagination", + "name": "n", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "last tag value for pagination", + "name": "last", + "in": "query", + "required": true } ], "responses": { "200": { "description": "OK", "schema": { - "type": "object", "$ref": "#/definitions/api.ImageTags" } }, + "400": { + "description": "bad request\".", + "schema": { + "type": "string" + } + }, "404": { "description": "not found", "schema": { @@ -673,6 +761,9 @@ } }, "definitions": { + "api.ExtensionList": { + "type": "object" + }, "api.ImageManifest": { "type": "object" }, diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 0c5f717f..23b912cd 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -1,5 +1,6 @@ -basePath: '{{.BasePath}}' definitions: + api.ExtensionList: + type: object api.ImageManifest: type: object api.ImageTags: @@ -18,7 +19,6 @@ definitions: type: string type: array type: object -host: '{{.Host}}' info: contact: email: support@swagger.io @@ -31,6 +31,43 @@ info: title: Open Container Initiative Distribution Specification version: v0.1.0-dev paths: + /oras/artifacts/v1/{name: + get: + consumes: + - application/json + description: Get references for an image given a digest and artifact type + parameters: + - description: repository name + in: path + name: name + required: true + type: string + - description: image digest + in: path + name: digest + required: true + type: string + - description: artifact type + in: query + name: artifactType + required: true + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + "404": + description: not found + schema: + type: string + "500": + description: internal server error + schema: + type: string + summary: Get references for an image /v2/: get: consumes: @@ -40,7 +77,7 @@ paths: - application/json responses: "200": - description: ok + description: ok". schema: type: string summary: Check API support @@ -56,12 +93,24 @@ paths: description: OK schema: $ref: '#/definitions/api.RepositoryList' - type: object "500": description: internal server error schema: type: string summary: List image repositories + /v2/_oci/ext/discover: + get: + consumes: + - application/json + description: List all extensions present on registry + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.ExtensionList' + summary: List Registry level extensions /v2/{name}/blobs/{digest}: delete: consumes: @@ -108,7 +157,6 @@ paths: description: OK schema: $ref: '#/definitions/api.ImageManifest' - type: object summary: Get image blob/layer head: consumes: @@ -131,12 +179,11 @@ paths: "200": description: OK headers: - api.DistContentDigestKey: + constants.DistContentDigestKey: description: OK type: object schema: $ref: '#/definitions/api.ImageManifest' - type: object summary: Check image blob/layer /v2/{name}/blobs/uploads: post: @@ -156,7 +203,7 @@ paths: description: accepted headers: Location: - description: /v2/{name}/blobs/uploads/{uuid} + description: /v2/{name}/blobs/uploads/{session_id} type: string Range: description: bytes=0-0 @@ -172,7 +219,7 @@ paths: schema: type: string summary: Create image blob/layer upload - /v2/{name}/blobs/uploads/{uuid}: + /v2/{name}/blobs/uploads/{session_id}: delete: consumes: - application/json @@ -183,9 +230,9 @@ paths: name: name required: true type: string - - description: upload uuid + - description: upload session_id in: path - name: uuid + name: session_id required: true type: string produces: @@ -207,16 +254,16 @@ paths: get: consumes: - application/json - description: Get an image's blob/layer upload given a uuid + description: Get an image's blob/layer upload given a session_id parameters: - description: repository name in: path name: name required: true type: string - - description: upload uuid + - description: upload session_id in: path - name: uuid + name: session_id required: true type: string produces: @@ -238,16 +285,16 @@ paths: patch: consumes: - application/json - description: Resume an image's blob/layer upload given an uuid + description: Resume an image's blob/layer upload given an session_id parameters: - description: repository name in: path name: name required: true type: string - - description: upload uuid + - description: upload session_id in: path - name: uuid + name: session_id required: true type: string produces: @@ -257,7 +304,7 @@ paths: description: accepted headers: Location: - description: /v2/{name}/blobs/uploads/{uuid} + description: /v2/{name}/blobs/uploads/{session_id} type: string Range: description: bytes=0-128 @@ -291,9 +338,9 @@ paths: name: name required: true type: string - - description: upload uuid + - description: upload session_id in: path - name: uuid + name: session_id required: true type: string - description: blob/layer digest @@ -362,12 +409,11 @@ paths: "200": description: OK headers: - api.DistContentDigestKey: + constants.DistContentDigestKey: description: OK type: object schema: $ref: '#/definitions/api.ImageManifest' - type: object "404": description: not found schema: @@ -398,7 +444,7 @@ paths: "200": description: ok headers: - api.DistContentDigestKey: + cosntants.DistContentDigestKey: description: OK type: object schema: @@ -408,7 +454,7 @@ paths: schema: type: string "500": - description: internal server error + description: internal server error". schema: type: string summary: Check image manifest @@ -458,6 +504,16 @@ paths: name: name required: true type: string + - description: limit entries for pagination + in: query + name: "n" + required: true + type: integer + - description: last tag value for pagination + in: query + name: last + required: true + type: string produces: - application/json responses: @@ -465,7 +521,10 @@ paths: description: OK schema: $ref: '#/definitions/api.ImageTags' - type: object + "400": + description: bad request". + schema: + type: string "404": description: not found schema: