From 2b8479f7f2c2ebb1d73ae20399538cb22027114b Mon Sep 17 00:00:00 2001 From: LaurentiuNiculae Date: Thu, 25 May 2023 14:46:52 +0300 Subject: [PATCH] feat(userprefs): update documentation and list extensions endpoint (#1456) Signed-off-by: Laurentiu Niculae --- pkg/api/controller_test.go | 18 ++--- pkg/extensions/_zot.md | 1 + pkg/extensions/extension_mgmt.go | 4 + pkg/extensions/extension_mgmt_disabled.go | 4 + pkg/extensions/extension_search.go | 44 +---------- pkg/extensions/extension_search_disabled.go | 10 +-- pkg/extensions/extension_userprefs.go | 4 + pkg/extensions/extension_userprefs_disable.go | 4 + pkg/extensions/get_extensions.go | 42 ++++++++++ .../get_extensions_disabled_test.go | 76 +++++++++++++++++++ pkg/extensions/userprefs.md | 31 ++++++++ 11 files changed, 182 insertions(+), 56 deletions(-) create mode 100644 pkg/extensions/get_extensions.go create mode 100644 pkg/extensions/get_extensions_disabled_test.go create mode 100644 pkg/extensions/userprefs.md diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index faaa4d21..faa538ad 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -7249,11 +7249,12 @@ func TestDistSpecExtensions(t *testing.T) { err = json.Unmarshal(resp.Body(), &extensionList) So(err, ShouldBeNil) So(len(extensionList.Extensions), ShouldEqual, 1) - So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 1) + So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 2) So(extensionList.Extensions[0].Name, ShouldEqual, "_zot") So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md") So(extensionList.Extensions[0].Description, ShouldNotBeEmpty) - So(extensionList.Extensions[0].Endpoints[0], ShouldEqual, constants.FullSearchPrefix) + So(extensionList.Extensions[0].Endpoints, ShouldContain, constants.FullSearchPrefix) + So(extensionList.Extensions[0].Endpoints, ShouldContain, constants.FullUserPreferencesPrefix) }) Convey("start zot server with search and mgmt extensions", t, func(c C) { @@ -7297,17 +7298,14 @@ func TestDistSpecExtensions(t *testing.T) { So(resp.StatusCode(), ShouldEqual, 200) err = json.Unmarshal(resp.Body(), &extensionList) So(err, ShouldBeNil) - So(len(extensionList.Extensions), ShouldEqual, 2) - So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 1) - So(len(extensionList.Extensions[1].Endpoints), ShouldEqual, 1) + So(len(extensionList.Extensions), ShouldEqual, 1) + So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 3) So(extensionList.Extensions[0].Name, ShouldEqual, "_zot") So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md") So(extensionList.Extensions[0].Description, ShouldNotBeEmpty) - So(extensionList.Extensions[0].Endpoints[0], ShouldEqual, constants.FullSearchPrefix) - So(extensionList.Extensions[1].Name, ShouldEqual, "_zot") - So(extensionList.Extensions[1].URL, ShouldContainSubstring, "_zot.md") - So(extensionList.Extensions[1].Description, ShouldNotBeEmpty) - So(extensionList.Extensions[1].Endpoints[0], ShouldEqual, constants.FullMgmtPrefix) + So(extensionList.Extensions[0].Endpoints, ShouldContain, constants.FullSearchPrefix) + So(extensionList.Extensions[0].Endpoints, ShouldContain, constants.FullUserPreferencesPrefix) + So(extensionList.Extensions[0].Endpoints, ShouldContain, constants.FullMgmtPrefix) }) Convey("start minimal zot server", t, func(c C) { diff --git a/pkg/extensions/_zot.md b/pkg/extensions/_zot.md index 0cc4fb24..8f2af16f 100644 --- a/pkg/extensions/_zot.md +++ b/pkg/extensions/_zot.md @@ -7,6 +7,7 @@ Component | Endpoint | Description --- | --- | --- [`search`](search/search.md) | `/v2/_zot/ext/search` | efficient and enhanced registry search capabilities using graphQL backend [`mgmt`](mgmt.md) | `/v2/_zot/ext/mgmt` | config management +[`userprefs`](userprefs.md) | `/v2/_zot/ext/userprefs` | change user preferences # References diff --git a/pkg/extensions/extension_mgmt.go b/pkg/extensions/extension_mgmt.go index 692aaee6..8599a990 100644 --- a/pkg/extensions/extension_mgmt.go +++ b/pkg/extensions/extension_mgmt.go @@ -40,6 +40,10 @@ type StrippedConfig struct { } `json:"http" mapstructure:"http"` } +func IsBuiltWithMGMTExtension() bool { + return true +} + func (auth Auth) MarshalJSON() ([]byte, error) { type localAuth Auth diff --git a/pkg/extensions/extension_mgmt_disabled.go b/pkg/extensions/extension_mgmt_disabled.go index 8c0a7bb6..1788f34e 100644 --- a/pkg/extensions/extension_mgmt_disabled.go +++ b/pkg/extensions/extension_mgmt_disabled.go @@ -10,6 +10,10 @@ import ( "zotregistry.io/zot/pkg/log" ) +func IsBuiltWithMGMTExtension() bool { + return false +} + func SetupMgmtRoutes(config *config.Config, router *mux.Router, log log.Logger) { log.Warn().Msg("skipping setting up mgmt routes because given zot binary doesn't include this feature," + "please build a binary that does so") diff --git a/pkg/extensions/extension_search.go b/pkg/extensions/extension_search.go index b5c6ec93..affca598 100644 --- a/pkg/extensions/extension_search.go +++ b/pkg/extensions/extension_search.go @@ -10,7 +10,6 @@ import ( gqlHandler "github.com/99designs/gqlgen/graphql/handler" "github.com/gorilla/mux" - distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/constants" @@ -34,6 +33,10 @@ const ( done ) +func IsBuiltWithSearchExtension() bool { + return true +} + func GetCVEInfo(config *config.Config, storeController storage.StoreController, repoDB repodb.RepoDB, log log.Logger, ) CveInfo { @@ -199,42 +202,3 @@ func SearchACHeadersHandler() mux.MiddlewareFunc { }) } } - -func getExtension(name, url, description string, endpoints []string) distext.Extension { - return distext.Extension{ - Name: name, - URL: url, - Description: description, - Endpoints: endpoints, - } -} - -func GetExtensions(config *config.Config) distext.ExtensionList { - extensionList := distext.ExtensionList{} - - extensions := make([]distext.Extension, 0) - - if config.Extensions != nil && config.Extensions.Search != nil { - endpoints := []string{constants.FullSearchPrefix} - searchExt := getExtension("_zot", - "https://github.com/project-zot/zot/blob/"+config.ReleaseTag+"/pkg/extensions/_zot.md", - "zot registry extensions", - endpoints) - - extensions = append(extensions, searchExt) - } - - if config.Extensions != nil && config.Extensions.Mgmt != nil { - endpoints := []string{constants.FullMgmtPrefix} - mgmtExt := getExtension("_zot", - "https://github.com/project-zot/zot/blob/"+config.ReleaseTag+"/pkg/extensions/_zot.md", - "zot registry extensions", - endpoints) - - extensions = append(extensions, mgmtExt) - } - - extensionList.Extensions = extensions - - return extensionList -} diff --git a/pkg/extensions/extension_search_disabled.go b/pkg/extensions/extension_search_disabled.go index a84681a2..f16f84ed 100644 --- a/pkg/extensions/extension_search_disabled.go +++ b/pkg/extensions/extension_search_disabled.go @@ -5,7 +5,6 @@ package extensions import ( "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" @@ -22,6 +21,10 @@ func GetCVEInfo(config *config.Config, storeController storage.StoreController, return nil } +func IsBuiltWithSearchExtension() bool { + return false +} + // EnableSearchExtension ... func EnableSearchExtension(config *config.Config, storeController storage.StoreController, repoDB repodb.RepoDB, scheduler *scheduler.Scheduler, cveInfo CveInfo, log log.Logger, @@ -37,8 +40,3 @@ func SetupSearchRoutes(config *config.Config, router *mux.Router, storeControlle log.Warn().Msg("skipping setting up search routes because given zot binary doesn't include this feature," + "please build a binary that does so") } - -// GetExtensions... -func GetExtensions(config *config.Config) distext.ExtensionList { - return distext.ExtensionList{} -} diff --git a/pkg/extensions/extension_userprefs.go b/pkg/extensions/extension_userprefs.go index 58505bb9..01696440 100644 --- a/pkg/extensions/extension_userprefs.go +++ b/pkg/extensions/extension_userprefs.go @@ -24,6 +24,10 @@ const ( ToggleRepoStarAction = "toggleStar" ) +func IsBuiltWithUserPrefsExtension() bool { + return true +} + func SetupUserPreferencesRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, repoDB repodb.RepoDB, cveInfo CveInfo, log log.Logger, ) { diff --git a/pkg/extensions/extension_userprefs_disable.go b/pkg/extensions/extension_userprefs_disable.go index 29b16a42..1947a4f7 100644 --- a/pkg/extensions/extension_userprefs_disable.go +++ b/pkg/extensions/extension_userprefs_disable.go @@ -12,6 +12,10 @@ import ( "zotregistry.io/zot/pkg/storage" ) +func IsBuiltWithUserPrefsExtension() bool { + return false +} + func SetupUserPreferencesRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, repoDB repodb.RepoDB, cveInfo CveInfo, log log.Logger, ) { diff --git a/pkg/extensions/get_extensions.go b/pkg/extensions/get_extensions.go new file mode 100644 index 00000000..d5f67b23 --- /dev/null +++ b/pkg/extensions/get_extensions.go @@ -0,0 +1,42 @@ +package extensions + +import ( + distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" + + "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" +) + +func GetExtensions(config *config.Config) distext.ExtensionList { + extensionList := distext.ExtensionList{} + + endpoints := []string{} + extensions := []distext.Extension{} + + if config.Extensions != nil && config.Extensions.Search != nil { + if IsBuiltWithSearchExtension() { + endpoints = append(endpoints, constants.FullSearchPrefix) + } + + if IsBuiltWithUserPrefsExtension() { + endpoints = append(endpoints, constants.FullUserPreferencesPrefix) + } + } + + if IsBuiltWithMGMTExtension() && config.Extensions != nil && config.Extensions.Mgmt != nil { + endpoints = append(endpoints, constants.FullMgmtPrefix) + } + + if len(endpoints) > 0 { + extensions = append(extensions, distext.Extension{ + Name: "_zot", + URL: "https://github.com/project-zot/zot/blob/" + config.ReleaseTag + "/pkg/extensions/_zot.md", + Description: "zot registry extensions", + Endpoints: endpoints, + }) + } + + extensionList.Extensions = extensions + + return extensionList +} diff --git a/pkg/extensions/get_extensions_disabled_test.go b/pkg/extensions/get_extensions_disabled_test.go new file mode 100644 index 00000000..7975d01c --- /dev/null +++ b/pkg/extensions/get_extensions_disabled_test.go @@ -0,0 +1,76 @@ +//go:build !search && !mgmt && !userprefs + +package extensions_test + +import ( + "encoding/json" + "os" + "testing" + + distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions" + . "github.com/smartystreets/goconvey/convey" + "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/test" +) + +func TestGetExensionsDisabled(t *testing.T) { + Convey("start zot server with extensions but no extensions built", t, func(c C) { + conf := config.New() + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + + conf.HTTP.Port = port + + defaultVal := true + + searchConfig := &extconf.SearchConfig{ + BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, + } + + mgmtConfg := &extconf.MgmtConfig{ + BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, + } + + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + Mgmt: mgmtConfg, + } + + logFile, err := os.CreateTemp("", "zot-log*.txt") + So(err, ShouldBeNil) + conf.Log.Output = logFile.Name() + defer os.Remove(logFile.Name()) // clean up + + ctlr := makeController(conf, t.TempDir(), "") + + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(port) + defer cm.StopServer() + + 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 makeController(conf *config.Config, dir string, copyTestDataDest string) *api.Controller { + ctlr := api.NewController(conf) + + if copyTestDataDest != "" { + test.CopyTestFiles(copyTestDataDest, dir) + } + ctlr.Config.Storage.RootDirectory = dir + + return ctlr +} diff --git a/pkg/extensions/userprefs.md b/pkg/extensions/userprefs.md new file mode 100644 index 00000000..d29611d0 --- /dev/null +++ b/pkg/extensions/userprefs.md @@ -0,0 +1,31 @@ +# `userprefs` + +`userprefs` component provides an endpoint for adding user preferences for repos. It is available only to authentificated users. Unauthentificated users will be denied access. + +| Supported queries | Input | Output | Description | +| --- | --- | --- | --- | +| [Toggle repo star](#toggle-repo-star) | None | None | Sets the repo starred property to true if it is false, and to false if it is true | +| [Toggle repo bookmark](#toggle-repo-bookmark) | None | None | Sets the repo bookmarked property to true if it is false, and to false if it is true | + +## General usage +The userprefs endpoint accepts as a query parameter what `action` to perform and then all other required parameters for the specified action. + +## Toggle repo star +| Action | Parameter | Parameter Type | Parameter Description | +| --- | --- | --- | --- | +| toggleStar | repo | string | The repo name which should be starred | + +A request to togle a star on a repo would look like this: +``` +(PUT) http://localhost:8080/v2/_zot/ext/userprefs?action=toggleStar&repo=repoName +``` + +## Toggle repo bookmark +| Action | Parameter | Parameter Type | Parameter Description | +| --- | --- | --- | --- | +| toggleBookmark | repo | string | The repo name which should be bookmarked | + +A request to togle a bookmark on a repo would look like this: +``` +(PUT) http://localhost:8080/v2/_zot/ext/userprefs?action=toggleBookmark&repo=repoName +```